From 69158cd4f3074fb2a08c53375acd1c29087d0040 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Tue, 6 Apr 2010 14:27:51 -0400 Subject: [PATCH 001/430] first commit --- dbal/manual/en.txt | 1 + dbal/manual/en/dbal.txt | 245 ++++++ migrations/manual/en.txt | 0 orm/manual/en.txt | 20 + orm/manual/en/annotations-reference.txt | 557 ++++++++++++ orm/manual/en/architecture.txt | 81 ++ orm/manual/en/association-mapping.txt | 429 ++++++++++ orm/manual/en/basic-mapping.txt | 279 ++++++ orm/manual/en/batch-processing.txt | 122 +++ orm/manual/en/best-practices.txt | 77 ++ orm/manual/en/caching.txt | 350 ++++++++ orm/manual/en/configuration.txt | 407 +++++++++ orm/manual/en/dql-doctrine-query-language.txt | 803 ++++++++++++++++++ orm/manual/en/events.txt | 304 +++++++ orm/manual/en/improving-performance.txt | 25 + orm/manual/en/inheritance-mapping.txt | 141 +++ orm/manual/en/introduction.txt | 222 +++++ orm/manual/en/native-sql.txt | 196 +++++ orm/manual/en/query-builder.txt | 356 ++++++++ orm/manual/en/tools.txt | 192 +++++ .../en/transactions-and-concurrency.txt | 124 +++ orm/manual/en/working-with-objects.txt | 445 ++++++++++ orm/manual/en/xml-mapping.txt | 83 ++ orm/manual/en/yaml-mapping.txt | 66 ++ rest/manual/en.txt | 0 25 files changed, 5525 insertions(+) create mode 100644 dbal/manual/en.txt create mode 100644 dbal/manual/en/dbal.txt create mode 100644 migrations/manual/en.txt create mode 100644 orm/manual/en.txt create mode 100644 orm/manual/en/annotations-reference.txt create mode 100644 orm/manual/en/architecture.txt create mode 100644 orm/manual/en/association-mapping.txt create mode 100644 orm/manual/en/basic-mapping.txt create mode 100644 orm/manual/en/batch-processing.txt create mode 100644 orm/manual/en/best-practices.txt create mode 100644 orm/manual/en/caching.txt create mode 100644 orm/manual/en/configuration.txt create mode 100755 orm/manual/en/dql-doctrine-query-language.txt create mode 100644 orm/manual/en/events.txt create mode 100644 orm/manual/en/improving-performance.txt create mode 100644 orm/manual/en/inheritance-mapping.txt create mode 100644 orm/manual/en/introduction.txt create mode 100644 orm/manual/en/native-sql.txt create mode 100644 orm/manual/en/query-builder.txt create mode 100644 orm/manual/en/tools.txt create mode 100644 orm/manual/en/transactions-and-concurrency.txt create mode 100644 orm/manual/en/working-with-objects.txt create mode 100644 orm/manual/en/xml-mapping.txt create mode 100644 orm/manual/en/yaml-mapping.txt create mode 100644 rest/manual/en.txt diff --git a/dbal/manual/en.txt b/dbal/manual/en.txt new file mode 100644 index 000000000..8383139ae --- /dev/null +++ b/dbal/manual/en.txt @@ -0,0 +1 @@ ++ DBAL \ No newline at end of file diff --git a/dbal/manual/en/dbal.txt b/dbal/manual/en/dbal.txt new file mode 100644 index 000000000..452f45646 --- /dev/null +++ b/dbal/manual/en/dbal.txt @@ -0,0 +1,245 @@ +The Doctrine 2 database layer can be used independently of the object-relational mapping. It offers a leightweight abstraction layer around a PDO like API and allows optional access to lots of convenience functionality aswell as the ability to generate platform independent DQL and DDL statements. + +++ Configuration + +You can create a Doctrine Connection by using the `Doctrine\DBAL\DriverManager` class. + + [php] + $config = new \Doctrine\DBAL\Configuration(); + //.. + + $connectionParams = array( + 'dbname' => 'mydb', + 'user' => 'user', + 'password' => 'secret', + 'host' => 'locahlost', + 'driver' => 'pdo_mysql', + ); + $conn = DriverManager::getConnection($connectionParams); + +The `DriverManager` returns an instance of `Doctrine\DBAL\Connection` which is a wrapper around any configured database driver, for example the PDO Mysql driver in the previous example. + ++++ Connection Options + +The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array +or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the +DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The following option keys can be specified +to create your connection: + +++++ Driver Management Options: + +* driver - Allows to specify the default drivers shipped with Doctrine 2, 'pdo_mysql', 'pdo_sqlite', 'pdo_pgsql, and 'oci'. +* driverClass - If no 'driver' is specified this allows usage of a userland implementation of Doctrine\DBAL\Driver. +* pdo - If PDO is already instantiated for Mysql, SqLite or PgSQL this key can be used to pass this instance into Doctrine. +* wrapperClass - By default Doctrine\DBAL\Connection is wrapped around each driver, however this option allows to specify a userland sub-class. + +++++ Driver Configuration Options: + +Common Configuration Options across all database drivers: + +* platform - An instance of `Doctrine\DBAL\Platforms\AbstractPlatform`. This is only required for userland implementations, each driver shipped with Doctrine 2 has a default platform. +* user - Username required to connect to the database. +* password - Password required to connect to the database. +* driverOptions - Array of options passed to the driver instance on calling to Driver::connect. + +Driver Configuration Options are different for each Database Driver, here are some of the db specific ones: + +* host - Database host (Mysql, Pgsql, Oracle) +* port - Database port (Mysql, Pgsql, Oracle) +* dbname - Name of the database/schema to connect to. (Mysql, Pgsql, Oracle) +* unix_socket - Name of the socket used to connect to the database. (Mysql) +* charset - The charset used when connecting to the database. (Oracle) +* path - The filesystem path to the database (Sqlite) +* memory - True if the sqlite database should be in memory. (Sqlite) + ++++ DBAL Events + +Both `Doctrine\DBAL\DriverManager` and `Doctrine\DBAL\Connection` accept an instance of `Doctrine\Common\EventManager`. +The EventManager has a couple of events inside the DBAL layer that are triggered for the user to listen to: + +++++ PostConnect Event + +`Doctrine\DBAL\Events::postConnect` is triggered right after the connection to the database is established. It allows +to specify any relevant connection specific options and gives access to the `Doctrine\DBAL\Connection` instance +that is responsible for the connection management via an instance of `Doctrine\DBAL\Event\ConnectionEventArgs` +event arguments instance. + +Doctrine is already shipped with two implementations for the "PostConnect" event: + +* `Doctrine\DBAL\Event\Listeners\OracleSessionInit` allows to specify any number of Oracle Session related enviroment variables that are set right after the connection is established. +* `Doctrine\DBAL\Event\Listeners\MysqlSessionInit` allows to specify the Charset and Collation of the Client Connection if these options are not configured correctly on the MySQL server side. + +You can register events by subscribing them to the `EventManager` instance passed to the Connection factory: + + [php] + $evm = new EventManager(), + $evm->addEventSubscriber(new MysqlSessionInit('UTF-8')); + + $conn = DriverManager::getConnection($connectionParams, null, $evm); + +++ DBAL API + ++++ DBAL Architecture + +The DBAL is seperated into several different packages that perfectly seperate responsibilities of the different RDBMS layers. + +* **Drivers** abstract a PHP specific database API by enforcing two interfaces `\Doctrine\DBAL\Driver\Driver` and `\Doctrine\DBAL\Driver\Statement` which require exactly the same methods as PDO. +* **Platforms** abstract the generation of queries and which database features a platform supports. The `\Doctrine\DBAL\Platforms\AbstractPlatform` defines the common denominator of what a database platform has to publish to the userland, to be fully supportable by Doctrine. This includes the SchemaTool, Transaction Isolation and many other features. The Database platform for MySQL for example can be used by all 3 mysql extensions, PDO, Mysqli and ext/mysql. +* **Logging** holds the interface and some implementations for debugging of Doctrine SQL query execution during a request. +* **Schema** offers an API for each database platform to execute DDL statements against your platform or retrieve metadata about it. It also holds the Schema Abstraction Layer which is used by the different Schema Management facilities of Doctrine DBAL and ORM. +* **Types** offers an abstraction layer for the converting and generation of types between Databases and PHP. + ++++ Data Retrieval and Manipulation + + The following methods exist for executing queries against your configured database, three very generic methods and some advanced retrievial methods: + +* `prepare($sql)` - Prepare a given sql statement and return the `\Doctrine\DBAL\Driver\Statement` instance. +* `executeUpdate($sql, array $params)` - Executes a prepared statement with the given sql and parameters and returns the affected rows count. +* `execute($sql, array $params)` - Creates a prepared statement for the given sql and passes the parameters to the execute method, then returning the statement. +* `fetchAll($sql, array $params)` - Execute the query and fetch all results into an array. +* `fetchArray($sql, array $params)` - Numeric index retrieval of first result row of the given query. +* `fetchBoth($sql, array $params)` - Both numeric and assoc column name retrieval of the first result row. +* `fetchColumn($sql, array $params, $colnum)` - Retrieve only the given column of the first result row. +* `fetchRow($sql, array $params)` - Retrieve assoc row of the first result row. +* `select($sql, $limit, $offset)` - Modify the given query with a limit clause. + +There are also convenience methods for data manipulation queries: + +* `delete($tableName, array $identifier)` - Delete all rows of a table matching the given identifier, where keys are column names. +* `insert($tableName, array $data)` - Insert a row into the given table name using the key value pairs of data. +* `update($tableName, array $data, array $identifier)` - Update all rows for the matching key value identifiers with the given data. + +By default the Doctrine DBAL does no escaping. Escaping is a very tricky business to do automagically, therefore there is none by default. The ORM internally escapes all your values, because it has lots of metadata available about the current context. When you use the Doctrine DBAL as standalone, you have to take care of this yourself. The following methods help you with it: + +* `quote($input, $type=null)` - Quote a value +* `quoteIdentifier($identifier)`- Quote an identifier according to the platform details. + ++++ Transactions + +Doctrine handles transactions with a PDO like API, having methods for `beginTransaction()`, `commit()` and `rollBack()`. For consistency across different drivers Doctrine also handles the nesting of transactions internally. You can call `beginTransaction()` more than once, and only a matching amount of calls to `commit()` triggers the commit to the database. The Doctrine connectionalso has a method to set the transaction isolation level of the connection as supported by the underlying database. + + [php] + class Connection + { + /** + * Constant for transaction isolation level READ UNCOMMITTED. + */ + const TRANSACTION_READ_UNCOMMITTED = 1; + + /** + * Constant for transaction isolation level READ COMMITTED. + */ + const TRANSACTION_READ_COMMITTED = 2; + + /** + * Constant for transaction isolation level REPEATABLE READ. + */ + const TRANSACTION_REPEATABLE_READ = 3; + + /** + * Constant for transaction isolation level SERIALIZABLE. + */ + const TRANSACTION_SERIALIZABLE = 4; + } + +A transaction with Doctrine DBAL might then look like: + + [php] + $conn->setTransactionIsolationLevel(Connection::TRANSACTION_SERIALIZABLE); + + try{ + $conn->beginTransaction(); + // do stuff + $conn->commit(); + } catch(\Exception $e) { + $conn->rollback(); + } + +++ Schema Representation + +Doctrine has a very powerful abstraction of database schemas. It offers an object-oriented representation of a database schema with support for all the details of Tables, Sequences, Indexes and Foreign Keys. These Schema instances generate a representation that is equal for all the supported platforms. Internally this functionality is used by the ORM Schema Tool to offer you create, drop and update database schema methods from your Doctrine ORM Metadata model. Up to very specific functionality of your database system this allows you to generate SQL code that makes your Domain model work. + +You will be pleased to hear, that Schema representation is completly decoupled from the Doctrine ORM though, that is you can also use it in any other project to implement database migrations or for SQL schema generation for any metadata model that your application has. You can easily generate a Schema, as a simple example shows: + + [php] + $schema = new \Doctrine\DBAL\Schema\Schema(); + $myTable = $schema->createTable("my_table"); + $myTable->addColumn("id", "integer", array("unsigned" => true)); + $myTable->addColumn("username", "string", array("length" => 32)); + $myTable->setPrimaryKey(array("id")); + $myTable->addUniqueIndex(array("username")); + $schema->createSequence("my_table_seq"); + + $myForeign = $schema->createTable("my_foreign"); + $myForeign->addColumn("id", "integer"); + $myForeign->addColumn("user_id", "integer"); + $myForeign->addForeignKeyConstraint($myTable, array("user_id"), array("id"), array("onUpdate" => "CASCADE")); + + $queries = $schema->toSql($myPlatform); // get queries to create this schema. + $dropSchema = $schema->toDropSql($myPlatform); // get queries to safely delete this schema. + +Now if you want to compare this schema with another schema, you can use the `Comparator` class to get instances of `SchemaDiff`, `TableDiff` and `ColumnDiff`, aswell as information about other foreign key, sequence and index changes. + + [php] + $comparator = new \Doctrine\DBAL\Schema\Comparator(); + $schemaDiff = $comparator->compare($fromSchema, $toSchema); + + $queries = $schemaDiff->toSql($myPlatform); // queries to get from one to another schema. + $saveQueries = $schemaDiff->toSaveSql($myPlatform); + +The Save Diff mode is a specific mode that prevents the deletion of tables and sequences that might occour when making a diff of your schema. This is often necessary when your target schema is not complete but only describes a subset of your application. + +All methods that generate SQL queries for you make much effort to get the order of generation correct, so that no problems will ever occour with missing links of foreign keys. + +++ Platforms + +Platforms abstract query generation and specifics of the RDBMS featuresets. In most cases you don't need to interact with this package alot, but there might be certain cases when you are programming database independent where you want to access the platform to generate queries for you. + +The platform can be accessed from any `Doctrine\DBAL\Connection` instance by calling the `getDatabasePlatform()` method. + +You can use your own platform by specifying the 'platform' key with an instance of your own platform: + + [php] + $myPlatform = new MyPlatform(); + $options = array( + 'driver' => 'pdo_sqlite', + 'path' => 'database.sqlite', + 'platform' => $myPlatform + ); + +This way you can optimize your schema or generated SQL code with features that might not be portable for instance, +however are required for your special needs. + +++ Schema Manager + +A Schema Manager instance helps you with the abstraction of the generation of SQL assets such as Tables, Sequences, Foreign Keys and Indexes. You can use any of the Schema Asset classes `Table`, `Sequence`, `ForeignKeyConstraint` and `Index` for use with the methods of the style `dropAndCreate(AssetName)($asset)`, `drop(AssetName)($asset)` and `create(AssetName)($asset)`. + +You also have methods to retrieve instances of those types from the current database you are connected to. These methods are: + +* `listDatabases()` +* `listSequences()` +* `listTableColumns($tableName)` +* `listTableDetails($tableName)` +* `listTableForeignKeys($tableName)` +* `listTableIndexes($tableName)` +* `listTables()` +* `listUsers()` +* `listViews()` + +For a complete representation of the current database you can use the `createSchema()` method which returns an instance of Schema, which you can use in conjunction with the SchemaTool or Schema Comparator. + +++ Supporting other Databases + +To support a database which is not currently shipped with Doctrine you have to implement the following interfaces and abstract classes: + +* `\Doctrine\DBAL\Driver\Driver` +* `\Doctrine\DBAL\Driver\Statement` +* `\Doctrine\DBAL\Platforms\AbstractPlatform` +* `\Doctrine\DBAL\Schema\AbstractSchemaManager` + +For an already supported platform but unsupported driver you only need to implement the first two interfaces, since the SQL Generation and Schema Management is already supported by the respective platform and schema instances. You can also make use of several Abstract Unittests in the `\Doctrine\Tests\DBAL` package to check if your platform behaves like all the others which is necessary for SchemaTool support, namely: + +* `\Doctrine\Tests\DBAL\Platforms\AbstractPlatformTestCase` +* `\Doctrine\Tests\DBAL\Functional\Schema\AbstractSchemaManagerTestCase` + +We would be very happy if any support for new databases would be contributed back to Doctrine to make it an even better product. diff --git a/migrations/manual/en.txt b/migrations/manual/en.txt new file mode 100644 index 000000000..e69de29bb diff --git a/orm/manual/en.txt b/orm/manual/en.txt new file mode 100644 index 000000000..b17808194 --- /dev/null +++ b/orm/manual/en.txt @@ -0,0 +1,20 @@ ++ Introduction ++ Architecture ++ Configuration ++ Basic Mapping ++ Association Mapping ++ Inheritance Mapping ++ Working with objects ++ Transactions and Concurrency ++ Events ++ Batch processing ++ DQL (Doctrine Query Language) ++ Query Builder ++ Native SQL ++ XML Mapping ++ YAML Mapping ++ Annotations Reference ++ Caching ++ Improving Performance ++ Tools ++ Best Practices \ No newline at end of file diff --git a/orm/manual/en/annotations-reference.txt b/orm/manual/en/annotations-reference.txt new file mode 100644 index 000000000..cf5eece3b --- /dev/null +++ b/orm/manual/en/annotations-reference.txt @@ -0,0 +1,557 @@ +In this chapter a reference of every Doctrine 2 Annotation is given with short explanations on their context and usage. + +++ Index + +* [@Column](#ann_column) +* [@ChangeTrackingPolicy](#ann_changetrackingpolicy) +* [@DiscriminatorColumn](#ann_discriminatorcolumn) +* [@DiscriminatorMap](#ann_discriminatormap) +* [@Entity](#ann_entity) +* [@GeneratedValue](#ann_generatedvalue) +* [@HasLifecycleCallbacks](#ann_haslifecyclecallbacks) +* [@Index](#ann_indexes) +* [@Id](#ann_id) +* [@InheritanceType](#ann_inheritancetype) +* [@JoinColumn](#ann_joincolumn) +* [@JoinTable](#ann_jointable) +* [@ManyToOne](#ann_manytoone) +* [@ManyToMany](#ann_manytomany) +* [@MappedSuperclass](#ann_mappedsuperclass) +* [@OneToOne](#ann_onetoone) +* [@OneToMany](#ann_onetomany) +* [@OrderBy](#ann_orderby) +* [@PostLoad](#ann_postload) +* [@PostPersist](#ann_postpersist) +* [@PostRemove](#ann_postremove) +* [@PostUpdate](#ann_postupdate) +* [@PrePersist](#ann_prepersist) +* [@PreRemove](#ann_preremove) +* [@PreUpdate](#ann_preupdate) +* [@SequenceGenerator](#ann_sequencegenerator) +* [@Table](#ann_table) +* [@UniqueConstraint](#ann_uniqueconstraint) +* [@Version](#ann_version) + +++ Reference + + ++++ @Column + +Marks an annotated instance variable as "persistent". It has to be inside the instance variables PHP DocBlock comment. +Any value hold inside this variable will be saved to and loaded from the database as part of the lifecycle of the instance variables entity-class. + +Required attributes: + +* type - Name of the Doctrine Type which is converted between PHP and Database representation. + +Optional attributes: + +* name - By default the property name is used for the database column name also, however the 'name' attribute allows you to determine the column name. +* length - Used by the "string" type to determine its maximum length in the database. Doctrine does not validate the length of a string values for you. +* precision - The precision for a decimal (exact numeric) column (Applies only for decimal column) +* scale - The scale for a decimal (exact numeric) column (Applies only for decimal column) +* unique - Boolean value to determine if the value of the column should be unique accross all rows of the underlying entities table. +* nullable - Determines if NULL values allowed for this column. +* columnDefinition - DDL SQL snippet that starts after the column name and specificies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. However you should make careful use of this feature and the consequences. Additionally you should remember that the "type" attribute still handles the conversion between PHP and Database values. If you use this attribute on a column that is used for joins between tables you should also take a look at [@JoinColumn](#ann_joincolumn). + +Examples: + + /** + * @Column(type="string", length=32, unique=true, nullable=false) + */ + protected $username; + + /** + * @Column(type="string", columnDefinition="CHAR(2) NOT NULL") + */ + protected $country; + + /** + * @Column(type="decimal", precision=2, scale=1) + */ + protected $height; + + ++++ @ChangeTrackingPolicy + +The Change Tracking Policy annotation allows to specify how the Doctrine 2 UnitOfWork should detect changes +in properties of entities during flush. By default each entity is checked according to a deferred implict +strategy, which means upon flush UnitOfWork compares all the properties of an entity to a previously stored +snapshot. This works out of the box, however you might want to tweak the flush performance where using +another change tracking policy is an interesting option. + +The [details on all the available change tracking policies](/../configuration#change-tracking-policies) +can be found in the configuration section. + +Example: + + /** + * @Entity + * @ChangeTrackingPolicy("DEFERRED_IMPLICIT") + * @ChangeTrackingPolicy("DEFERRED_EXPLICIT") + * @ChangeTrackingPolicy("NOTIFY") + */ + class User {} + + ++++ @DiscrimnatorColumn + +This annotation is a required annotation for the topmost/super class of an inheritance hierachy. It specifies +the details of the column which saves the name of the class, which the entity is actually instantiated as. + +Required attributes: + +* name - The column name of the discriminator. This name is also used during Array hydration as key to specify the class-name. + +Optional attributes: + +* type - By default this is string. +* length - By default this is 255. + + ++++ @DiscriminatorMap + +The discrimnator map is a required annotation on the top-most/super class in an inheritance hierachy. It takes +an array as only argument which defines which class should be saved under which name in the database. Keys +are the database value and values are the classes, either as fully- or as unqualified class names depending +if the classes are in the namespace or not. + + /** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + + + ++++ @Entity + +Required annotation to mark a PHP class as Entity. Doctrine manages the persistence of all classes marked as entity. + +Optional attributes: + +* repositoryClass - Specifies the FQCN of a subclass of the Doctrine\ORM\EntityRepository. Use of repositories for entites is encouraged to keep specialized DQL and SQL operations separated from the Model/Domain Layer. + +Example: + + /** + * @Entity(repositoryClass="MyProject\UserRepository") + */ + class User + { + //... + } + + ++++ @GeneratedValue + +Specifies which strategy is used for identifier generation for an instance variable which is annotated by [@Id](#ann_id). +This annotation is optional and only has meaning when used in conjunction with @Id. + +If this annotation is not specified with @Id the NONE strategy is used as default. + +Required attributes: + +* strategy - Set the name of the identifier generation strategy. Valid values are AUTO, SEQUENCE, TABLE, IDENTITY and NONE. + +Example: + + /** + * @Id + * @Column(type="integer") + * @generatedValue(strategy="IDENTITY") + */ + protected $id = null; + + ++++ @HasLifecycleCallbacks + +Annotation which has to be set on the entity-class PHP DocBlock to notify Doctrine that this entity has entity life-cycle +callback annotations set on at least one of its methods. Using @PostLoad, @PrePersist, @PostPersist, @PreRemove, @PostRemove, +@PreUpdate or @PostUpdate without this marker annotation will make Doctrine ignore the callbacks. + +Example: + + /** + * @Entity + * @HasLifecycleCallbacks + */ + class User + { + /** + * @PostPersist + */ + public function sendOptinMail() {} + } + + ++++ @Index + +Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the +SchemaTool to generate a database index on the specified table columns. It only has meaning in the SchemaTool +schema generation context. + +Required attributes: + +* name - Name of the Index +* columns - Array of columns. + +Example: + + /** + * @Entity + * @Table(name="ecommerce_products",indexes={@index(name="search_idx", columns={"name", "email"})}) + */ + class ECommerceProduct + { + } + + ++++ @Id + +The annotated instance variable will be marked as entity identifier, the primary key in the database. +This annotation is a marker only and has no required or optional attributes. For entites that have multiple +identifier columns each column has to be marked with @Id. + +Example: + + /** + * @Id + * @Column(type="integer") + */ + protected $id = null; + + ++++ @InheritanceType + +In an inheritance hierachy you have to use this annotation on the topmost/super class to define which +strategy should be used for inheritance. Currently Single Table and Class Table Inheritance are supported. + +This annotation has always been used in conjunction with the [@DiscriminatorMap](#ann_discriminatormap) and +[@DiscriminatorColumn](#ann_discriminatorcolumn) annotations. + +Examples: + + /** + * @Entity + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + + /** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + + ++++ @JoinColumn + +This annotation is used in the context of relations in [@ManyToOne](#ann_manytoone), [@OneToOne](#ann_onetoone) fields +and in the Context of [@JoinTable](#ann_jointable) nested inside a @ManyToMany. This annotation is not required. +If its not specified the attributes *name* and *referencedColumnName* are infered from the table and primary key names. + +Required attributes: + +* name - Column name that holds the foreign key identifier for this relation. In the context of @JoinTable it specifies the column name in the join table. +* referencedColumnName - Name of the primary key identifier that is used for joining of this relation. + +Optional attributes: + +* unique - Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false. +* nullable - Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true. +* onDelete - Cascade Action (Database-level) +* onUpdate - Cascade Action (Database-level) +* columnDefinition - DDL SQL snippet that starts after the column name and specificies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on @JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a "columnDefinition" attribute on [@Column](#ann_column) also sets the related @JoinColumn's columnDefinition. This is necessary to make foreign keys work. + +Example: + + /** + * @OneToOne(targetEntity="Customer") + * @JoinColumn(name="customer_id", referencedColumnName="id") + */ + private $customer; + + ++++ @JoinColumns + +An array of @JoinColumn annotations for a [@ManyToOne](#ann_manytoone) or [@OneToOne](#ann_onetoone) relation +with an entity that has multiple identifiers. + + ++++ @JoinTable + +Using [@OneToMany](#ann_onetomany) or [@ManyToMany](#ann_manytomany) on the owning side of the relation requires to specifiy +the @JoinTable annotation which describes the details of the database join table. If you do not specify @JoinTable on +these relations reasonable mapping defaults apply using the affected table and the column names. + +Required attributes: + +* name - Database name of the join-table +* joinColumns - An array of @JoinColumn annotations describing the join-relation between the owning entites table and the join table. +* inverseJoinColumns - An array of @JoinColumn annotations describing the join-relation between the inverse entities table and the join table. + +Optional attributes: + +* schema - Database schema name of this table. + +Example: + + /** + * @ManyToMany(targetEntity="Phonenumber") + * @JoinTable(name="users_phonenumbers", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} + * ) + */ + public $phonenumbers; + + ++++ @ManyToOne + +Defines that the annotated instance variable holds a reference that describes a many-to-one relationship between two entities. + +Required attributes: + +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. + +Optional attributes: + +* cascade - Cascade Option +* fetch - One of LAZY or EAGER + +Example: + + /** + * @ManyToOne(targetEntity="Cart", cascade="ALL", fetch="EAGER") + */ + private $cart; + + ++++ @ManyToMany + +Defines an instance variable holds a many-to-many relationship between two entities. [@JoinTable](#ann_jointable) +is an additional, optional annotation that has reasonable default configuration values using the table +and names of the two related entities. + +Required attributes: + +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. + +Optional attributes: + +* mappedBy - This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship. +* cascade - Cascade Option +* fetch - One of LAZY or EAGER + +Example: + + /** + * Owning Side + * + * @ManyToMany(targetEntity="Group") + * @JoinTable(name="user_groups", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} + * ) + */ + private $groups; + + /** + * Inverse Side + * + * @ManyToMany(targetEntity="User", mappedBy="groups") + */ + private $features; + + ++++ @MappedSuperclass + +An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information +for its subclasses, but which is not itself an entity. This annotation is specified on the Class docblock +and has no additional attributes. + +The @MappedSuperclass annotation cannot be used in conjunction with @Entity. See the Inheritance Mapping +section for [more details on the restrictions of mapped superclasses](/../inheritance-mapping#mapped-superclasses). + + ++++ @OnetoOne + +The @OneToOne annotation works almost exactly as the [@ManyToOne](#ann_manytoone) with one additional option +that can be specified. The configuration defaults for [@JoinColumn](#ann_joincolumn) using the target entity table and primary key column names +apply here too. + +Required attributes: + +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. + +Optional attributes: + +* cascade - Cascade Option +* fetch - One of LAZY or EAGER +* orphanRemoval - Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any +owning instance, should be removed by Doctrine. Defaults to false. + + ++++ @OneToMany + + ++++ @OrderBy + +Optional annotation that can be specified with a [@ManyToMany](#ann_manytomany) or [@OneToMany](#ann_onetomany) +annotation to specify by which criteria the collection should be retrieved from the database by using an ORDER BY +clause. + +This annotation requires a single non-attributed value with an DQL snippet: + +Example: + + /** + * @ManyToMany(targetEntity="Group") + * @OrderBy({"name" = "ASC"}) + */ + private $groups; + +The DQL Snippet in OrderBy is only allowed to consist of unqualified, +unquoted field names and of an optional ASC/DESC positional statement. +Multiple Fields are separated by a comma (,). The referenced field +names have to exist on the `targetEntity` class of the `@ManyToMany` or +`@OneToMany` annotation. + + ++++ @PostLoad + +Marks a method on the entity to be called as a @PostLoad event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PostPersist + +Marks a method on the entity to be called as a @PostPersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PostRemove + +Marks a method on the entity to be called as a @PostRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PostUpdate + +Marks a method on the entity to be called as a @PostUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PrePersist + +Marks a method on the entity to be called as a @PrePersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PreRemove + +Marks a method on the entity to be called as a @PreRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PreUpdate + +Marks a method on the entity to be called as a @PreUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @SequenceGenerator + +For the use with @generatedValue(strategy="SEQUENCE") this annotation allows to specifiy details about the sequence, +such as the increment size and initial values of the sequence. + +Required attributes: + +* sequenceName - Name of the sequence + +Optional attributes: + +* allocationSize - Increment the sequence by the allocation size when its fetched. A value larger than 1 allows to optimize for scenarios where you create more than one new entity per request. Defaults to 10 +* initialValue - Where does the sequence start, defaults to 1. + +Example: + + /** + * @Id + * @GeneratedValue(strategy="SEQUENCE") + * @Column(type="integer") + * @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100) + */ + protected $id = null; + + ++++ @Table + +Annotation describes the table an entity is persisted in. It is placed on the entity-class PHP DocBlock and is optional. +If it is not specified the table name will default to the entities unqualified classname. + +Required attributes: + +* name - Name of the table + +Optional attributes: + +* schema - Database schema name of this table. +* indexes - Array of @Index annotations +* uniqueConstraints - Array of @UniqueConstraint annotations. + +Example: + + /** + * @Entity + * @Table(name="user", + * uniqueConstraints={@UniqueConstraint(name="user_unique",columns={"username"})}, + * indexes={@Index(name="user_idx", columns={"email"})} + * ) + */ + class User { } + + ++++ @UniqueConstraint + +Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the +SchemaTool to generate a database unique constraint on the specified table columns. It only has meaning in the SchemaTool +schema generation context. + +Required attributes: + +* name - Name of the Index +* columns - Array of columns. + +Example: + + /** + * @Entity + * @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "email"})}) + */ + class ECommerceProduct + { + } + + ++++ @Version + +Marker annotation that defines a specified column as version attribute used in an optimistic locking scenario. +It only works on [@Column](#ann_column) annotations that have the type integer or datetime. + +Example: + + /** + * @column(type="integer") + * @version + */ + protected $version; \ No newline at end of file diff --git a/orm/manual/en/architecture.txt b/orm/manual/en/architecture.txt new file mode 100644 index 000000000..45f10d160 --- /dev/null +++ b/orm/manual/en/architecture.txt @@ -0,0 +1,81 @@ +This chapter gives an overview of the overall architecture, terminology and constraints of +Doctrine 2. It is recommended to read this chapter carefully. + +++ Entities + +An entity is a lightweight persistent domain object. An entity can be any regular +php class that obeys to the following restrictions: + +* An entity class must not be final or contain final methods. +* An entity class must not implement `__clone` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). +* An entity class must not implement `__wakeup` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). + Also consider implementing [Serializable](http://de3.php.net/manual/en/class.serializable.php]) instead. +* Any two entity classes in a class hierarchy that inherit directly or indirectly from one another must not have a mapped property with the same name. + That is, if B inherits from A then B must not have a mapped field with the same name as an already mapped field that is inherited from A. + +Entities support inheritance, polymorphic associations, and polymorphic queries. +Both abstract and concrete classes can be entities. Entities may extend non-entity +classes as well as entity classes, and non-entity classes may extend entity classes. + +> **TIP** +> The constructor of an entity is only ever invoked when *you* construct a new instance +> with the *new* keyword. Doctrine never calls entity constructors, thus you are free to use +> them as you wish and even have it require arguments of any type. + ++++ Entity states + +An entity instance can be characterized as being NEW, MANAGED, DETACHED or REMOVED. + +* A NEW entity instance has no persistent identity, and is not yet associated with an EntityManager and a UnitOfWork (i.e. those just created with the "new" operator). +* A MANAGED entity instance is an instance with a persistent identity that is associated with an EntityManager and whose persistence is thus managed. +* A DETACHED entity instance is an instance with a persistent identity that is not (or no longer) associated with an EntityManager and a UnitOfWork. +* A REMOVED entity instance is an instance with a persistent identity, associated with an EntityManager, that will be removed from the database upon transaction commit. + ++++ Persistent fields + +The persistent state of an entity is represented by instance variables. An +instance variable must be directly accessed only from within the methods of the +entity by the entity instance itself. Instance variables must not be accessed by +clients of the entity. The state of the entity is available to clients only through +the entity’s methods, i.e. accessor methods (getter/setter methods) or other +business methods. + +Collection-valued persistent fields and properties must be defined in terms of +the `Doctrine\Common\Collections\Collection` interface. The collection +implementation type may be used by the application to initialize fields or +properties before the entity is made persistent. Once the entity becomes +managed (or detached), subsequent access must be through the interface type. + ++++ Serializing entities + +Serializing entities can be problematic and is not really recommended, at least not as long as an +entity instance still holds references to proxy objects or is still managed by an EntityManager. +If you intend to serialize (and unserialize) entity instances that still hold references to proxy objects +you may run into problems with private properties because of technical limitations. +Proxy objects implement `__sleep` and it is not possible for `__sleep` to return names of +private properties in parent classes. On ther other hand it is not a solution for proxy objects +to implement `Serializable` because Serializable does not work well with any potential cyclic +object references (at least we did not find a way yet, if you did, please contact us). + +++ The EntityManager + +The `EntityManager` class is a central access point to the ORM functionality +provided by Doctrine 2. The `EntityManager` API is used to manage the persistence +of your objects and to query for persistent objects. + ++++ Transactional write-behind + +An `EntityManager` and the underlying `UnitOfWork` employ a strategy called +"transactional write-behind" that delays the execution of SQL statements in +order to execute them in the most efficient way and to execute them at the end +of a transaction so that all write locks are quickly released. You should see +Doctrine as a tool to synchronize your in-memory objects with the database in +well defined units of work. Work with your objects and modify them as usual and +when you're done call `EntityManager#flush()` to make your changes persistent. + ++++ The Unit of Work + +Internally an `EntityManager` uses a `UnitOfWork`, which is a typical +implementation of the [Unit of Work pattern](http://martinfowler.com/eaaCatalog/unitOfWork.html), to keep track of all the things that need to be done +the next time `flush` is invoked. You usually do not directly interact with +a `UnitOfWork` but with the `EntityManager` instead. \ No newline at end of file diff --git a/orm/manual/en/association-mapping.txt b/orm/manual/en/association-mapping.txt new file mode 100644 index 000000000..5190f6a14 --- /dev/null +++ b/orm/manual/en/association-mapping.txt @@ -0,0 +1,429 @@ +This chapter explains how associations between entities are mapped with Doctrine. We start out with an explanation of the concept of owning and inverse sides which is important to understand when working with bidirectional associations. Please read these explanations carefully. + +++ Owning Side and Inverse Side + +When mapping bidirectional associations it is important to understand the concept of the owning and inverse sides. The following general rules apply: + +* Relationships may be bidirectional or unidirectional. +* A bidirectional relationship has both an owning side and an inverse side. +* A unidirectional relationship only has an owning side. +* The owning side of a relationship determines the updates to the relationship in the database. + + +The following rules apply to *bidirectional* associations: + +* The inverse side of a bidirectional relationship must refer to its owning side by use of the mappedBy attribute of the OneToOne, OneToMany, or ManyToMany mapping declaration. The mappedBy attribute designates the field in the entity that is the owner of the relationship. +* The owning side of a bidirectional relationship must refer to its inverse side by use of the inversedBy attribute of the OneToOne, ManyToOne, or ManyToMany mapping declaration. The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. +* The many side of OneToMany/ManyToOne bidirectional relationships *must* be the owning side, hence the mappedBy element can not be specified on the ManyToOne side. +* For OneToOne bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key (@JoinColumn(s)). +* For ManyToMany bidirectional relationships either side may be the owning side (the side that defines the @JoinTable and/or does not make use of the mappedBy attribute, thus using a default join table). + +Especially important is the following: + +**The owning side of a relationship determines the updates to the relationship in the database**. + +To fully understand this, remember how bidirectional associations are maintained +in the object world. There are 2 references on each side of the association +and these 2 references both represent the same association but can change +independently of one another. Of course, in a correct application the semantics +of the bidirectional association are properly maintained by the application +developer (that's his responsiblity). Doctrine needs to know which of +these two in-memory references is the one that should be persisted and which +not. This is what the owning/inverse concept is mainly used for. + +**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)** + +The owning side of a bidirectional association is the side Doctrine "looks at" when determining +the state of the association, and consequently whether there is anything to do to update the association +in the database. + +> **NOTE** +> "Owning side" and "inverse side" are technical concepts of the ORM technology, not concepts +> of your domain model. What you consider as the owning side in your domain model can be different +> from what the owning side is for Doctrine. These are unrelated. + +++ Collections + +In all the examples of many-valued associations in this manual we will make use of a `Collection` interface and a corresponding default implementation `ArrayCollection` that are defined in the `Doctrine\Common\Collections` namespace. Why do we need that? Doesn't that couple my domain model to Doctrine? Unfortunately, PHP arrays, while being great for many things, do not make up for good collections of business objects, especially not in the context of an ORM. The reason is that plain PHP arrays can not be transparently extended / instrumented in PHP code, which is necessary for a lot of advanced ORM features. The classes / interfaces that come closest to an OO collection are ArrayAccess and ArrayObject but until instances of these types can be used in all places where a plain array can be used (something that may happen in PHP6) their useability is fairly limited. You "can" type-hint on `ArrayAccess` instead of `Collection`, since the Collection interface extends `ArrayAccess`, but this will severely limit you in the way you can work with the collection, because the `ArrayAccess` API is (intentionally) very primitive and more importantly because you can not pass this collection to all the useful PHP array functions, which makes it very hard to work with. + +> **CAUTION** +> The Collection interface and ArrayCollection class, like everything else in the +> Doctrine\Common namespace, are neither part of the ORM, nor the DBAL, it is a plain PHP +> class that has no outside dependencies apart from dependencies on PHP itself (and the +> SPL). Therefore using this class in your domain classes and elsewhere does not introduce +> a coupling to the persistence layer. The Collection class, like everything else in the +> Common namespace, is not part of the persistence layer. You could even copy that class +> over to your project if you want to remove Doctrine from your project and all your +> domain classes will work the same as before. + +++ Mapping Defaults + +The @JoinColumn and @JoinTable definitions are usually optional and have sensible default values. The defaults for a join column in a one-to-one/many-to-one association is as follows: + + name: "_id" + referencedColumnName: "id" + +As an example, consider this mapping: + + [php] + /** @OneToOne(targetEntity="Shipping") */ + private $shipping; + +This is essentially the same as the following, more verbose, mapping: + + [php] + /** + * @OneToOne(targetEntity="Shipping") + * @JoinColumn(name="shipping_id", referencedColumnName="id") + */ + private $shipping; + + +The @JoinTable definition used for many-to-many mappings has similar defaults. As an example, consider this mapping: + + [php] + class User { + //... + /** @ManyToMany(targetEntity="Group") */ + private $groups; + //... + } + +This is essentially the same as the following, more verbose, mapping: + + [php] + class User { + //... + /** + * @ManyToMany(targetEntity="Group") + * @JoinTable(name="User_Group", + * joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")} + * ) + */ + private $groups; + //... + } + +In that case, the name of the join table defaults to a combination of the simple, unqualified class names of the participating classes, separated by an underscore character. The names of the join columns default to the simple, unqualified class name of the targeted class followed by "_id". The referencedColumnName always defaults to "id", just as in one-to-one or many-to-one mappings. + +If you accept these defaults, you can reduce the mapping code to a minimum. + +++ One-To-One, Unidirectional + +A unidirectional one-to-one association is very common. Here is an example of a `Product` that has one `Shipping` object associated to it. The `Shipping` side does not reference back to the `Product` so it is unidirectional. + + [php] + /** @Entity */ + class Product + { + // ... + + /** + * @OneToOne(targetEntity="Shipping") + * @JoinColumn(name="shipping_id", referencedColumnName="id") + */ + private $shipping; + + // ... + } + + /** @Entity */ + class Shipping + { + // ... + } + +Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. + +++ One-To-One, Bidirectional + +Here is a one-to-one relationship between a `Customer` and a `Cart`. The `Cart` +has a reference back to the `Customer` so it is bidirectional. + + [php] + /** @Entity */ + class Customer + { + // ... + + /** + * @OneToOne(targetEntity="Cart", mappedBy="customer") + */ + private $cart; + + // ... + } + + /** @Entity */ + class Cart + { + // ... + + /** + * @OneToOne(targetEntity="Customer", inversedBy="cart") + * @JoinColumn(name="customer_id", referencedColumnName="id") + */ + private $customer; + + // ... + } + +Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. + +++ One-To-One, Self-referencing + +You can easily have self referencing one-to-one relationships like below. + + [php] + /** @Entity */ + class Customer + { + // ... + + /** + * @OneToOne(targetEntity="Customer") + * @JoinColumn(name="mentor_id", referencedColumnName="id") + */ + private $mentor; + + // ... + } + +Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. + +++ One-To-Many, Unidirectional with Join Table + +A unidirectional one-to-many association can be mapped through a join table. From Doctrine's point of view, it is simply mapped as a unidirectional many-to-many whereby a unique constraint on one of the join columns enforces the one-to-many cardinality. +The following example sets up such a unidirectional one-to-many association: + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToMany(targetEntity="Phonenumber") + * @JoinTable(name="users_phonenumbers", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} + * ) + */ + private $phonenumbers; + + // ... + } + + /** @Entity */ + class Phonenumber + { + // ... + } + +> **NOTE** +> One-To-Many uni-directional relations with join-table only work using the @ManyToMany annotation and a unique-constraint. + +++ One-To-Many, Bidirectional + +Bidirectional one-to-many associations are very common. The following code shows an example with a Product and a Feature class: + + [php] + /** @Entity */ + class Product + { + // ... + /** + * @OneToMany(targetEntity="Feature", mappedBy="product") + */ + private $features; + // ... + } + + /** @Entity */ + class Feature + { + // ... + /** + * @ManyToOne(targetEntity="Product", inversedBy="features") + * @JoinColumn(name="product_id", referencedColumnName="id") + */ + private $product; + // ... + } + +Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. + +++ One-To-Many, Self-referencing + +You can also setup a one-to-many association that is self-referencing. In this example we +setup a hierarchy of `Category` objects by creating a self referencing relationship. +This effectively models a hierarchy of categories and from the database perspective is known as an adjacency list approach. + + [php] + /** @Entity */ + class Category + { + // ... + /** + * @OneToMany(targetEntity="Category", mappedBy="parent") + */ + private $children; + + /** + * @ManyToOne(targetEntity="Category", inversedBy="children") + * @JoinColumn(name="parent_id", referencedColumnName="id") + */ + private $parent; + // ... + } + +Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. + +++ Many-To-Many, Unidirectional + +Real many-to-many associations are less common. The following example shows a unidirectional association between User and Group entities: + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToMany(targetEntity="Group") + * @JoinTable(name="users_groups", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} + * ) + */ + private $groups; + + // ... + } + + /** @Entity */ + class Group + { + // ... + } + +> **NOTE** +> Why are many-to-many associations less common? Because frequently you want to associate +> additional attributes with an association, in which case you introduce an association +> class. Consequently, the direct many-to-many association disappears and is replaced +> by one-to-many/many-to-one associations between the 3 participating classes. + +++ Many-To-Many, Bidirectional + +Here is a similar many-to-many relationship as above except this one is bidirectional. + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToMany(targetEntity="Group", inversedBy="users") + * @JoinTable(name="users_groups", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} + * ) + */ + private $groups; + + // ... + } + + /** @Entity */ + class Group + { + // ... + /** + * @ManyToMany(targetEntity="User", mappedBy="groups") + */ + private $users; + // ... + } + +++ Many-To-Many, Self-referencing + +You can even have a self-referencing many-to-many association. A common scenario is where a `User` has friends and the target entity of that relationship is a `User` so it is self referencing. In this example it is bidirectional so `User` has a field named `$friendsWithMe` and `$myFriends`. + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToMany(targetEntity="User", mappedBy="myFriends") + */ + private $friendsWithMe; + + /** + * @ManyToMany(targetEntity="User", inversedBy="friendsWithMe") + * @JoinTable(name="friends", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")} + * ) + */ + private $myFriends; + + // ... + } + +++ Ordering To-Many Collections + +In many use-cases you will want to sort collections when they are retrieved from the database. +In userland you do this as long as you haven't initially saved an entity with its associations +into the database. To retrieve a sorted collection from the database you can use the +`@OrderBy` annotation with an collection that specifies an DQL snippet that is appended +to all queries with this collection. + +Additional to any `@OneToMany` or `@ManyToMany` annotation you can specify the `@OrderBy` +in the following way: + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToMany(targetEntity="Group") + * @OrderBy({"name" = "ASC"}) + */ + private $groups; + } + +The DQL Snippet in OrderBy is only allowed to consist of unqualified, +unquoted field names and of an optional ASC/DESC positional statement. +Multiple Fields are separated by a comma (,). The referenced field +names have to exist on the `targetEntity` class of the `@ManyToMany` or +`@OneToMany` annotation. + +The semantics of this feature can be described as follows. + +* `@OrderBy` acts as an implicit ORDER BY clause for the given fields, that is appended +to all the explicitly given ORDER BY items. +* All collections of the ordered type are always retrieved in an ordered fashion. +* To keep the database impact low, these implicit ORDER BY items are only added +to an DQL Query if the collection is fetch joined in the DQL query. + +Given our previously defined Example: + + [sql] + -- Would not add ORDER BY, since g is not fetch joined + SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10 + + -- However + SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 + -- would internally be rewritten to + SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC + + -- You can't reverse the order, an explicit: + SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC + -- is internally be rewritten to + SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC diff --git a/orm/manual/en/basic-mapping.txt b/orm/manual/en/basic-mapping.txt new file mode 100644 index 000000000..17910de81 --- /dev/null +++ b/orm/manual/en/basic-mapping.txt @@ -0,0 +1,279 @@ +This chapter explains the basic mapping of objects and properties. Mapping of associations will be covered in the next chapter "Association Mapping". + +++ Mapping Drivers + +Doctrine provides several different ways for specifying object-relational mapping metadata: + +* Docblock Annotations +* XML +* YAML + +This manual usually uses docblock annotations in all the examples that are spread throughout all chapters. There are dedicated chapters for XML and YAML mapping, respectively. + +> **NOTE** +> If you're wondering which mapping driver gives the best performance, the answer is: +> None. Once the metadata of a class has been read from the source (annotations, xml or +> yaml) it is stored in an instance of the `Doctrine\ORM\Mapping\ClassMetadata` class +> and these instances are stored in the metadata cache. Therefore at the end of the day +> all drivers perform equally well. If you're not using a metadata cache (not +> recommended!) then the XML driver might have a slight edge in performance due to the +> powerful native XML support in PHP. + +++ Introduction to Docblock Annotations + +You've probably used docblock annotations in some form already, most likely to provide documentation metadata for a tool like `PHPDocumentor` (@author, @link, ...). Docblock annotations are a tool to embed metadata inside the documentation section which can then be processed by some tool. Doctrine 2 generalizes the concept of docblock annotations so that they can be used for any kind of metadata and so that it is easy to define new docblock annotations. In order to allow more involved annotation values and to reduce the chances of clashes with other docblock annotations, the Doctrine 2 docblock annotations feature an alternative syntax that is heavily inspired by the Annotation syntax introduced in Java 5. + +The implementation of these enhanced docblock annotations is located in the `Doctrine\Common\Annotations` namespace and therefore part of the Common package. Doctrine 2 docblock annotations support namespaces and nested annotations among other things. The Doctrine 2 ORM defines its own set of docblock annotations for supplying object-relational mapping metadata. + +> **NOTE** +> If you're not comfortable with the concept of docblock annotations, don't worry, as +> mentioned earlier Doctrine 2 provides XML and YAML alternatives and you could easily +> implement your own favourite mechanism for defining ORM metadata. + +++ Persistent classes + +In order to mark a class for object-relational persistence it needs to be designated as an entity. This can be done through the `@Entity` marker annotation. + + [php] + /** @Entity */ + class MyPersistentClass + { + //... + } + +By default, the entity will be persisted to a table with the same name as the class name. In order to change that, you can use the `@Table` annotation as follows: + + [php] + /** + * @Entity + * @Table(name="my_persistent_class") + */ + class MyPersistentClass + { + //... + } + +Now instances of MyPersistentClass will be persisted into a table named `my_persistent_class`. + +++ Doctrine Mapping Types + +A Doctrine Mapping Type defines the mapping between a PHP type and an SQL type. All Doctrine Mapping Types that ship with Doctrine are fully portable between different RDBMS. You can even write your own custom mapping types that might or might not be portable, which is explained later in this chapter. + +For example, the Doctrine Mapping Type `string` defines the mapping from a PHP string to an SQL VARCHAR (or VARCHAR2 etc. depending on the RDBMS brand). Here is a quick overview of the built-in mapping types: + +* `string`: Type that maps an SQL VARCHAR to a PHP string. +* `integer`: Type that maps an SQL INT to a PHP integer. +* `smallint`: Type that maps a database SMALLINT to a PHP integer. +* `bigint`: Type that maps a database BIGINT to a PHP string. +* `boolean`: Type that maps an SQL boolean to a PHP boolean. +* `decimal`: Type that maps an SQL DECIMAL to a PHP double. +* `date`: Type that maps an SQL DATETIME to a PHP DateTime object. +* `time`: Type that maps an SQL TIME to a PHP DateTime object. +* `datetime`: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object. +* `text`: Type that maps an SQL CLOB to a PHP string. + +> **NOTE** +> Doctrine Mapping Types are NOT SQL types and NOT PHP types! They are mapping types +> between 2 types. + +++ Property Mapping + +After a class has been marked as an entity it can specify mappings for its instance fields. Here we will only look at simple fields that hold scalar values like strings, numbers, etc. Associations to other objects are covered in the chapter "Association Mapping". + +To mark a property for relational persistence the `@Column` docblock annotation is used. This annotation usually requires at least 1 attribute to be set, the `type`. The `type` attribute specifies the Doctrine Mapping Type to use for the field. If the type is not specified, 'string' is used as the default mapping type since it is the most flexible. + +Example: + + [php] + /** @Entity */ + class MyPersistentClass + { + /** @Column(type="integer") */ + private $id; + /** @Column(length=50) */ + private $name; // type defaults to string + //... + } + +In that example we mapped the field `id` to the column `id` using the mapping type `integer` and the field `name` is mapped to the column `name` with the default mapping type `string`. As you can see, by default the column names are assumed to be the same as the field names. To specify a different name for the column, you can use the `name` attribute of the Column annotation as follows: + + [php] + /** @Column(name="db_name") */ + private $name; + +The Column annotation has some more attributes. Here is a complete list: + +* `type`: (optional, defaults to 'string') The mapping type to use for the column. +* `name`: (optional, defaults to field name) The name of the column in the database. +* `length`: (optional, default 255) The length of the column in the database. (Applies only if a string-valued column is used). +* `unique`: (optional, default FALSE) Whether the column is a unique key. +* `nullable`: (optional, default FALSE) Whether the database column is nullable. +* `precision`: (optional, default 0) The precision for a decimal (exact numeric) column. (Applies only if a decimal column is used.) +* `scale`: (optional, default 0) The scale for a decimal (exact numeric) column. (Applies only if a decimal column is used.) + +++ Custom Mapping Types + +Doctrine allows you to create new mapping types. This can come in handy when you're missing a specific mapping type +or when you want to replace the existing implementation of a mapping type. + +In order to create a new mapping type you need to subclass `Doctrine\DBAL\Types\Type` and implement/override +the methods as you wish. Here is an example skeleton of such a custom type class: + + [php] + namespace My\Project\Types; + + use Doctrine\DBAL\Types\Type; + use Doctrine\DBAL\Platforms\AbstractPlatform; + + /** + * My custom datatype. + */ + class MyType extends Type + { + public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + // return the SQL used to create your column type. To create a portable column type, use the $platform. + } + + public function convertToPHPValue($value, AbstractPlatform $platform) + { + // This is executed when the value is read from the database. Make your conversions here, optionally using the $platform. + } + + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + // This is executed when the value is written to the database. Make your conversions here, optionally using the $platform. + } + } + +Restrictions to keep in mind: + +* If the value of the field is *NULL* the method `convertToDatabaseValue()` is not called. +* The `UnitOfWork` never passes values to the database convert method that did not change in the request. + +When you have implemented the type you still need to let Doctrine know about it. This can be achieved +through the `Doctrine\DBAL\Configuration#setCustomTypes(array $types)` method. + +> **NOTE** +> `Doctrine\ORM\Configuration` is a subclass of `Doctrine\DBAL\Configuration`, so the +> methods are available on your ORM Configuration instance as well. + +Here is an example: + + [php] + // in bootstrapping code + + // ... + + use Doctrine\DBAL\Types\Type; + + // ... + + // Register my type + Type::addType('mytype', 'My\Project\Types\MyType'); + +As can be seen above, when registering the custom types in the configuration you specify a unique name +for the mapping type and map that to the corresponding fully qualified class name. Now you can use your new type in your mapping like this: + + [php] + class MyPersistentClass + { + /** @Column(type="mytype") */ + private $field; + } + +++ Identifiers / Primary Keys + +Every entity class needs an identifier/primary key. You designate the field that serves as the identifier with the `@Id` marker annotation. Here is an example: + + [php] + class MyPersistentClass + { + /** @Id @Column(type="integer") */ + private $id; + //... + } + +Without doing anything else, the identifier is assumed to be manually assigned. That means your code would need to properly set the identifier property before passing a new entity to `EntityManager#persist($entity)`. + +A common alternative strategy is to use a generated value as the identifier. To do this, you use the `@GeneratedValue` annotation like this: + + [php] + class MyPersistentClass + { + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + private $id; + } + +This tells Doctrine to automatically generate a value for the identifier. How this value is generated is specified by the `strategy` attribute, which is optional and defaults to 'AUTO'. A value of `AUTO` tells Doctrine to use the generation strategy that is preferred by the currently used database platform. For MySql, for example, Doctrine would use the `IDENTITY` strategy which means a typical AUTO_INCREMENT column. For PostgreSql it would choose to use the `SEQUENCE` strategy which would result in using a database sequence. + ++++ Id Generation Strategies + +The previous example showed how to use the default Id generation strategy without knowing the underlying database with the AUTO-detection strategy. +It is also possible to specifiy the Id Generation strategy more explicitly, which allows to make use of some additional features. + +Here is the list of possible generation strategies: + +* `AUTO` (default): Tells Doctrine to pick the strategy that is preferred by the used database platform. This strategy provides full portability. +* `NONE`: Tells Doctrine that you generated the entities primary key value in userland before `EntityManager#persist()` is called. +* `SEQUENCE`: Tells Doctrine to use a database sequence for ID generation. This strategy does currently not provide full portability. Sequences are supported by Oracle and PostgreSql. +* `IDENTITY`: Tells Doctrine to use special identity columns in the database that usually generate a value on insertion of a row (i.e. MySql AUTO_INCREMENT). This strategy does currently not provide full portability. IDENTITY is supported by MySql, Sqlite and MsSql. +* `TABLE`: Tells Doctrine to use a separate table for ID generation. This strategy provides full portability. ***This strategy is not yet implemented!*** + +++++ Sequence Generator + +The Sequence Generator can be used in conjunction with Oracle or Postgres Platforms and allows some additional configuration options besides +specifiying the sequence's name: + + [php] + class User { + /** + * @Id + * @GeneratedValue(strategy="SEQUENCE") + * @SequenceGenerator(name="tablename_seq", initialValue=1, allocationSize=100) + */ + protected $id = null; + } + +The initial value specifies at which value the sequence should start. + +Allocation Size is a powerful feature to optimize INSERT performance of Doctrine. The allocation size specifies +by how much values the sequence is incremented whenever the next value is retrieved. If this is larger than one +Doctrine 2 can generate Id values for the allocationSizes amount of entities. In the above example with +`allocationSize=100` Doctrine 2 would only need to access the sequence once to generate the identifiers for +100 new entities. + +> **CAUTION** + +> Allocation Size is detected by SchemaTool and transformed into an "INCREMENT BY " clause +> in the CREATE SEQUENCE statement. For a database schema created by you (and not SchemaTool) you have to +> make sure that the allocationSize configuration option is never larger than the actual sequences INCREMENT BY value. + ++++ Composite Keys + +Doctrine 2 allows to use composite primary keys. There are however some restrictions oposed to using a single identifier. +The use of the `@GeneratedValue` annotation is only supported for simple (not composite) primary keys, which means +you can only use composite keys if you generate the primary key values yourself before calling `EntityManager#persist()` +on the entity. + +To designate a composite primary key / identifier, simply put the @Id marker annotation on all fields that make up the primary key. + +++ Quoting Reserved Words + +It may sometimes be necessary to quote a column or table name because it conflicts with a reserved word of the particular RDBMS in use. This is often referred to as "Identifier Quoting". To let Doctrine know that you would like a table or column name to be quoted in all SQL statements, enclose the table or column name in backticks. Here is an example: + + [php] + /** @Column(name="`number`", type="integer") */ + private $number; + +Doctrine will then quote this column name in all SQL statements according to the used database platform. + +> **CAUTION** +> Identifier Quoting is not supported for join column names or discriminator column names. + +> **CAUTION** +> Identifier Quoting is a feature that is mainly intended to support legacy database +> schemas. The use of reserved words and identifier quoting is generally discouraged. \ No newline at end of file diff --git a/orm/manual/en/batch-processing.txt b/orm/manual/en/batch-processing.txt new file mode 100644 index 000000000..b9c3619d1 --- /dev/null +++ b/orm/manual/en/batch-processing.txt @@ -0,0 +1,122 @@ +This chapter shows you how to accomplish bulk inserts, updates and deletes with Doctrine in an efficient way. The main problem with bulk operations is usually not to run out of memory and this is especially what the strategies presented here provide help with. + +> **CAUTION** +> An ORM tool is not primarily well-suited for mass inserts, updates or deletions. +> Every RDBMS has its own, most effective way of dealing with such operations and if +> the options outlined below are not sufficient for your purposes we recommend you +> use the tools for your particular RDBMS for these bulk operations. + +++ Bulk Inserts + +Bulk inserts in Doctrine are best performed in batches, taking advantage of the transactional write-behind behavior of an `EntityManager`. The following code shows an example for inserting 10000 objects with a batch size of 20. You may need to experiment with the batch size to find the size that works best for you. Larger batch sizes mean more prepared statement reuse internally but also mean more work during `flush`. + + [php] + $batchSize = 20; + for ($i = 1; $i <= 10000; ++$i) { + $user = new CmsUser; + $user->setStatus('user'); + $user->setUsername('user' . $i); + $user->setName('Mr.Smith-' . $i); + $em->persist($user); + if (($i % $batchSize) == 0) { + $em->flush(); + $em->clear(); // Detaches all objects from Doctrine! + } + } + +++ Bulk Updates + +There are 2 possibilities for bulk updates with Doctrine. + ++++ DQL UPDATE + +The by far most efficient way for bulk updates is to use a DQL UPDATE query. Example: + + [php] + $q = $em->createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9'); + $numUpdated = $q->execute(); + ++++ Iterating results + +An alternative solution for bulk updates is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this, combining the iteration with the batching strategy that was already used for bulk inserts: + + [php] + $batchSize = 20; + $i = 0; + $q = $em->createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + foreach($iterableResult AS $row) { + $user = $row[0]; + $user->increaseCredit(); + $user->calculateNewBonuses(); + if (($i % $batchSize) == 0) { + $em->flush(); // Executes all updates. + $em->clear(); // Detaches all objects from Doctrine! + } + ++$i; + } + +> **NOTE** +> Iterating results is not possible with queries that fetch-join a collection-valued +> association. The nature of such SQL result sets is not suitable for incremental +> hydration. + +++ Bulk Deletes + +There are two possibilities for bulk deletes with Doctrine. You can either issue +a single DQL DELETE query or you can iterate over results removing them one at a time. + ++++ DQL DELETE + +The by far most efficient way for bulk deletes is to use a DQL DELETE query. + +Example: + + [php] + $q = $em->createQuery('delete from MyProject\Model\Manager m where m.salary > 100000'); + $numDeleted = $q->execute(); + ++++ Iterating results + +An alternative solution for bulk deletes is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this: + + [php] + $batchSize = 20; + $i = 0; + $q = $em->createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + while (($row = $iterableResult->next()) !== false) { + $em->remove($row[0]); + if (($i % $batchSize) == 0) { + $em->flush(); // Executes all deletions. + $em->clear(); // Detaches all objects from Doctrine! + } + ++$i; + } + +> **NOTE** +> Iterating results is not possible with queries that fetch-join a collection-valued +> association. The nature of such SQL result sets is not suitable for incremental +> hydration. + +++ Iterating Large Results for Data-Processing + +You can use the `iterate()` method just to iterate over a large result and no UPDATE or DELETE +intention. The `IterableResult` instance returned from `$query->iterate()` implements the +Iterator interface so you can process a large result without memory problems using the +following approach: + + [php] + $q = $this->_em->createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + foreach ($iterableResult AS $row) { + // do stuff with the data in the row, $row[0] is always the object + + // detach from Doctrine, so that it can be Garbage-Collected immediately + $this->_em->detach($row[0]); + } + +> **NOTE** +> Iterating results is not possible with queries that fetch-join a collection-valued +> association. The nature of such SQL result sets is not suitable for incremental +> hydration. \ No newline at end of file diff --git a/orm/manual/en/best-practices.txt b/orm/manual/en/best-practices.txt new file mode 100644 index 000000000..9c095a5ae --- /dev/null +++ b/orm/manual/en/best-practices.txt @@ -0,0 +1,77 @@ + +> **NOTE** +> The best practices mentioned here that affect database design generally refer to best +> practices when working with Doctrine and do not necessarily reflect best practices for +> database design in general. + +++ Don't use public properties on entities + +It is very important that you don't map public properties on entities, but only protected or private ones. +The reason for this is simple, whenever you access a public property of a proxy object that hasn't been initialized +yet the return value will be null. Doctrine cannot hook into this process and magically make the entity lazy load. + +This can create situations where it is very hard to debug the current failure. We therefore urge you to map only +private and protected properties on entities and use getter methods or magic __get() to access them. + +++ Constrain relationships as much as possible + +It is important to constrain relationships as much as possible. This means: + +* Impose a traversal direction (avoid bidirectional associations if possible) +* Eliminate nonessential associations + +This has several benefits: + +* Reduced coupling in your domain model +* Simpler code in your domain model (no need to maintain bidirectionality properly) +* Less work for Doctrine + +++ Avoid composite keys + +Even though Doctrine fully supports composite keys it is best not to use them if possible. Composite keys require additional work by Doctrine and thus have a higher probability of errors. + +++ Use events judiciously + +The event system of Doctrine is great and fast. Even though making heavy use of events, especially lifecycle events, can have a negative impact on the performance of your application. Thus you should use events judiciously. + +++ Use cascades judiciously + +Automatic cascades of the persist/remove/merge/etc. operations are very handy but should be used wisely. Do NOT simply add all cascades to all associations. Think about which cascades actually do make sense for you for a particular association, given the scenarios it is most likely used in. + +++ Don't use special characters + +Avoid using any non-ASCII characters in class, field, table or column names. Doctrine itself is not unicode-safe in many places and will not be until PHP itself is fully unicode-aware (PHP6). + +++ Don't use identifier quoting + +Identifier quoting is a workaround for using reserved words that often causes problems in edge cases. Do not use identifier quoting and avoid using reserved words as table or column names. + +++ Initialize collections in the constructor + +It is recommended best practice to initialize any business collections in entities in the constructor. Example: + + [php] + namespace MyProject\Model; + use Doctrine\Common\Collections\ArrayCollection; + + class User { + private $addresses; + private $articles; + + public function __construct() { + $this->addresses = new ArrayCollection; + $this->articles = new ArrayCollection; + } + } + +++ Don't map foreign keys to fields in an entity + +Foreign keys have no meaning whatsoever in an object model. Foreign keys are how a relational database establishes relationships. Your object model establishes relationships through object references. Thus mapping foreign keys to object fields heavily leaks details of the relational model into the object model, something you really should not do. + +++ Use explicit transaction demarcation + +While Doctrine will automatically wrap all DML operations in a transaction on flush(), it is considered best practice to explicitly set the transaction boundaries yourself. +Otherwise every single query is wrapped in a small transaction (Yes, SELECT queries, too) since you can not talk to your database outside of a transaction. +While such short transactions for read-only (SELECT) queries generally dont have any noticable performance impact, it is still preferrable to use fewer, well-defined transactions +that are established through explicit transaction boundaries. + diff --git a/orm/manual/en/caching.txt b/orm/manual/en/caching.txt new file mode 100644 index 000000000..f3e5d202f --- /dev/null +++ b/orm/manual/en/caching.txt @@ -0,0 +1,350 @@ +Doctrine provides cache drivers in the `Common` package for some of the most +popular caching implementations such as APC, Memcache and Xcache. We also provide +an `ArrayCache` driver which stores the data in a PHP array. Obviously, the cache +does not live between requests but this is useful for testing in a development +environment. + +++ Cache Drivers + +The cache drivers follow a simple interface that is defined in `Doctrine\Common\Cache\Cache`. +All the cache drivers extend a base class `Doctrine\Common\Cache\AbstractCache` +which implements the before mentioned interface. + +The interface defines the following methods for you to publicly use. + + * fetch($id) - Fetches an entry from the cache. + * contains($id) - Test if an entry exists in the cache. + * save($id, $data, $lifeTime = false) - Puts data into the cache. + * delete($id) - Deletes a cache entry. + +Each driver extends the `AbstractCache` class which defines a few abstract +protected methods that each of the drivers must implement. + + * _doFetch($id) + * _doContains($id) + * _doSave($id, $data, $lifeTime = false) + * _doDelete($id) + +The public methods `fetch()`, `contains()`, etc. utilize the above protected methods +that are implemented by the drivers. The code is organized this way so that the +protected methods in the drivers do the raw interaction with the cache implementation +and the `AbstractCache` can build custom functionality on top of these methods. + ++++ APC + +In order to use the APC cache driver you must have it compiled and enabled in +your php.ini. You can read about APC [here](http://us2.php.net/apc) on the PHP +website. It will give you a little background information about what it is and +how you can use it as well as how to install it. + +Below is a simple example of how you could use the APC cache driver by itself. + + [php] + $cacheDriver = new \Doctrine\Common\Cache\ApcCache(); + $cacheDriver->save('cache_id', 'my_data'); + ++++ Memcache + +In order to use the Memcache cache driver you must have it compiled and enabled in +your php.ini. You can read about Memcache [here](http://us2.php.net/memcache) on +the PHP website. It will give you a little background information about what it is +and how you can use it as well as how to install it. + +Below is a simple example of how you could use the Memcache cache driver by itself. + + [php] + $memcache = new Memcache(); + $memcache->connect('memcache_host', 11211); + + $cacheDriver = new \Doctrine\Common\Cache\MemcacheCache(); + $cacheDriver->setMemcache() + $cacheDriver->save('cache_id', 'my_data'); + ++++ Xcache + +In order to use the Xcache cache driver you must have it compiled and enabled in +your php.ini. You can read about Xcache [here](http://xcache.lighttpd.net/). It +will give you a little background information about what it is and how you can +use it as well as how to install it. + +Below is a simple example of how you could use the Xcache cache driver by itself. + + [php] + $cacheDriver = new \Doctrine\Common\Cache\XcacheCache(); + $cacheDriver->save('cache_id', 'my_data'); + +++ Using Cache Drivers + +In this section we'll describe how you can fully utilize the API of the cache +drivers to save cache, check if some cache exists, fetch the cached data and +delete the cached data. We'll use the `ArrayCache` implementation as our +example here. + + [php] + $cacheDriver = new \Doctrine\Common\Cache\ArrayCache(); + ++++ Saving + +To save some data to the cache driver it is as simple as using the `save()` method. + + [php] + $cacheDriver->save('cache_id', 'my_data'); + +The `save()` method accepts three arguments which are described below. + + * `$id` - The cache id + * `$data` - The cache entry/data. + * `$lifeTime` - The lifetime. If != false, sets a specific lifetime for this cache entry (null => infinite lifeTime). + +You can save any type of data whether it be a string, array, object, etc. + + [php] + $array = array( + 'key1' => 'value1', + 'key2' => 'value2' + ); + $cacheDriver->save('my_array', $array); + ++++ Checking + +Checking whether some cache exists is very simple, just use the `contains()` method. +It accepts a single argument which is the ID of the cache entry. + + [php] + if ($cacheDriver->contains('cache_id')) { + echo 'cache exists'; + } else { + echo 'cache does not exist'; + } + ++++ Fetching + +Now if you want to retrieve some cache entry you can use the `fetch()` method. It +also accepts a single argument just like `contains()` which is the ID of the cache entry. + + [php] + $array = $cacheDriver->fetch('my_array'); + ++++ Deleting + +As you might guess, deleting is just as easy as saving, checking and fetching. +We have a few ways to delete cache entries. You can delete by an individual ID, +regular expression, prefix, suffix or you can delete all entries. + +++++ By Cache ID + + [php] + $cacheDriver->delete('my_array'); + +You can also pass wild cards to the `delete()` method and it will return an array +of IDs that were matched and deleted. + + [php] + $deleted = $cacheDriver->delete('users_*'); + +++++ By Regular Expression + +If you need a little more control than wild cards you can use a PHP regular +expression to delete cache entries. + + [php] + $deleted = $cacheDriver->deleteByRegex('/users_.*/'); + +++++ By Prefix + +Because regular expressions are kind of slow, if simply deleting by a prefix or +suffix is sufficient, it is recommended that you do that instead of using a regular +expression because it will be much faster if you have many cache entries. + + [php] + $deleted = $cacheDriver->deleteByPrefix('users_'); + +++++ By Suffix + +Just like we did above with the prefix you can do the same with a suffix. + + [php] + $deleted = $cacheDriver->deleteBySuffix('_my_account'); + +++++ All + +If you simply want to delete all cache entries you can do so with the `deleteAll()` +method. + + [php] + $deleted = $cacheDriver->deleteAll(); + ++++ Counting + +If you want to count how many entries are stored in the cache driver instance +you can use the `count()` method. + + [php] + echo $cacheDriver->count(); + +> **NOTE** +> In order to use `deleteByRegex()`, `deleteByPrefix()`, `deleteBySuffix()`, +> `deleteAll()`, `count()` or `getIds()` you must enable an option for the cache +> driver to manage your cache IDs internally. This is necessary because APC, +> Memcache, etc. don't have any advanced functionality for fetching and deleting. +> We add some functionality on top of the cache drivers to maintain an index of +> all the IDs stored in the cache driver so that we can allow more granular deleting +> operations. +> +> [php] +> $cacheDriver->setManageCacheIds(true); + ++++ Namespaces + +If you heavily use caching in your application and utilize it in multiple parts +of your application, or use it in different applications on the same server you +may have issues with cache naming collisions. This can be worked around by using +namespaces. You can set the namespace a cache driver should use by using the +`setNamespace()` method. + + [php] + $cacheDriver->setNamespace('my_namespace_'); + +++ Integrating with the ORM + +The Doctrine ORM package is tightly integrated with the cache drivers to allow +you to improve performance of various aspects of Doctrine by just simply making +some additional configurations and method calls. + ++++ Query Cache + +It is highly recommended that in a production environment you cache the +transformation of a DQL query to its SQL counterpart. It doesn't make sense to +do this parsing multiple times as it doesn't change unless you alter the DQL +query. + +This can be done by configuring the query cache implementation to use on your ORM +configuration. + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + ++++ Result Cache + +The result cache can be used to cache the results of your queries so that we +don't have to query the database or hydrate the data again after the first time. +You just need to configure the result cache implementation. + + [php] + $config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + +Now when you're executing DQL queries you can configure them to use the result cache. + + [php] + $query = $em->createQuery('select u from \Entities\User u'); + $query->useResultCache(true); + +You can also configure an individual query to use a different result cache driver. + + [php] + $query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache()); + +> **NOTE** +> Setting the result cache driver on the query will automatically enable the +> result cache for the query. If you want to disable it pass false to +> `useResultCache()`. +> +> [php] +> $query->useResultCache(false); + +If you want to set the time the cache has to live you can use the `setResultCacheLifetime()` +method. + + [php] + $query->setResultCacheLifetime(3600); + +The ID used to store the result set cache is a hash which is automatically generated +for you if you don't set a custom ID yourself with the `setResultCacheId()` method. + + [php] + $query->setResultCacheId('my_custom_id'); + +You can also set the lifetime and cache ID by passing the values as the second +and third argument to `useResultCache()`. + + [php] + $query->useResultCache(true, 3600, 'my_custom_id'); + ++++ Metadata Cache + +Your class metadata can be parsed from a few different sources like YAML, XML, +Annotations, etc. Instead of parsing this information on each request we should +cache it using one of the cache drivers. + +Just like the query and result cache we need to configure it first. + + [php] + $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + +Now the metadata information will only be parsed once and stored in the cache +driver. + +++ Clearing the Cache + +We've already shown you previously how you can use the API of the cache drivers +to manually delete cache entries. For your convenience we offer a command line task +for you to help you with clearing the query, result and metadata cache. + +From the Doctrine command line you can run the following command. + + $ ./doctrine clear-cache + +Running this task with no arguments will clear all the cache for all the configured +drivers. If you want to be more specific about what you clear you can use the +following options. + +To clear the query cache use the `--query` option. + + $ ./doctrine clear-cache --query + +To clear the metadata cache use the `--metadata` option. + + $ ./doctrine clear-cache --metadata + +To clear the result cache use the `--result` option. + + $ ./doctrine clear-cache --result + +When you use the `--result` option you can use some other options to be more +specific about what queries result sets you want to clear. + +Just like the API of the cache drivers you can clear based on an ID, regular +expression, prefix or suffix. + + $ ./doctrine clear-cache --result --id=cache_id + +Or if you want to clear based on a regular expressions. + + $ ./doctrine clear-cache --result --regex=users_.* + +Or with a prefix. + + $ ./doctrine clear-cache --result --prefix=users_ + +And finally with a suffix. + + $ ./doctrine clear-cache --result --suffix=_my_account + +> **NOTE** +> Using the `--id`, `--regex`, etc. options with the `--query` and `--metadata` +> are not allowed as it is not necessary to be specific about what you clear. +> You only ever need to completely clear the cache to remove stale entries. + +++ Cache Slams + +Something to be careful of when utilizing the cache drivers is cache slams. If +you have a heavily trafficked website with some code that checks for the existence +of a cache record and if it does not exist it generates the information and saves +it to the cache. Now if 100 requests were issued all at the same time and each one +sees the cache does not exist and they all try and insert the same cache entry +it could lock up APC, Xcache, etc. and cause problems. Ways exist to work around +this, like pre-populating your cache and not letting your users requests populate +the cache. + +You can read more about cache slams [here](http://t3.dotgnu.info/blog/php/user-cache-timebomb). \ No newline at end of file diff --git a/orm/manual/en/configuration.txt b/orm/manual/en/configuration.txt new file mode 100644 index 000000000..b9a144bb3 --- /dev/null +++ b/orm/manual/en/configuration.txt @@ -0,0 +1,407 @@ +++ Bootstrapping + +Bootstrapping Doctrine is a relatively simple procedure that roughly exists of +just 2 steps: + +* Making sure Doctrine class files can be loaded on demand. +* Obtaining an EntityManager instance. + ++++ Class loading + +Lets start with the class loading setup. We need to set up some class loaders +(often called "autoloader") so that Doctrine class files are loaded on demand. +The Doctrine\Common namespace contains a very fast and minimalistic class loader +that can be used for Doctrine and any other libraries where the coding standards +ensure that a class's location in the directory tree is reflected by its name +and namespace and where there is a common root namespace. + +> **NOTE** +> You are not forced to use the Doctrine class loader to load Doctrine +> classes. Doctrine does not care how the classes are loaded, if you want to use a +> different class loader or your own to load Doctrine classes, just do that. +> Along the same lines, the class loader in the Doctrine\Common namespace is not +> meant to be only used for Doctrine classes, too. It is a generic class loader that can +> be used for any classes that follow some basic naming standards as described above. + +The following example shows the setup of a `ClassLoader` + +> **NOTE** +> This assumes you've created some kind of script to test the following code in. +> Something like a `test.php` file. + + [php] + // test.php + + require '/path/to/lib/Doctrine/Common/ClassLoader.php'; + $classLoader = new \Doctrine\Common\ClassLoader('Doctrine', '/path/to/Doctrine2/lib); + $classLoader->register(); // register on SPL autoload stack + +For best class loading performance it is recommended that you keep your include_path short, ideally it should only contain the path to the PEAR libraries, and any other class libraries should be registered with their full base path. + ++++ Obtaining an EntityManager + +Once you have prepared the class loading, you acquire an EntityManager instance +with the following minimalist configuration: + + [php] + use Doctrine\ORM\EntityManager, + Doctrine\ORM\Configuration; + + // ... + + $config = new Configuration; + $cache = new \Doctrine\Common\Cache\ApcCache; + $config->setMetadataCacheImpl($cache); + $config->setQueryCacheImpl($cache); + $config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); + $config->setProxyNamespace('MyProject\Proxies'); + $connectionOptions = array( + 'driver' => 'pdo_sqlite', + 'path' => 'database.sqlite' + ); + + $em = EntityManager::create($connectionOptions, $config); + +> **CAUTION** +> Do not use Doctrine without a metadata and query cache! Doctrine is highly +> optimized for working with caches. The main parts in Doctrine that are optimized +> for caching are the metadata mapping information with the metadata cache and the +> DQL to SQL conversions with the query cache. These 2 caches require only an absolute +> minimum of memory yet they heavily improve the runtime performance of Doctrine. +> The recommended cache driver to use with Doctrine is [APC](http://www.php.net/apc). +> APC provides you with an opcode-cache (which is highly recommended anyway) and +> a very fast in-memory cache storage that you can use for the metadata and query +> caches as seen in the previous code snippet. + +An EntityManager is your central access point to ORM functionality provided by Doctrine. + +++ Configuration Options + +The following sections describe all the configuration options available on a `Doctrine\ORM\Configuration` instance. + ++++ Proxy Directory (***REQUIRED***) + + [php] + $config->setProxyDir($dir); + $config->getProxyDir(); + +Gets or sets the directory where Doctrine generates any proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. + ++++ Proxy Namespace (***REQUIRED***) + + [php] + $config->setProxyNamespace($namespace); + $config->getProxyNamespace(); + +Gets or sets the namespace to use for generated proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. + ++++ Metadata Driver (***REQUIRED***) + + [php] + $config->setMetadataDriverImpl($driver); + $config->getMetadataDriverImpl(); + +Gets or sets the metadata driver implementation that is used by Doctrine to acquire the object-relational metadata for your classes. + +There are currently 4 available implementations: + + * `Doctrine\ORM\Mapping\Driver\AnnotationDriver` + * `Doctrine\ORM\Mapping\Driver\XmlDriver` + * `Doctrine\ORM\Mapping\Driver\YamlDriver` + * `Doctrine\ORM\Mapping\Driver\DriverChain` + +Throughout the most part of this manual the AnnotationDriver is used in the examples. For information on the usage of the XmlDriver or YamlDriver please refer to the dedicated chapters `XML Mapping` and `YAML Mapping`. + +The annotation driver is configured as the default metadata driver, using Annotations as Metadata source +does not require you to set this option explicitly. + ++++ Metadata Cache (***RECOMMENDED***) + + [php] + $config->setMetadataCacheImpl($cache); + $config->getMetadataCacheImpl(); + +Gets or sets the cache implementation to use for caching metadata information, that is, all the information you supply via annotations, xml or yaml, so that they do not need to be parsed and loaded from scratch on every single request which is a waste of resources. +The cache implementation must implement the `Doctrine\Common\Cache\Cache` interface. + +Usage of a metadata cache is highly recommended. + +The recommended implementations are: + + * `Doctrine\Common\Cache\ApcCache` + * `Doctrine\Common\Cache\MemcacheCache` + * `Doctrine\Common\Cache\XcacheCache` + ++++ Query Cache (***RECOMMENDED***) + + [php] + $config->setQueryCacheImpl($cache); + $config->getQueryCacheImpl(); + +Gets or sets the cache implementation to use for caching DQL queries, that is, the result of a DQL parsing process that includes the final SQL as well as meta information about how to process the SQL result set of a query. Note that the query cache does not affect query results. You do not get stale data. This is a pure optimization cache without any negative side-effects (except some minimal memory usage in your cache). + +Usage of a query cache is highly recommended. + +The recommended implementations are: + + * `Doctrine\Common\Cache\ApcCache` + * `Doctrine\Common\Cache\MemcacheCache` + * `Doctrine\Common\Cache\XcacheCache` + ++++ SQL Logger (***Optional***) + + [php] + $config->setSqlLogger($logger); + $config->getSqlLogger(); + +Gets or sets the logger to use for logging all SQL statements executed by Doctrine. The logger class must implement the `Doctrine\DBAL\Logging\SqlLogger` interface. A simple default implementation that logs to the standard output using `echo` and `var_dump` can be found at `Doctrine\DBAL\Logging\EchoSqlLogger`. + ++++ Auto-generating Proxy Classes (***OPTIONAL***) + + [php] + $config->setAutoGenerateProxyClasses($bool); + $config->getAutoGenerateProxyClasses(); + +Gets or sets whether proxy classes should be generated automatically at runtime by Doctrine. If set to `FALSE`, proxy classes must be generated manually through the doctrine command line task `generate-proxies`. The strongly recommended value for a production environment is `FALSE`. + +++ Connection Options + +The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array +or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the +DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The DBAL configuration is explained +in the [DBAL section](./../dbal). + +++ 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: + + /** + * @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 should 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; + } + + protected function _onPropertyChanged($propName, $oldValue, $newValue) + { + if ($this->_listeners) { + foreach ($this->_listeners as $listener) { + $listener->propertyChanged($this, $propName, $oldValue, $newValue); + } + } + } + } + +Then, in each property setter of this class or derived classes, you need to +invoke `_onPropertyChanged` as follows to notify listeners: + + [php] + data) { + $this->_onPropertyChanged('data', $this->data, $data); + $this->data = $data; + } + } + } + +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. + +++ Partial Objects + +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 `Query#HINT_FORCE_PARTIAL_LOAD` query hint as follows: + + [php] + $q = $em->createQuery("select u.id, u.name from MyApp\Domain\User u"); + $q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); + ++++ When should I force partial objects? + +Mainly for optimization purposes, especially since the stateless nature of PHP +applications means that any fields or objects that are loaded unnecessarily in +a request are useless (though often minimal) overhead. Be careful of premature optimization. Only force partial objects if it proves to provide an improvement to a performance problem. + +++ Proxy Objects + +A proxy object is an object that is put in place or used instead of the "real" object. A proxy object can add behavior to the object being proxied without that object being aware of it. In Doctrine 2, proxy objects are used to realize several features but mainly for transparent lazy-loading. + +Proxy objects with their lazy-loading facilities help to keep the subset of objects that are already in memory connected to the rest of the objects. This is an essential property as without it there would always be fragile partial objects at the outer edges of your object graph. + +Doctrine 2 implements a variant of the proxy pattern where it generates classes that extend your entity classes and adds lazy-loading capabilities to them. Doctrine can then give you an instance of such a proxy class whenever you request an object of the class being proxied. This happens in two situations: + +**Reference Proxies** + +The method `EntityManager#getReference($entityName, $identifier)` lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier. You could simply do this: + + [php] + // $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart + // $itemId comes from somewhere, probably a request parameter + $item = $em->getReference('MyProject\Model\Item', $itemId); + $cart->addItem($item); + +Here, we added an Item to a Cart without loading the Item from the database. If you invoke any method on the Item instance, it would fully initialize its state transparently from the database. Here $item is actually an instance of the proxy class that was generated for the Item class but your code does not need to care. In fact it **should not care**. Proxy objects should be transparent to your code. + +**Association proxies** + +The second most important situation where Doctrine uses proxy objects is when querying for objects. Whenever you query for an object that has a single-valued association to another object that is configured LAZY, without joining that association in the same query, Doctrine puts proxy objects in place where normally the associated object would be. +Just like other proxies it will transparently initialize itself on first access. + +> **NOTE** +> Joining an association in a DQL or native query essentially means eager loading of that +> association in that query. This will override the 'fetch' option specified in +> the mapping for that association, but only for that query. + ++++ Generating Proxy classes + +Proxy classes can either be generated manually through the Doctrine CLI or automatically by Doctrine. The configuration option that controls this behavior is: + + [php] + $config->setAutoGenerateProxyClasses($bool); + $config->getAutoGenerateProxyClasses(); + +The default value is `TRUE` for convenient development. However, this setting is not optimal for performance and therefore not recommended for a production environment. +To eliminate the overhead of proxy class generation during runtime, set this configuration option to `FALSE`. When you do this in a development environment, note that you may get class/file not found errors if certain proxy classes are not available or failing lazy-loads if new methods were added to the entity class that are not yet in the proxy class. In such a case, simply use the Doctrine CLI to (re)generate the proxy classes like so: + + doctrine orm:generate-proxies + +++ Multiple Metadata Sources + +When using different components using Doctrine 2 you may end up with them using two different metadata drivers, +for example XML and YAML. You can use the DriverChain Metadata implementations to aggregate these drivers +based on namespaces: + + [php] + $chain = new DriverChain(); + $chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company'); + $chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping'); + +Based on the namespace of the entity the loading of entities is delegated to the appropriate driver. The chain +semantics come from the fact that the driver loops through all namespaces and matches the entity class name +against the namespace using a `strpos() === 0` call. This means you need to order the drivers correctly if +sub-namespaces use different metadata driver implementations. \ No newline at end of file diff --git a/orm/manual/en/dql-doctrine-query-language.txt b/orm/manual/en/dql-doctrine-query-language.txt new file mode 100755 index 000000000..87aa54a93 --- /dev/null +++ b/orm/manual/en/dql-doctrine-query-language.txt @@ -0,0 +1,803 @@ +++ DQL Explained + +DQL stands for **D**octrine **Q**uery **L**anguage and is an Object Query Language derivate that is very similar to the **H**ibernate **Q**uery **L**anguage (HQL) or the **J**ava **P**ersistence **Q**uery **L**anguage (JPQL). + +In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects. + +> **CAUTION** +> A common mistake for beginners is to mistake DQL for being just some form of SQL +> and therefore trying to use table names and column names or join arbitrary tables +> together in a query. You need to think about DQL as a query language for your object +> model, not for your relational schema. + +DQL is case in-sensitive, except for namespace, class and field names, which are case sensitive. + + +++ Types of DQL queries + +DQL as a query language has SELECT, UPDATE and DELETE constructs that map to their corresponding +SQL statement types. INSERT statements are not allowed in DQL, because entities and their relations +have to be introduced into the persistence context through `EntityManager#persist()` to ensure +consistency of your object model. + +DQL SELECT statements are a very powerful way of retrieving parts of your domain model that are +not accessible via assocations. Additionally they allow to retrieve entities and their associations +in one single sql select statement which can make a huge difference in performance in contrast +to using several queries. + +DQL UPDATE and DELETE statements offer a way to execute bulk changes on the entities of your +domain model. This is often necessary when you cannot load all the affected entities of a bulk +update into memory. + +++ SELECT queries + ++++ DQL SELECT clause + +The select clause of a DQL query specifies what appears in the query result. The composition of all the expressions in the select clause also influences the nature of the query result. + +Here is an example that selects all users with an age > 20: + + [sql] + SELECT u FROM MyProject\Model\User u WHERE u.age > 20 + +Lets examine the query: + +* `u` is a so called identification variable or alias that refers to the `MyProject\Model\User` class. By placing this alias in the SELECT clause we specify that we want all instances of the User class that are matched by this query appear in the query result. +* The FROM keyword is always followed by a fully-qualified class name which in turn is followed by an identification variable or alias for that class name. This class designates a root of our query from which we can navigate further via joins (explained later) and path expressions. +* The expression `u.age` in the WHERE clause is a path expression. Path expressions in DQL are easily identified by the use of the '.' operator that is used for constructing paths. The path expression `u.age` refers to the `age` field on the User class. + +The result of this query would be a list of User objects where all users are older than 20. + +The SELECT clause allows to specify both class identification variables that signal the hydration +of a complete entity class or just fields of the entity using the syntax `u.name`. +Combinations of both are also allowed and it is possible to wrap both fields and +identification values into aggregation and DQL functions. Numerical fields can +be part of computations using mathematical operatos. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn) +on more information. + ++++ Joins + +A SELECT query can contain joins. There are 2 types of JOINs: "Regular" Joins and "Fetch" Joins. + +**Regular Joins**: Used to limit the results and/or compute aggregate values. + +**Fetch Joins**: In addition to the uses of regular joins: Used to fetch related entities and include them in the hydrated result of a query. + +There is no special DQL keyword that distinguishes a regular join from a fetch join. A join (be it an inner or outer join) becomes a "fetch join" as soon as fields of the joined entity appear in the SELECT part of the DQL query outside of an aggregate function. Otherwise its a "regular join". + +Example: + +Regular join of the address: + + [sql] + SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin' + +Fetch join of the address: + + [sql] + SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin' + +When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the +root level of the result array. In the previous example an array of User instances is returned +and the address of each user is fetched and hydrated into the `User#address` variable. If you access +the address Doctrine does not need to + +> **NOTE** +> Doctrine allows you to walk all the assocations between all the objects in your domain model. +> Objects that were not already loaded from the database are replaced with lazy load proxy instances. +> Non-loaded Collections are also replaced by lazy-load instances that fetch all the contained objects upon +> first access. However relying on the lazy-load mechanism leads to many small queries executed +> against the database, which can significantly affect the performance of your application. +> **Fetch Joins** are the solution to hydrate most or all of the entities that you +> need in a single SELECT query. + ++++ Named and Positional Parameters + +DQL supports both named and positional parameters, however in contrast to many SQL dialects +positional parameters are specified with numbers, for example "?1", "?2" and so on. +Named parameters are specified with ":name1", ":name2" and so on. + ++++ DQL SELECT Examples + +This section contains a large set of DQL queries and some explainations of what is happening. +The actual result also depends on the hydration mode. + + [sql] + -- Hydrate all Users + SELECT u FROM MyProject\Model\User u + + -- Retrieve the IDs of all CmsUsers + SELECT u.id FROM CmsUser u + + -- Retrieve the IDs of all users that have written an article + SELECT DISTINCT a.user.id FROM CmsArticle a + + -- Retrieve all articles and sort them by the name of the articles users instance + SELECT a FROM CmsArticle a ORDER BY a.user.name ASC + + -- Retrieve the Username and Name of a CmsUser + SELECT u.username, u.name FROM CmsUser u + + -- Retrieve a ForumUser and his single associated entity + SELECT u, a FROM ForumUser u JOIN u.avatar a + + -- Retrieve a CmsUser and fetch join all the phonenumbers he has + SELECT u, p FROM CmsUser u JOIN u.phonenumbers p + + -- Hydrate a result in Ascending or Descending Order + SELECT u FROM ForumUser u ORDER BY u.id ASC + SELECT u FROM ForumUser u ORDER BY u.id DESC + + -- Using Aggregate Functions + SELECT COUNT(u.id) FROM CmsUser u GROUP BY u.id + + -- With WHERE Clause and Positional Parameter + SELECT u FROM ForumUser u WHERE u.id = ?1 + + -- With WHERE Clause and Named Parameter + SELECT u FROM ForumUser u WHERE u.username = :name + + -- With Nested Conditions in WHERE Clause + SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id + + -- With COUNT DISTINCT + SELECT COUNT(DISTINCT u.name) FROM CmsUser + + -- With Arithmetic Expression in WHERE clause + SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000 + + -- Using multiple classes fetched using a FROM clause (all returned on the root level of the result) + SELECT u, a FROM CmsUser u, CmsArticle a WHERE u.id = a.user.id + + -- Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids + SELECT u.id, a.id FROM CmsUser u LEFT JOIN u.articles a + + -- Restricting a JOIN clause by additional conditions + SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%' + + -- Using several Fetch JOINs + SELECT u, a, p, c FROM CmsUser u + JOIN u.articles a + JOIN u.phonenumbers p + JOIN a.comments c + + -- BETWEEN in WHERE clause + SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2 + + -- DQL Functions in WHERE clause + SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone' + + -- IN() Expression + SELECT u.name FROM CmsUser u WHERE u.id IN(46) + SELECT u FROM CmsUser u WHERE u.id IN (1, 2) + SELECT u FROM CmsUser u WHERE u.id NOT IN (1) + + -- CONCAT() DQL Function + SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1 + SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1 + + -- EXISTS in WHERE clause with correlated Subquery + SELECT u.id FROM CmsUser u + WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.phonenumber = u.id) + + -- Get all users who are members of $group. + SELECT u.id FROM CmsUser u WHERE :param MEMBER OF u.groups + + -- Get all users that have more than 1 phonenumber + SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1 + + -- Get all users that have no phonenumber + SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY + ++++ Using INDEX BY + +The INDEX BY construct is nothing that directly translates into SQL but that affects +object and array hydration. After each FROM and JOIN clause you specify by which field +this class should be indexed in the result. By default a result is incremented +by numerical keys starting with 0. However with INDEX BY you can specifiy any +other column to be the key of your result, it really only makes sense with primary +or unique fields though: + + [sql] + SELECT u.id, u.status, upper(u.name) nameUpper FROM User u INDEX BY u.id + JOIN u.phonenumbers p INDEX BY p.phonenumber + +Returns an array of the following kind, indexed by both user-id then phonenumber-id: + + array + 0 => + array + 1 => + object(stdClass)[299] + public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) + public 'id' => int 1 + .. + 'nameUpper' => string 'ROMANB' (length=6) + 1 => + array + 2 => + object(stdClass)[298] + public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) + public 'id' => int 2 + ... + 'nameUpper' => string 'JWAGE' (length=5) + +++ UPDATE queries + +DQL not only allows to select your Entities using field names, you can also execute bulk updates on a set +of entities using an DQL-UPDATE query. The Syntax of an UPDATE query works as expected, as the following +example shows: + + UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) + +References to related entities are only possible in the WHERE clause and using subselects. + +> **CAUTION** +> DQL UPDATE statements are ported directly into a Database UPDATE statement and therefore bypass +> any locking scheme and do not increment the version column. Entities that are already +> loaded into the persistence context will *NOT* be synced with the updated database state. It +> is recommended to call `EntityManager#clear()` and retrieve new instances of any affected entity. + +++ DELETE queries + +DELETE queries can also be specified using DQL and their syntax is as simple as the UPDATE syntax: + + DELETE MyProject\Model\User u WHERE u.id = 4 + +The same restrictions apply for the reference of related entities. + +> **CAUTION** +> DQL DELETE statements are ported directly into a Database DELETE statement and therefore +> bypass any checks for the version column if they are not explicitly added to the WHERE +> clause of the query. Additionally Deletes of specifies entities are *NOT* cascaded +> to related entities even if specified in the metadata. + +++ Functions, Operators, Aggregates + + ++++ DQL Functions + +The following functions are supported in SELECT, WHERE and HAVING clauses: + +* ABS(arithmetic_expression) +* CONCAT(str1, str2) +* CURRENT_DATE() - Return the current date +* CURRENT_TIME() - Returns the current time +* CURRENT_TIMESTAMP() - Returns a timestamp of the current date and time. +* LENGTH(str) - Returns the length of the given string +* LOCATE(needle, haystack [, offset]) - Locate the first occurance of the substring in the string. +* LOWER(str) - returns the string lowercased. +* MOD(a, b) - Return a MOD b. +* SIZE(collection) - Return the number of elements in the specified collection +* SQRT(q) - Return the squareroot of q. +* SUBSTRING(str, start [, length]) - Return substring of given string. +* TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str) - Trim the string by the given trim char, defaults to whitespaces. +* UPPER(str) - Return the upper-case of the given string. + ++++ Arithmetic operators + +You can do math in DQL using numeric values, for example: + + SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 + ++++ Aggregate Functions + +The following aggregate functions are allowed in SELECT and GROUP BY clauses: AVG, COUNT, MIN, MAX, SUM + ++++ Other Expressions + +DQL offers a wide-range of additional expressions that are known from SQL, here is a list of +all the supported constructs: + +* `ALL/ANY/SOME` - Used in a WHERE clause followed by a subselect this works like the equivalent constructs in SQL. +* `BETWEEN a AND b` and `NOT BETWEEN a AND b` can be used to match ranges of arithmetic values. +* `IN (x1, x2, ...)` and `NOT IN (x1, x2, ..)` can be used to match a set of given values. +* `LIKE ..` and `NOT LIKE ..` match parts of a string or text using % as a wildcard. +* `IS NULL` and `IS NOT NULL` to check for null values +* `EXISTS` and `NOT EXISTS` in combination with a subselect + ++++ Adding your own functions to the DQL language + +By default DQL comes with functions that are part of a large basis of underlying databases. However you will most likely +choose a database platform at the beginning of your project and most likely never change it. For this cases you can +easily extend the DQL parser with own specialized platform functions. + +You can register custom DQL functions in your ORM Configuration: + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->addCustomStringFunction($name, $class); + $config->addCustomNumericFunction($name, $class); + $config->addCustomDatetimeFunction($name, $class); + + $em = EntityManager::create($dbParams, $config); + +The functions have to return either a string, numeric or datetime value depending on the registered function type. As an example +we will add a MySQL specific FLOOR() functionality. All the given classes have to implement the base class +\Doctrine\ORM\Query\AST\Functions\FunctionNode: + + [php] + namespace MyProject\Query\AST; + + use \Doctrine\ORM\Query\AST\Functions\FunctionsNode; + + class MysqlFloor extends FunctionNode + { + public $simpleArithmeticExpression; + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'FLOOR(' . $sqlWalker->walkSimpleArithmeticExpression( + $this->simpleArithmeticExpression + ) . ')'; + } + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $lexer = $parser->getLexer(); + + $parser->match(Lexer::T_ABS); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + } + +We will register the function by calling and can then use it: + + [php] + \Doctrine\ORM\Query\Parser::registerNumericFunction('FLOOR', 'MyProject\Query\MysqlFloor'); + $dql = "SELECT FLOOR(person.salary * 1.75) FROM CompanyPerson person"; + +++ The Query class + +An instance of the `Doctrine\ORM\Query` class represents a DQL query. You create a Query instance be calling `EntityManager#createQuery($dql)`, passing the DQL query string. Alternatively you can create an empty `Query` instance and invoke `Query#setDql($dql)` afterwards. Here are some examples: + + [php] + // $em instanceof EntityManager + + // example1: passing a DQL string + $q = $em->createQuery('select u from MyProject\Model\User u'); + + // example2: usin setDql + $q = $em->createQuery(); + $q->setDql('select u from MyProject\Model\User u'); + + ++++ Query Result Formats + +The format in which the result of a DQL SELECT query is returned can be influenced by a so-called `hydration mode`. A hydration mode specifies a particular way in which an SQL result set is transformed. Each hydration mode has its own dedicated method on the Query class. Here they are: + +* `Query#getResult()`: Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array where the objects are nested in the result rows (mixed). +* `Query#getSingleResult()`: Retrieves a single object. If the result contains more than one object, an exception is thrown. The pure/mixed distinction does not apply. +* `Query#getArrayResult()`: Retrieves an array graph (a nested array) that is largely interchangeable with the object graph generated by `Query#getResultList()` for read-only purposes. + +> **NOTE** +> An array graph can differ from the corresponding object graph in +> certain scenarios due to the difference of the identity semantics between arrays and +> objects. + +* `Query#getScalarResult()`: Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The pure/mixed distinction does not apply. +* `Query#getSingleScalarResult()`: Retrieves a single scalar value from the result returned by the dbms. If the result contains more than a single scalar value, an exception is thrown. The pure/mixed distinction does not apply. + +Instead of using these methods, you can alternatively use the general-purpose method `Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)`. Using this method you can directly supply the hydration mode as the second parameter via one of the Query constants. In fact, the methods mentioned earlier are just convenient shortcuts for the execute method. For example, the method `Query#getResultList()` internally invokes execute, passing in `Query::HYDRATE_OBJECT` as the hydration mode. + +The use of the methods mentioned earlier is generally preferred as it leads to more concise code. + ++++ Pure and Mixed Results + +The nature of a result returned by a DQL SELECT query retrieved through `Query#getResult()` or `Query#getArrayResult()` can be of 2 forms: **pure** and **mixed**. In the previous simple examples, you already saw a "pure" query result, with only objects. By default, the result type is **pure** but **as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. A mixed result has a different structure than a pure result in order to accomodate for the scalar values. + +A pure result usually looks like this: + + array + [0] => Object + [1] => Object + [2] => Object + ... + +A mixed result on the other hand has the following general structure: + + array + array + [0] => Object + [1] => "some scalar string" + ['count'] => 42 + // ... more scalar values, either indexed numerically or with a name + array + [0] => Object + [1] => "some scalar string" + ['count'] => 42 + // ... more scalar values, either indexed numerically or with a name + +To better understand mixed results, consider the following DQL query: + + [sql] + SELECT u, UPPER(u.name) nameUpper FROM MyProject\Model\User u + +This query makes use of the `UPPER` DQL function that returns a scalar value and because there is now a scalar value in the SELECT clause, we get a mixed result. + +Here is how the result could look like: + + array + array + [0] => User (Object) + ['nameUpper'] => "Roman" + array + [0] => User (Object) + ['nameUpper'] => "Jonathan" + ... + +And here is how you would access it in PHP code: + + [php] + foreach ($results as $row) { + echo "Name: " . $row[0]->getName(); + echo "Name UPPER: " . $row['nameUpper']; + } + +You may have observed that in a mixed result, the object always ends up on index 0 of a result row. + ++++ Hydration Mode Asumptions + +Each of the Hydration Modes makes assumptions about how the result is returned to userland. You should +know about all the details to make best use of the different result formats: + +++++ Object Hydration + +++++ Array Hydration + +++++ Scalar Hydration Details + +The following assumptions are made about selected fields using Scalar Hydration: + +1. Fields from classes are prefixed by the DQL alias in the result. A query of the kind 'SELECT u.name ..' returns a key 'u_name' in the result rows. + ++++ Iterating Large Resultsets + +There are situations when a query you want to execute returns a very large result-set that needs +to be processed. All the previously described hydration modes completly load a result-set into +memory which might not be feasible with large result sets. See the [Batch Processing](batch-processing) +section on details how to iterate large result sets. + ++++ Functions + +The following methods exist on the `AbstractQuery` which both `Query` and `NativeQuery` extend from. + +++++ Parameters + +Prepared Statements that use numerical or named wildcards require additional parameters to be executable +agains the database. To pass parameters to the query the following methods can be used: + +* `AbstractQuery::setParameter($param, $value)` - Set the numerical or named wildcard to the given value. +* `AbstractQuery::setParameters(array $params)` - Set an array of parameter key-value pairs. +* `AbstractQuery::getParameter($param)` +* `AbstractQuery::getParameters()` + +++++ Cache related API + +You can cache query results based either on all variables that define the result (SQL, Hydration Mode, Parameters and Hints) +or on user-defined cache keys. However by default query results are not cached at all. You have to enable +the result cache on a per query basis. The following example shows a complete workflow using the Result Cache +API: + + [php] + $query = $em->createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); + $query->setParameter(1, 12); + + $query->setResultCacheDriver(new ApcCache()); + + $query->useResultCache(true) + ->setResultCacheLifeTime($seconds = 3600); + + $result = $query->getResult(); // cache miss + + $query->expireResultCache(true); + $result = $query->getResult(); // forced expire, cache miss + + $query->setResultCacheId('my_query_result'); + $result = $query->getResult(); // saved in given result cache id. + + // or call useResultCache() with all parameters: + $query->useResultCache(true, $seconds = 3600, 'my_query_result'); + $result = $query->getResult(); // cache hit! + +> **TIP!** +> You can set the Result Cache Driver globally on the `Doctrine\ORM\Configuration` instance +> so that it is passed to every `Query` and `NativeQuery` instance. + +++++ Query Hints + +You can pass hints to the query parser and hydrators by using the `AbstractQuery::setHint($name, $value)` method. +Currently there exist mostly internal query hints that are not be consumed in userland. However the following few hints +are to be used in userland: + +* Query::HINT_FORCE_PARTIAL_LOAD - Allows to hydrate objects although not all their columns are fetched. This query +hint can be used to handle memory consumption problems with large result-sets that contain char or binary data. +Doctrine has no way of implicitly reloaded this data. Partially loaded objects have to be passed to +`EntityManager::refresh()` if they are to be reloaded fully from the database. +* Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland aswell. +If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the +fields of the existing entity will be refreshed. In normal operation a result-set that loads data of an already existing +entity is discarded in favour of the already existing entity. +* Query::HINT_CUSTOM_TREE_WALKERS - An array of additional `Doctrine\ORM\Query\TreeWalker` instances that are attached +to the DQL query parsing process. + +++++ Query Cache (DQL Query Only) + +Parsing a DQL query and converting it into an SQL query against the underlying database platform obviously has some overhead +in contrast to directly executing Native SQL queries. That is why there is a dedicated Query Cache for caching the +DQL parser results. In combination with the use of wildcards you can reduce the number of parsed queries in production +to zero. + +The Query Cache Driver is passed from the `Doctrine\ORM\Configuration` instance to each `Doctrine\ORM\Query` instance +by default and is also enabled by default. This also means you don't regularly need to fiddle with the parameters +of the Query Cache, however if you do there are several methods to interact with it: + +* `Query::setQueryCacheDriver($driver)` - Allows to set a Cache instance +* `Query::setQueryCacheLifeTime($seconds = 3600)` - Set lifetime of the query caching. +* `Query::expireQueryCache($bool)` - Enforce the expiring of the query cache if set to true. +* `Query::getExpireQueryCache()` +* `Query::getQueryCacheDriver()` +* `Query::getQueryCacheLifeTime()` + +++++ First and Max Result Items (DQL Query Only) + +You can limit the number of results returned from a DQL query aswell as specify the starting offset, Doctrine +then uses a strategy of manipulating the select query to return only the requested number of results: + +* `Query::setMaxResults($maxResults)` +* `Query::setFirstResult($offset)` + +> **NOTE** +> If your query contains a fetch-joined collection specifying the result limit methods are not working +> as you would expect. Set Max Results restricts the number of database result rows, however in the +> case of fetch-joined collections one root entity might appear in many rows, effectivly hydrating +> less than the specified number of results. + +++ EBNF + +The following context-free grammar, written in an EBNF variant, describes the Doctrine Query Language. You can consult this grammar whenever you are unsure about what is possible with DQL or what the correct syntax for a particular query should be. + ++++ Document syntax: + +* non-terminals begin with an upper case character +* terminals begin with a lower case character +* parentheses (...) are used for grouping +* square brackets [...] are used for defining an optional part, eg. zero or one time +* curly brackets {...} are used for repetion, eg. zero or more times +* double quotation marks "..." define a terminal string a vertical bar | represents an alternative + ++++ Terminals + +* identifier (name, email, ...) +* string ('foo', 'bar''s house', '%ninja%', ...) +* char ('/', '\\', ' ', ...) +* integer (-1, 0, 1, 34, ...) +* float (-0.23, 0.007, 1.245342E+8, ...) +* boolean (false, true) + ++++ Query Language + + QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement + ++++ Statements + + SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + UpdateStatement ::= UpdateClause [WhereClause] + DeleteStatement ::= DeleteClause [WhereClause] + ++++ Identifiers + + /* Alias Identification usage (the "u" of "u.name") */ + IdentificationVariable ::= identifier + + /* Alias Identification declaration (the "u" of "FROM User u") */ + AliasIdentificationVariable :: = identifier + + /* identifier that must be a class name (the "User" of "FROM User u") */ + AbstractSchemaName ::= identifier + + /* identifier that must be a field (the "name" of "u.name") */ + /* This is responsable to know if the field exists in Object, no matter if it's a relation or a simple field */ + FieldIdentificationVariable ::= identifier + + /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ + CollectionValuedAssociationField ::= FieldIdentificationVariable + + /* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */ + SingleValuedAssociationField ::= FieldIdentificationVariable + + /* identifier that must be an embedded class state field (for the future) */ + EmbeddedClassStateField ::= FieldIdentificationVariable + + /* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ + /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ + SimpleStateField ::= FieldIdentificationVariable + + /* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */ + AliasResultVariable = identifier + + /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ + ResultVariable = identifier + ++++ Path Expressions + + /* "u.Group" or "u.Phonenumbers" declarations */ + JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) + + /* "u.Group" or "u.Phonenumbers" usages */ + AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression + + /* "u.name" or "u.Group" */ + SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression + + /* "u.name" or "u.Group.name" */ + StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField + + /* "u.Group" */ + SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField + + /* "u.Group.Permissions" */ + CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField + + /* "name" */ + StateField ::= {EmbeddedClassStateField "."}* SimpleStateField + + /* "u.name" or "u.address.zip" (address = EmbeddedClassStateField) */ + SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField + + ++++ Clauses + + SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* + SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression + UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* + DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable + FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* + SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* + WhereClause ::= "WHERE" ConditionalExpression + HavingClause ::= "HAVING" ConditionalExpression + GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* + OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* + Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + + ++++ Items + + UpdateItem ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue + OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] + GroupByItem ::= IdentificationVariable | SingleValuedPathExpression + NewValue ::= ScalarExpression | SimpleEntityExpression | "NULL" + + ++++ From, Join and Index by + + IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* + SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) + JoinVariableDeclaration ::= Join [IndexBy] + RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable + Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression + ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] + IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression + + ++++ Select Expressions + + SelectExpression ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")" | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable] + SimpleSelectExpression ::= ScalarExpression | IdentificationVariable | + (AggregateExpression [["AS"] AliasResultVariable]) + PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet + PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" + + ++++ Conditional Expressions + + ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* + ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* + ConditionalFactor ::= ["NOT"] ConditionalPrimary + ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" + SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression | + InExpression | NullComparisonExpression | ExistsExpression | + EmptyCollectionComparisonExpression | CollectionMemberExpression + + ++++ Collection Expressions + + EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" + CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression + ++++ Literal Values + + Literal ::= string | char | integer | float | boolean + InParameter ::= Literal | InputParameter + ++++ Input Parameter + + InputParameter ::= PositionalParameter | NamedParameter + PositionalParameter ::= "?" integer + NamedParameter ::= ":" string + + ++++ Arithmetic Expressions + + ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" + SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* + ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* + ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary + ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" + | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings + | FunctionsReturningDatetime | IdentificationVariable | InputParameter + + ++++ Scalar and Type Expressions + + ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression + BooleanPrimary | CaseExpression | EntityTypeExpression + CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | + CoalesceExpression | NullifExpression + GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression + "END" + WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* + "ELSE" ScalarExpression "END" + CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" + NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" + StringExpression ::= StringPrimary | "(" Subselect ")" + StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression + BooleanExpression ::= BooleanPrimary | "(" Subselect ")" + BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter + EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression + SimpleEntityExpression ::= IdentificationVariable | InputParameter + DatetimeExpression ::= DatetimePrimary | "(" Subselect ")" + DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression + + ++++ Aggregate Expressions + + AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | + "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" + ++++ Other Expressions + +QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS + + QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" + BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression + ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) + InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" + LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] + NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" + ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" + ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" + ++++ Functions + + FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime + + FunctionsReturningNumerics ::= + "LENGTH" "(" StringPrimary ")" | + "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | + "ABS" "(" SimpleArithmeticExpression ")" | "SQRT" "(" SimpleArithmeticExpression ")" | + "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + "SIZE" "(" CollectionValuedPathExpression ")" + + FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" + + FunctionsReturningStrings ::= + "CONCAT" "(" StringPrimary "," StringPrimary ")" | + "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | + "LOWER" "(" StringPrimary ")" | + "UPPER" "(" StringPrimary ")" + + + + + + + diff --git a/orm/manual/en/events.txt b/orm/manual/en/events.txt new file mode 100644 index 000000000..f8f848563 --- /dev/null +++ b/orm/manual/en/events.txt @@ -0,0 +1,304 @@ +Doctrine 2 features a lightweight event system that is part of the Common package. + +++ The Event System + +The event system is controlled by the `EventManager`. It is the central point +of Doctrine's event listener system. Listeners are registered on the manager +and events are dispatched through the manager. + + [php] + $evm = new EventManager(); + +Now we can add some event listeners to the `$evm`. Let's create a `EventTest` class +to play around with. + + [php] + class EventTest + { + const preFoo = 'preFoo'; + const postFoo = 'postFoo'; + + private $_evm; + + public $preFooInvoked = false; + public $postFooInvoked = false; + + public function __construct($evm) + { + $evm->addEventListener(array(self::preFoo, self::postFoo), $this); + } + + public function preFoo(EventArgs $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(EventArgs $e) + { + $this->postFooInvoked = true; + } + } + + // Create a new instance + $test = new EventTest($evm); + +Events can be dispatched by using the `dispatchEvent()` method. + + [php] + $evm->dispatchEvent(EventTest::preFoo); + $evm->dispatchEvent(EventTest::postFoo); + +You can easily remove a listener with the `removeEventListener()` method. + + [php] + $evm->removeEventListener(array(self::preFoo, self::postFoo), $this); + +The Doctrine 2 event system also has a simple concept of event subscribers. We +can define a simple `TestEventSubscriber` class which implements the +`\Doctrine\Common\EventSubscriber` interface and implements a `getSubscribedEvents()` +method which returns an array of events it should be subscribed to. + + [php] + class TestEventSubscriber implements \Doctrine\Common\EventSubscriber + { + const preFoo = 'preFoo'; + + public $preFooInvoked = false; + + public function preFoo() + { + $this->preFooInvoked = true; + } + + public function getSubscribedEvents() + { + return array(self::preFoo); + } + } + + $eventSubscriber = new TestEventSubscriber(); + $evm->addEventSubscriber($eventSubscriber); + +Now when you dispatch an event any event subscribers will be notified for that event. + + [php] + $evm->dispatchEvent(TestEventSubscriber::preFoo); + +Now the test the `$eventSubscriber` instance to see if the `preFoo()` method was invoked. + + [php] + if ($eventSubscriber->preFooInvoked) { + echo 'pre foo invoked!'; + } + +++ Lifecycle Events + +The EntityManager and UnitOfWork trigger a bunch of events during the life-time of their registered entities. + +* preRemove - The preRemove event occurs for a given entity before the respective EntityManager remove operation for that entity is executed. +* postRemove - The postRemove event occurs for an entity after the entity has been deleted. It will be invoked after the database delete operations. +* prePersist - The prePersist event occurs for a given entity before the respective EntityManager persist operation for that entity is executed. +* postPersist - The postPersist event occurs for an entity after the entity has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event. +* preUpdate - The preUpdate event occurs before the database update operations to entity data. +* postUpdate - The postUpdate event occurs after the database update operations to entity data. +* postLoad - The postLoad event occurs for an entity after the entity has been loaded into the current EntityManager from the database or after the refresh operation has been applied to it. +* loadClassMetadata - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (annotations/xml/yaml). +* onFlush - The onFlush event occours after the change-sets of all managed entities are computed. This event is not a lifecycle callback. + +> **NOTE** +> Note that the postLoad event occurs for an entity before any associations have been +> initialized. Therefore it is not safe to access associations in a postLoad callback +> or event handler. + +You can access the Event constants from the `Events` class in the ORM package. + + [php] + use Doctrine\ORM\Events; + echo Events::preUpdate; + +These can be hooked into by two different types of event listeners: + +* Lifecycle Callbacks are methods on the entity classes that are called when the event is triggered. They recieve absolutely no arguments and are specifically designed to allow changes inside the entity classes state. +* Lifecycle Event Listeners are classes with specific callback methods that recieves some kind of `EventArgs` instance which give access to the entity, EntityManager or other relevant data. + +> **NOTE** +> All Lifecycle events that happen during the `flush()` of an EntityManager have very specific constraints on the allowed +> operations that can be executed. Please read the *Implementing Event Listeners* section very carefully to understand +> which operations are allowed in which lifecycle event. + +++ Lifecycle Callbacks + +A lifecycle event is a regular event with the additional feature of providing +a mechanism to register direct callbacks inside the corresponding entity classes +that are executed when the lifecycle event occurs. + + [php] + + /** @Entity @HasLifecycleCallbacks */ + class User + { + // ... + + /** + * @Column(type="string", length=255) + */ + public $value; + + /** @Column(name="created_at", type="string", length=255) */ + private $createdAt; + + /** @PrePersist */ + public function doStuffOnPrePersist() + { + $this->createdAt = date('Y-m-d H:m:s'); + } + + /** @PrePersist */ + public function doOtherStuffOnPrePersist() + { + $this->value = 'changed from prePersist callback!'; + } + + /** @PostPersist */ + public function doStuffOnPostPersist() + { + $this->value = 'changed from postPersist callback!'; + } + + /** @PostLoad */ + public function doStuffOnPostLoad() + { + $this->value = 'changed from postLoad callback!'; + } + + /** @PreUpdate */ + public function doStuffOnPreUpdate() + { + $this->value = 'changed from preUpdate callback!'; + } + } + +Note that when using annotations you have to apply the @HasLifecycleCallbacks marker annotation on the entity class. + +If you want to register lifecycle callbacks from YAML or XML you can do it with +the following. + + [yml] + User: + type: entity + fields: + # ... + name: + type: string(50) + lifecycleCallbacks: + doStuffOnPrePersist: prePersist + doStuffOnPostPersist: postPersist + +XML would look something like this: + + [xml] + + + + + + + + + + + + + + + +You just need to make sure a public `doStuffOnPrePersist()` and `doStuffOnPostPersist()` method is defined on your `User` model. + + [php] + // ... + + class User + { + // ... + + public function doStuffOnPrePersist() + { + // ... + } + + public function doStuffOnPostPersist() + { + // ... + } + } + +The `key` of the lifecycleCallbacks is the name of the method and the value is +the event type. The allowed event types are the ones listed in the previous Lifecycle Events section. + +++ Listening to Lifecycle Events + +Lifecycle event listeners are much more powerful than the simple lifecycle callbacks that are defined on the entity +classes. They allow to implement re-usable behaviours between different entity classes, yet require much more detailed +knowledge about the inner workings of the EntityManager and UnitOfWork. Please read the *Implementing Event Listeners* +section carefully if you are trying to write your own listener. + +To register an event listener you have to hook it into the EventManager that is passed to the EntityManager factory: + + [php] + $eventManager = new EventManager(); + $eventManager->addEventListener(array(Events::preUpdate), MyEventListener()); + $eventManager->addEventSubscriber(new MyEventSubscriber()); + + $entityManager = EntityManager::create($dbOpts, $config, $eventManager); + +You can also retrieve the event manager instance after the EntityManager was created: + + [php] + $entityManager->getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener()); + $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); + +++ Implementing Event Listeners + +This section explains what is and what is not allowed during specific lifecycle events of the UnitOfWork. +Although you get passed the EntityManager in all of these events, you have to follow this restrictions very +carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and +lost updates/persists/removes. + ++++ prePersist + ++++ preRemove + ++++ onFlush + ++++ preUpdate + ++++ postUpdate, postRemove, postPersist + +++ Load ClassMetadata Event + +When the mapping information for an entity is read, it is populated in to a +`ClassMetadataInfo` instance. You can hook in to this process and manipulate +the instance. + + [php] + $test = new EventTest(); + $metadataFactory = $em->getMetadataFactory(); + $evm = $em->getEventManager(); + $evm->addEventListener(Events::loadClassMetadata, $test); + + class EventTest + { + public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) + { + $classMetadata = $eventArgs->getClassMetadata(); + $fieldMapping = array( + 'fieldName' => 'about', + 'type' => 'string', + 'length' => 255 + ); + $classMetadata->mapField($fieldMapping); + } + } diff --git a/orm/manual/en/improving-performance.txt b/orm/manual/en/improving-performance.txt new file mode 100644 index 000000000..644636fc9 --- /dev/null +++ b/orm/manual/en/improving-performance.txt @@ -0,0 +1,25 @@ + +++ Bytecode Cache + +It is highly recommended to make use of a bytecode cache like APC. A bytecode cache removes the need for parsing PHP code on every request and can greatly improve performance. + +> **NOTE** +> "If you care about performance and don’t use a bytecode cache then you don’t really care +> about performance. Please get one and start using it." (Stas Malyshev, Core Contributor +> to PHP and Zend Employee). + + +++ Metadata and Query caches + +As already mentioned earlier in the chapter about configuring Doctrine, it is strongly discouraged to use Doctrine without a Metadata and Query cache (preferrably with APC or Memcache as the cache driver). Operating Doctrine without these caches means Doctrine will need to load your mapping information on every single request and has to parse each DQL query on every single request. This is a waste of resources. + +++ Alternative Query Result Formats + +Make effective use of the available alternative query result formats like nested array graphs or pure scalar results, especially in scenarios where data is loaded for read-only purposes. + +++ Apply Best Practices + +A lot of the points mentioned in the Best Practices chapter will also positively affect the performance of Doctrine. + + + diff --git a/orm/manual/en/inheritance-mapping.txt b/orm/manual/en/inheritance-mapping.txt new file mode 100644 index 000000000..1b82b1d2a --- /dev/null +++ b/orm/manual/en/inheritance-mapping.txt @@ -0,0 +1,141 @@ +++ Mapped Superclasses + +An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information +for its subclasses, but which is not itself an entity. Typically, the purpose of such a mapped superclass is to define +state and mapping information that is common to multiple entity classes. + +Mapped superclasses, just as regular, non-mapped classes, can appear in the middle of an otherwise +mapped inheritance hierarchy (through Single Table Inheritance or Class Table Inheritance). + +> *NOTICE* +> +> A mapped superclass cannot be an entity, it is not queryable and persistent relationships defined by a mapped +> superclass must be unidirectional. For further support of inheritance, the single or joined table inheritance +> features have to be used. + +Example: + + [php] + /** @MappedSuperclass */ + class MappedSuperclassBase + { + /** @Column(type="integer") */ + private $mapped1; + /** @Column(type="string") */ + private $mapped2; + /** + * @OneToOne(targetEntity="MappedSuperclassRelated1") + * @JoinColumn(name="related1_id", referencedColumnName="id") + */ + private $mappedRelated1; + + // ... more fields and methods + } + + /** @Entity */ + class EntitySubClass extends MappedSuperclassBase + { + /** @Id @Column(type="integer") */ + private $id; + /** @Column(type="string") */ + private $name; + + // ... more fields and methods + } + +The DDL for the corresponding database schema would look something like this (this is for SQLite): + + [sql] + CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id)) + +As you can see from this DDL snippet, there is only a single table for the entity subclass. All the mappings from the mapped superclass were inherited to the subclass as if they had been defined on that class directly. + + +++ Single Table Inheritance + +[Single Table Inheritance](http://martinfowler.com/eaaCatalog/singleTableInheritance.html) is an inheritance mapping strategy where all classes of a hierarchy are mapped to a single database table. In order to distinguish which row represents which type in the hierarchy a so-called discriminator column is used. + +Example: + + [php] + namespace MyProject\Model; + + /** + * @Entity + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + + /** + * @Entity + */ + class Employee extends Person + { + // ... + } + +Things to note: + +* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified on the topmost class that is part of the mapped entity hierarchy. +* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of a certain type. In the case above a value of "person" identifies a row as being of type `Person` and "employee" identifies a row as being of type `Employee`. +* The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied. + ++++ Design-time considerations + +This mapping approach works well when the type hierarchy is fairly simple and stable. Adding a new type to the hierarchy and adding fields to existing supertypes simply involves adding new columns to the table, though in large deployments this may have an adverse impact on the index and column layout inside the database. + ++++ Performance impact + +This strategy is very efficient for querying across all types in the hierarchy or for specific types. No table joins are required, only a WHERE clause listing the type identifiers. In particular, relationships involving types that employ this mapping strategy are very performant. + + +++ Class Table Inheritance + +[Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html) is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. +Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hieararchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance. + +Example: + + [php] + namespace MyProject\Model; + + /** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + + /** @Entity */ + class Employee extends Person + { + // ... + } + +Things to note: + +* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified on the topmost class that is part of the mapped entity hierarchy. +* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of which type. In the case above a value of "person" identifies a row as being of type `Person` and "employee" identifies a row as being of type `Employee`. +* The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied. + +> **NOTE** +> When you do not use the SchemaTool to generate the required SQL you should know that deleting a class table inheritance +> makes use of the foreign key property `ON DELETE CASCADE` in all database implementations. A failure to implement this +> yourself will lead to dead rows in the database. + ++++ Design-time considerations + +Introducing a new type to the hierarchy, at any level, simply involves interjecting a new table into the schema. Subtypes of that type will automatically join with that new type at runtime. Similarly, modifying any entity type in the hierarchy by adding, modifying or removing fields affects only the immediate table mapped to that type. This mapping strategy provides the greatest flexibility at design time, since changes to any type are always limited to that type's dedicated table. + ++++ Performance impact + +This strategy inherently requires multiple JOIN operations to perform just about any query which can have a negative impact on performance, especially with large tables and/or large hierarchies. When partial objects are allowed, either globally or on the specific query, then querying for any type will not cause the tables of subtypes to be OUTER JOINed which can increase performance but the resulting partial objects will not fully load themselves on access of any subtype fields, so accessing fields of subtypes after such a query is not safe. \ No newline at end of file diff --git a/orm/manual/en/introduction.txt b/orm/manual/en/introduction.txt new file mode 100644 index 000000000..96f627d6a --- /dev/null +++ b/orm/manual/en/introduction.txt @@ -0,0 +1,222 @@ +++ Welcome + +Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides +transparent persistence for PHP objects. It sits on top of a powerful database +abstraction layer (DBAL). One of its key features is the option to write +database queries in a proprietary object oriented SQL dialect called Doctrine +Query Language (DQL), inspired by Hibernates HQL. This provides developers with +a powerful alternative to SQL that maintains flexibility without requiring +unnecessary code duplication. + +++ Disclaimer + +This is the Doctrine 2 reference documentation. Introductory guides and tutorials that you can follow along from start to finish, like the "Guide to Doctrine" book known from the Doctrine 1.x series, will be available at a later date. + +++ Requirements + +Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved performance it +is also recommended that you use APC with PHP. + +++ Doctrine 2 Packages + +Doctrine 2 is divided into three main packages. + +* Common +* DBAL (includes Common) +* ORM (includes DBAL+Common) + +This manual mainly covers the ORM package, sometimes touching parts of the +underlying DBAL and Common packages. The Doctrine code base is split in to these +packages for a few reasons and they are to... + +* ...make things more maintainable and decoupled +* ...allow you to use the code in Doctrine Common without the ORM or DBAL +* ...allow you to use the DBAL without the ORM + ++++ The Common Package + +The Common package contains highly reusable components that have no dependencies +beyond the package itself (and PHP, of course). The root namespace of the +Common package is `Doctrine\Common`. + ++++ The DBAL Package + +The DBAL package contains an enhanced database abstraction layer on top of +PDO but is not strongly bound to PDO. The purpose of this layer is to provide a +single API that bridges most of the differences between the different RDBMS vendors. +The root namespace of the DBAL package is `Doctrine\DBAL`. + ++++ The ORM Package + +The ORM package contains the object-relational mapping toolkit that provides +transparent relational persistence for plain PHP objects. The root namespace of +the ORM package is `Doctrine\ORM`. + +++ Installing + +Doctrine can be installed many different ways. We will describe all the different +ways and you can choose which one suits you best. + ++++ PEAR + +You can easily install any of the three Doctrine packages from the PEAR command +line installation utility. + +To install just the `Common` package you can run the following command: + + $ sudo pear install pear.phpdoctrine.org/DoctrineCommon-2.0.0 + +If you want to use the Doctrine Database Abstraction Layer you can install it +with the following command. + + $ sudo pear install pear.phpdoctrine.org/DoctrineDBAL-2.0.0 + +Or, if you want to get the works and go for the ORM you can install it with the +following command. + + $ sudo pear install pear.phpdoctrine.org/DoctrineORM-2.0.0 + +When you have a package installed via PEAR you can required and load the +`ClassLoader` with the following code. + + [php] + require 'Doctrine/Common/ClassLoader.php'; + $classLoader = new \Doctrine\Common\ClassLoader(); + +The packages are installed in to your shared PEAR PHP code folder in a folder +named `Doctrine`. You also get a nice command line utility installed and made +available on your system. Now when you run the `doctrine` command you will +see what you can do with it. + + $ doctrine + Doctrine Command Line Interface + Available Tasks: + core:help + dbal:run-sql (--file= | --sql=) --depth= + orm:clear-cache (--query | --metadata | --result [--id=] [--regex=] [--prefix=] [--suffix=]) + orm:convert-mapping (--from= | --from-database) --to= --dest= + orm:ensure-production-settings + orm:generate-proxies --class-dir= [--to-dir=] + orm:run-dql --dql= --depth= + orm:schema-tool (--create | --drop | --update | --complete-update | --re-create) [--dump-sql] [--class-dir=] + orm:version + ++++ Package Download + +You can also use Doctrine 2 by downloading the latest release package +from [the download page](http://www.doctrine-project.org/download). + ++++ Subversion + +Alternatively you can check out the latest version of Doctrine 2 via SVN. + + $ svn co http://svn.doctrine-project.org/trunk doctrine + +++ Sandbox Quickstart + +> **NOTE** +> The sandbox is only available via SVN or soon as a separate download on the downloads +> page. + +The sandbox is a pre-configured environment for evaluating and playing +with Doctrine 2. + ++++ Overview + +After navigating to the sandbox directory, you should see the following structure: + + sandbox/ + Entities/ + Address.php + User.php + xml/ + Entities.Address.dcm.xml + Entities.User.dcm.xml + yaml/ + Entities.Address.dcm.yml + Entities.User.dcm.yml + cli-config.php + doctrine + doctrine.php + index.php + +Here is a short overview of the purpose of these folders and files: + + * The `Entities` folder is where any model classes are created. Two example entities are already there. + * The `xml` folder is where any XML mapping files are created (if you want to use XML mapping). Two example mapping documents for the 2 example entities are already there. + * The `yaml` folder is where any YAML mapping files are created (if you want to use YAML mapping). Two example mapping documents for the 2 example entities are already there. + * The `cli-config.php` contains bootstrap code for a configuration that is used by the CLI tool `doctrine` whenever you execute a task. + * `doctrine`/`doctrine.php` is a command-line tool. + * `index.php` is a basic classical bootstrap file of a php application that uses Doctrine 2. + ++++ Mini-tutorial + +1) From within the tools/sandbox folder, run the following command and you should +see the same output. + + $ php doctrine orm:schema-tool --create + Creating database schema... + Database schema created successfully. + +2) Take another look into the tools/sandbox folder. A SQLite database should +have been created with the name `database.sqlite`. + +3) Open `index.php` and edit it so that it looks as follows: + + [php] + //... bootstrap stuff + + ## PUT YOUR TEST CODE BELOW + + $user = new \Entities\User; + $user->setName('Garfield'); + $em->persist($user); + $em->flush(); + + echo "User saved!"; + +Open index.php in your browser or execute it on the command line. You should see +the output "User saved!". + +5) Inspect the SQLite database. Again from within the tools/sandbox folder, +execute the following command: + + $ php doctrine dbal:run-sql --sql="select * from users" + +You should get the following output: + + array(1) { + [0]=> + array(2) { + ["id"]=> + string(1) "1" + ["name"]=> + string(8) "Garfield" + } + } + +You just saved your first entity with a generated ID in an SQLite database. + +6) Replace the contents of index.php with the following: + + [php] + //... bootstrap stuff + + ## PUT YOUR TEST CODE BELOW + + $q = $em->createQuery('select u from Entities\User u where u.name = ?1'); + $q->setParameter(1, 'Garfield'); + $garfield = $q->getSingleResult(); + + echo "Hello " . $garfield->getName() . "!"; + +You just created your first DQL query to retrieve the user with the name +'Garfield' from an SQLite database (Yes, there is an easier way to do it, +but we wanted to introduce you to DQL at this point. Can you **find** the easier way?). + +> **TIP** +> When you create new model classes or alter existing ones you can recreate the database +> schema with the command `doctrine orm:schema-tool --drop` followed by +> `doctrine orm:schema-tool --create`. + +7) Explore Doctrine 2! diff --git a/orm/manual/en/native-sql.txt b/orm/manual/en/native-sql.txt new file mode 100644 index 000000000..fb6359737 --- /dev/null +++ b/orm/manual/en/native-sql.txt @@ -0,0 +1,196 @@ +A `NativeQuery` lets you execute native SQL, mapping the results according to your specifications. Such a specification that describes how an SQL result set is mapped to a Doctrine result is represented by a `ResultSetMapping`. + +++ The NativeQuery class + +To create a `NativeQuery` you use the method `EntityManager#createNativeQuery($sql, $resultSetMapping)`. As you can see in the signature of this method, it expects 2 ingredients: The SQL you want to execute and the `ResultSetMapping` that describes how the results will be mapped. + +Once you obtained an instance of a `NativeQuery`, you can bind parameters to it and finally execute it. + +++ The ResultSetMapping + +Understanding the `ResultSetMapping` is the key to using a `NativeQuery`. +A Doctrine result can contain the following components: + + * Entity results. These represent root result elements. + * Joined entity results. These represent joined entities in associations of root entity results. + * Field results. These represent a column in the result set that maps to a field of an entity. A field result always belongs to an entity result or joined entity result. + * Scalar results. These represent scalar values in the result set that will appear in each result row. Adding scalar results to a ResultSetMapping can also cause the overall result to becomed **mixed** (see DQL - Doctrine Query Language) if the same ResultSetMapping also contains entity results. + * Meta results. These represent columns that contain meta-information, such as foreign keys and discriminator columns. + When querying for objects (`getResult()`), all meta columns of root entities or joined entities must be present in the SQL query + and mapped accordingly using `ResultSetMapping#addMetaResult`. + +> **TIP** +> It might not surprise you that Doctrine uses `ResultSetMapping`s internally when you +> create DQL queries. As the query gets parsed and transformed to SQL, Doctrine fills +> a `ResultSetMapping` that describes how the results should be processed by the hydration +> routines. + +We will now look at each of the result types that can appear in a ResultSetMapping in detail. + ++++ Entity results + +An entity result describes an entity type that appears as a root element in the transformed result. You add an entity result through `ResultSetMapping#addEntityResult()`. +Let's take a look at the method signature in detail: + + [php] + /** + * Adds an entity result to this ResultSetMapping. + * + * @param string $class The class name of the entity. + * @param string $alias The alias for the class. The alias must be unique among all entity + * results or joined entity results within this ResultSetMapping. + */ + public function addEntityResult($class, $alias) + +The first parameter is the fully qualified name of the entity class. The second parameter is some arbitrary alias for this entity result that must be unique within a `ResultSetMapping`. You use this alias to attach field results to the entity result. It is very similar to an identification variable that you use in DQL to alias classes or relationships. + +An entity result alone is not enough to form a valid `ResultSetMapping`. An entity result or joined entity result always needs a set of field results, which we will look at soon. + ++++ Joined entity results + +A joined entity result describes an entity type that appears as a joined relationship element in the transformed result, attached to a (root) entity result. You add a joined entity result through `ResultSetMapping#addJoinedEntityResult()`. Let's take a look at the method signature in detail: + + [php] + /** + * Adds a joined entity result. + * + * @param string $class The class name of the joined entity. + * @param string $alias The unique alias to use for the joined entity. + * @param string $parentAlias The alias of the entity result that is the parent of this joined result. + * @param object $relation The association field that connects the parent entity result with the joined entity result. + */ + public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) + +The first parameter is the class name of the joined entity. The second parameter is an arbitrary alias for the joined entity that must be unique within the `ResultSetMapping`. +You use this alias to attach field results to the entity result. The third parameter is the alias of the entity result that is the parent type of the joined relationship. The fourth and last parameter is the name of the field on the parent entity result that should contain the joined entity result. + + ++++ Field results + +A field result describes the mapping of a single column in an SQL result set to a field in an entity. As such, field results are inherently bound to entity results. You add a field result through `ResultSetMapping#addFieldResult()`. Again, let's examine the method signature in detail: + + [php] + /** + * Adds a field result that is part of an entity result or joined entity result. + * + * @param string $alias The alias of the entity result or joined entity result. + * @param string $columnName The name of the column in the SQL result set. + * @param string $fieldName The name of the field on the (joined) entity. + */ + public function addFieldResult($alias, $columnName, $fieldName) + +The first parameter is the alias of the entity result to which the field result will belong. The second parameter is the name of the column in the SQL result set. Note that this name is case sensitive, i.e. if you use a native query against Oracle it must be all uppercase. The third parameter is the name of the field on the entity result identified by `$alias` into which the value of the column should be set. + ++++ Scalar results + +A scalar result describes the mapping of a single column in an SQL result set to a scalar value in the Doctrine result. Scalar results are typically used for aggregate values but any column in the SQL result set can be mapped as a scalar value. To add a scalar result use `ResultSetMapping#addScalarResult()`. The method signature in detail: + + [php] + /** + * Adds a scalar result mapping. + * + * @param string $columnName The name of the column in the SQL result set. + * @param string $alias The result alias with which the scalar result should be placed in the result structure. + */ + public function addScalarResult($columnName, $alias) + +The first parameter is the name of the column in the SQL result set and the second parameter is the result alias under which the value of the column will be placed in the transformed Doctrine result. + ++++ Meta results + +A meta result describes a single column in an SQL result set that is either a foreign key or a discriminator column. +These columns are essential for Doctrine to properly construct objects out of SQL result sets. +To add a column as a meta result use `ResultSetMapping#addMetaResult()`. The method signature in detail: + + [php] + /** + * Adds a meta column (foreign key or discriminator column) to the result set. + * + * @param string $alias + * @param string $columnAlias + * @param string $columnName + */ + public function addMetaResult($alias, $columnAlias, $columnName) + +The first parameter is the alias of the entity result to which the meta column belongs. +A meta result column (foreign key or discriminator column) always belongs to to an entity result. +The second parameter is the column alias/name of the column in the SQL result set and the third parameter is the +column name used in the mapping. + + ++++ Examples + +Understanding a ResultSetMapping is probably easiest through looking at some examples. + +First a basic example that describes the mapping of a single entity. + + [php] + // Equivalent DQL query: "select u from User u where u.name=?1" + // User owns no associations. + $rsm = new ResultSetMapping; + $rsm->addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + + $query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +The result would look like this: + + array( + [0] => User (Object) + ) + +Note that this would be a partial object if the entity has more fields than just id and name. In the example above the column and field names are identical but that is not necessary, of course. Also note that the query string passed to createNativeQuery is **real native SQL**. Doctrine does not touch this SQL in any way. + +In the previous basic example, a User had no relations and the table the class is mapped to owns no foreign keys. +The next an example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress, +where the User is the owning side and thus owns the foreign key. + + [php] + // Equivalent DQL query: "select u from User u where u.name=?1" + // User owns an association to an Address but the Address is not loaded in the query. + $rsm = new ResultSetMapping; + $rsm->addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addMetaResult('u', 'address_id', 'address_id'); + + $query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +Foreign keys are used by Doctrine for lazy-loading purposes when querying for objects. +In the previous example, each user object in the result will have a proxy (a "ghost") in place +of the address that contains the address_id. When the ghost proxy is accessed, it loads itself +based on this key. + +Consequently, associations that are *fetch-joined* do not require the foreign keys to be present +in the SQL result set, only associations that are lazy. + +If a fetched entity is part of a mapped hierarchy that requires a discriminator column, this +column must be present in the result set as a meta column so that Doctrine can create the +appropriate concrete type. This is shown in the following example where we assume that there +are one or more subclasses that extend User and either Class Table Inheritance or Single Table Inheritance +is used to map the hierarchy (both use a discriminator column). + + [php] + // Equivalent DQL query: "select u from User u where u.name=?1" + // User is a mapped base class for other classes. User owns no associations. + $rsm = new ResultSetMapping; + $rsm->addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column + + $query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +Note that in the case of Class Table Inheritance, an example as above would result in partial objects +if any objects in the result are actually a subtype of User. When using DQL, Doctrine automatically +includes the necessary joins for this mapping strategy but with native SQL it is your responsibility. \ No newline at end of file diff --git a/orm/manual/en/query-builder.txt b/orm/manual/en/query-builder.txt new file mode 100644 index 000000000..1080d4601 --- /dev/null +++ b/orm/manual/en/query-builder.txt @@ -0,0 +1,356 @@ + +++ The QueryBuilder + +A `QueryBuilder` provides an API that is designed for conditionally constructing a DQL query in several steps. + +It provides a set of classes and methods that is able to programatically build you queries, and also provides a fluent API. +This means that you can change between one methodology to the other as you want, and also pick one if you prefer. + ++++ Constructing a new QueryBuilder object + +The same way you build a normal Query, you build a `QueryBuilder` object, just providing the correct method name. +Here is an example how to build a `QueryBuilder` object: + + [php] + // $em instanceof EntityManager + + // example1: creating a QueryBuilder instance + $qb = $em->createQueryBuilder(); + +Once you created an instance of QueryBuilder, it provides a set of useful informative functions that you can use. +One good example is to inspect what type of object the `QueryBuilder` is. + + [php] + // $qb instanceof QueryBuilder + + // example2: retrieving type of QueryBuilder + echo $qb->getType(); // Prints: 0 + +There're currently 3 possible return values for `getType()`: + +* `QueryBuilder::SELECT`, which returns value 0 +* `QueryBuilder::DELETE`, returning value 1 +* `QueryBuilder::UPDATE`, which returns value 2 + +It is possible to retrieve the associated `EntityManager` of the current `QueryBuilder`, its DQL and also a `Query` object when you finish building your DQL. + + [php] + // $qb instanceof QueryBuilder + + // example3: retrieve the associated EntityManager + $em = $qb->getEntityManager(); + + // example4: retrieve the DQL string of what was defined in QueryBuilder + $dql = $qb->getDql(); + + // example5: retrieve the associated Query object with the processed DQL + $q = $qb->getQuery(); + +Internally, `QueryBuilder` works with a DQL cache, which prevents multiple processment if called multiple times. Any changes that may affect the generated DQL actually modifies the state of `QueryBuilder` to a stage we call as STATE_DIRTY. +One `QueryBuilder`can be in two different state: + +* `QueryBuilder::STATE_CLEAN`, which means DQL haven't been altered since last retrieval or nothing were added since its instantiation +* `QueryBuilder::STATE_DIRTY`, means DQL query must (and will) be processed on next retrieval + ++++ Working with QueryBuilder + +All helper methods in `QueryBuilder` relies actually on a single one: `add()`. +This method is the responsable to build every piece of DQL. It takes 3 parameters: `$dqlPartName`, `$dqlPart` and `$append` (default=false) + +* `$dqlPartName`: Where the `$dqlPart` should be placed. Possible values: select, from, where, groupBy, having, orderBy +* `$dqlPart`: What should be placed in `$dqlPartName`. Accepts a string or any instance of `Doctrine\ORM\Query\Expr\*` +* `$append`: Optional flag (default=false) if the `$dqlPart` should override all previously defined items in `$dqlPartName` or not + +- + + [php] + // $qb instanceof QueryBuilder + + // example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support + $qb->add('select', 'u') + ->add('from', 'User u') + ->add('where', 'u.id = ?1') + ->add('orderBy', 'u.name ASC'); + +++++ Expr\* classes + +When you call `add()` with string, it internally evaluates to an instance of `Doctrine\ORM\Query\Expr\Expr\*` class. +Here is the same query of example 6 written using `Doctrine\ORM\Query\Expr\Expr\*` classes: + + [php] + // $qb instanceof QueryBuilder + + // example7: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder using Expr\* instances + $qb->add('select', new Expr\Select(array('u'))) + ->add('from', new Expr\From('User', 'u')) + ->add('where', new Expr\Comparison('u.id', '=', '?1')) + ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); + +Of course this is the hardest way to build a DQL query in Doctrine. To simplify some of these efforts, we introduce what we call as `Expr` helper class. + +++++ The Expr class + +To workaround most of the issues that `add()` method may cause, Doctrine created a class that can be considered as a helper for building queries. +This class is called `Expr`, which provides a set of useful static methods to help building queries: + + [php] + // $qb instanceof QueryBuilder + + // example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.surname DESC" using Expr class + $qb->add('select', $qb->expr()->select('u')) + ->add('from', $qb->expr()->from('User', 'u')) + ->add('where', $qb->expr()->orx( + $qb->expr()->eq('u.id', '?1'), + $qb->expr()->like('u.nickname', '?2') + )) + ->add('orderBy', $qb->expr()->orderBy('u.surname', 'ASC')); + +Although it still sounds complex, the ability to programatically create conditions are the main feature of `Expr`. +Here it is a complete list of supported helper methods available: + + [php] + class Expr + { + /** Base objects **/ + + // Example usage - $qb->expr()->select('u') + public function select($select = null); // Returns Expr\Select instance + + // Example - $qb->expr()->from('User', 'u') + public function from($from, $alias); // Returns Expr\From instance + + // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', Expr\Join::ON, 'p.user_id = u.id AND p.country_code = 55'); + // Example - $qb->expr()->leftJoin('u. Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); + public function leftJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance + + // Example - $qb->expr()->innerJoin('u.Group', 'g', Expr\Join::WITH, 'g.manager_level = 100'); + // Example - $qb->expr()->innerJoin('u.Group', 'g', 'WITH', $qb->expr()->eq('g.manager_level', '100')); + public function innerJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance + + // Example - $qb->expr()->orderBy('u.surname', 'ASC')->add('u.firstname', 'ASC')->... + public function orderBy($sort = null, $order = null); // Returns Expr\OrderBy instance + + // Example - $qb->expr()->groupBy()->add('u.id')->... + public function groupBy($groupBy = null); // Returns Expr\GroupBy instance + + + /** Conditional objects **/ + + // Example - $qb->expr()->andx($cond1 [, $condN])->add(...)->... + public function andx($x = null); // Returns Expr\Andx instance + + // Example - $qb->expr()->orx($cond1 [, $condN])->add(...)->... + public function orx($x = null); // Returns Expr\Orx instance + + + /** Comparison objects **/ + + // Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1 + public function eq($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1 + public function neq($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1 + public function lt($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1 + public function lte($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1 + public function gt($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1 + public function gte($x, $y); // Returns Expr\Comparison instance + + + /** Arithmetic objects **/ + + // Example - $qb->expr()->prod('u.id', '2') => u.id * 2 + public function prod($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->diff('u.id', '2') => u.id - 2 + public function diff($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->sum('u.id', '2') => u.id + 2 + public function sum($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->quot('u.id', '2') => u.id / 2 + public function quot($x, $y); // Returns Expr\Math instance + + + /** Pseudo-function objects **/ + + // Example - $qb->expr()->exists($qb2->getDql()) + public function exists($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->all($qb2->getDql()) + public function all($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->some($qb2->getDql()) + public function some($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->any($qb2->getDql()) + public function any($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1')) + public function not($restriction); // Returns Expr\Func instance + + // Example - $qb->expr()->in('u.id', array(1, 2, 3)) + public function in($x, $y); // Returns Expr\Func instance + + // Example - $qb->expr()->notIn('u.id', '2') + public function notIn($x, $y); // Returns Expr\Func instance + + // Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%')) + public function like($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->between('u.id', '1', '10') + public function between($val, $x, $y); // Returns Expr\Func + + + /** Function objects **/ + + // Example - $qb->expr()->trim('u.firstname') + public function trim($x); // Returns Expr\Func + + // Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat(' ', 'u.lastname')) + public function concat($x, $y); // Returns Expr\Func + + // Example - $qb->expr()->substr('u.firstname', 0, 1) + public function substr($x, $from, $len); // Returns Expr\Func + + // Example - $qb->expr()->lower('u.firstname') + public function lower($x); // Returns Expr\Func + + // Example - $qb->expr()->upper('u.firstname') + public function upper($x); // Returns Expr\Func + + // Example - $qb->expr()->length('u.firstname') + public function length($x); // Returns Expr\Func + + // Example - $qb->expr()->avg('u.age') + public function avg($x); // Returns Expr\Func + + // Example - $qb->expr()->max('u.age') + public function max($x); // Returns Expr\Func + + // Example - $qb->expr()->min('u.age') + public function min($x); // Returns Expr\Func + + // Example - $qb->expr()->abs('u.currentBalance') + public function abs($x); // Returns Expr\Func + + // Example - $qb->expr()->sqrt('u.currentBalance') + public function sqrt($x); // Returns Expr\Func + + // Example - $qb->expr()->count('u.firstname') + public function count($x); // Returns Expr\Func + + // Example - $qb->expr()->countDistinct('u.surname') + public function countDistinct($x); // Returns Expr\Func + } + +++++ Helper methods + +Until now it was described the hardcore level of creating queries. It may be useful to work that way for optimization purposes, but most of the time it is preferred to work higher level. +To simplify even more the way you build a query in Doctrine, we can take advantage of what we call as helper methods. For all base code, it has a set of useful methods to simplify programmer's life. +Illustrating how to work with it, here is the same example 6 written now using `QueryBuilder` helper methods: + + [php] + // $qb instanceof QueryBuilder + + // example9: how to define: "SELECT u FROM User u WHERE u.id = ?1 ORDER BY u.name ASC" using QueryBuilder helper methods + $qb->select('u') + ->from('User', 'u') + ->where('u.id = ?1') + ->orderBy('u.name ASC'); + +`QueryBuilder` helper methods are considered the standard way to build DQL queries. Although it is supported, it should be avoided to use string based queries and greatly encouraged to use `$qb->expr()->*` methods. +Here is a converted example 8 to suggested standard way to build queries: + + [php] + // $qb instanceof QueryBuilder + + // example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ?1 OR u.nickname LIKE ?2 ORDER BY u.surname DESC" using QueryBuilder helper methods + $qb->select(array('u')) // string 'u' is converted to array internally + ->from('User', 'u') + ->where($qb->expr()->orx( + $qb->expr()->eq('u.id', '?1'), + $qb->expr()->like('u.nickname', '?2') + )) + ->orderBy('u.surname', 'ASC')); + +Here is a complete list of helper methods in `QueryBuilder`: + + [php] + class QueryBuilder + { + // Example - $qb->select('u') + // Example - $qb->select(array('u', 'p')) + // Example - $qb->select($qb->expr()->select('u', 'p')) + public function select($select = null); + + // Example - $qb->delete('User', 'u') + public function delete($delete = null, $alias = null); + + // Example - $qb->update('Group', 'g') + public function update($update = null, $alias = null); + + // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold')) + // Example - $qb->set('u.numChilds', 'u.numChilds + ?1') + // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1')) + public function set($key, $value); + + // Example - $qb->from('Phonenumber', 'p') + public function from($from, $alias = null); + + // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::ON, $qb->expr()->and($qb->expr()->eq('u.group_id', 'g.id'), 'g.name = ?1')) + // Example - $qb->innerJoin('u.Group', 'g', 'ON', 'u.group_id = g.id AND g.name = ?1') + public function innerJoin($join, $alias = null, $conditionType = null, $condition = null); + + // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55)) + // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55') + public function leftJoin($join, $alias = null, $conditionType = null, $condition = null); + + // NOTE: ->where() overrides all previously set conditions + // + // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2')) + // Example - $qb->where($qb->expr()->andx($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2'))) + // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2') + public function where($where); + + // Example - $qb->andWhere($qb->expr()->orx($qb->expr()->lte('u.age', 40), 'u.numChild = 0')) + public function andWhere($where); + + // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10)); + public function orWhere($where); + + // NOTE: -> groupBy() overrides all previously set grouping items + // + // Example - $qb->groupBy('u.id') + public function groupBy($groupBy); + + // Example - $qb->addGroupBy('g.name') + public function addGroupBy($groupBy); + + // NOTE: -> having() overrides all previously set having conditions + // + // Example - $qb->having('u.salary >= ?1') + // Example - $qb->having($qb->expr()->gte('u.salary', '?1')) + public function having($having); + + // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0)) + public function andHaving($having); + + // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100')) + public function orHaving($having); + + // NOTE: -> orderBy() overrides all previously set ordering items + // + // Example - $qb->orderBy('u.surname', 'DESC') + public function orderBy($sort, $order = null); + + // Example - $qb->addOrderBy('u.firstName') + public function addOrderBy($sort, $order = null); // Default $order = 'ASC' + } \ No newline at end of file diff --git a/orm/manual/en/tools.txt b/orm/manual/en/tools.txt new file mode 100644 index 000000000..d97136c69 --- /dev/null +++ b/orm/manual/en/tools.txt @@ -0,0 +1,192 @@ +++ The Doctrine CLI + +The Doctrine CLI (Command Line Interface) is a tool for simplifying many common tasks during the development of a project that uses Doctrine. + ++++ Installation + +If you installed Doctrine 2 through PEAR, the `doctrine` command line tool should already be available to you. + +If you use Doctrine through SVN or a release package you need to copy the `doctrine` and `doctrine.php` files from the `tools/sandbox` or `bin` folder, respectively, to a location of your choice, for example a `tools` folder of your project. +In addition you may need to edit `doctrine.php` and adjust some paths to the new environment. You may want to add require_once() statement at the top of doctrine.php to set up the include_path for Doctrine classes. + ++++ Getting Help + +Type `doctrine` on the command line and you should see an overview of the available tasks or use the --help flag to get information on the available tasks. If you want to know more about the use of the schema tool for example you can call: + + doctrine orm:schema-tool --help + ++++ Configuration + +Whenever the `doctrine` command line tool is invoked it requires an instance of `Doctrine\Common\Cli\CliConfiguration` to be able to correctly work. When using ORM package, it is required to define an attribute inside Configuration: `em`. `em` must be an `EntityManager` instance that is used by ORM command-line tasks. + +Many tasks of the Doctrine CLI require the `em` attribute to be an `EntityManager` in order to execute. The `EntityManager` instance implicitly defines a database connection. +If you invoke a task that requires an EntityManager (and/or a database connection) and the `em` attribute is not defined in your CLI Configuration instance, the task invoking will report an error for you. + +CLI COnfiguration instance can be in a separate file (ie. `cli-config.php`) that contains typical Doctrine bootstrap code and predefines the needed attributes mentioned above. A typical `cli-config.php` file looks as follows: + + [php] + require_once '/path/to/lib/Doctrine/Common/ClassLoader.php'; + + $classLoader = new \Doctrine\Common\ClassLoader('MyProject', '/path/to/myproject/lib'); + $classLoader->register(); + + $ormConfig = new \Doctrine\ORM\Configuration(); + $ormConfig->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); + $ormConfig->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); + $ormConfig->setProxyNamespace('MyProject\Proxies'); + + $connectionOptions = array( + 'driver' => 'pdo_sqlite', + 'path' => 'database.sqlite' + ); + + $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $ormConfig); + + $cliConfig = new \Doctrine\Common\Cli\Configuration(); + $cliConfig->setAttribute('em', $em); + $cliConfig->setAttribute('globalArguments', array( + 'class-dir' => '/path/to/myproject/lib/MyProject/Models/' + )); + +It is important to define an instance of Doctrine\Common\Cli\Configuration that the doctrine.php script will ultimately use. For the required name of this variable, check the doctrine.php included in your package; the most new ones will automatically find the variable no matter what its name is, as long as it is an instance of the right class. +The CLI Configuration should contain at least the 'em' attribute, set to the EntityManager. To use many tasks a 'globalOptions' array should be set. +The `globalArguments` content will be passed to every command line task. For instance, to use the orm:schema-tool task to generate database tables from annotations, the class-dir global option must be set as in the example. + ++++ Task Overview + +The following tasks are currently available: + + * `dbal:run-sql`: Used to run arbitrary SQL on the command-line. + * `orm:convert-mapping`: Used to convert between annotations/xml/yaml mapping informations as well as for class generating from xml/yaml mapping documents or for reverse engineering. + * `orm:generate-proxies`: Used to generate proxy classes used by Doctrine. + * `orm:run-dql`: Used to run arbitrary DQL on the command-line. + * `orm:schema-tool`: Used to forward-engineer the relational database schema from existing classes and mappings. + * `orm:version`: Used to show the current version of the CLI and Doctrine. + +++ Database Schema Generation + +To generate your database schema from your Doctrine mapping files you can use the +`SchemaTool` class or the `schema-tool` CLI task. + +When using the SchemaTool class directly, create your schema using the `createSchema()` method. First create an instance of the `SchemaTool` and pass it an instance of the `EntityManager` that you want to use to create the schema. This method receives an array of `ClassMetadataInfo` instances. + + [php] + $tool = new \Doctrine\ORM\Tools\SchemaTool($em); + $classes = array( + $em->getClassMetadata('Entities\User'), + $em->getClassMetadata('Entities\Profile') + ); + $tool->createSchema($classes); + +To drop the schema you can use the `dropSchema()` method. + + [php] + $tool->dropSchema($classes); + +This drops all the tables that are currently used by your metadata model. +When you are changing your metadata alot during development you might want +to drop the complete database instead of only the tables of the current model +to clean up with orphaned tables. + + [php] + $tool->dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE); + +You can also use database introspection to update your schema easily with the +`updateSchema()` method. It will compare your existing database schema to the +passed array of `ClassMetdataInfo` instances. + + [php] + $tool->updateSchema($classes); + +If you want to use this functionality from the command line you can use the +`schema-tool` task. + +To create the schema use the `--create` option: + + $ php doctrine orm:schema-tool --create + +To drop the scheme use the `--drop` option: + + $ php doctrine orm:schema-tool --drop + +If you want to drop and then recreate the schema then use both options: + + $ php doctrine orm:schema-tool --drop --create + +As you would think, if you want to update your schema use the `--update` option: + + $ php doctrine orm:schema-tool --update + +All of the above tasks also accept a `--dump-sql` option that will output the SQL +for the ran operation. + + $ php doctrine orm:schema-tool --create --dump-sql + +Before using the orm:schema-tool task, remember to configure your cli-config.php properly. + +++ Convert Mapping Information + +Doctrine comes with some special tools for working with the various supported +formats for specifying mapping information. + +You have the ability to convert from a few different sources. + +* An existing database +* A directory of YAML schema files +* A directory of XML schema files +* A directory of PHP scripts which populate `ClassMetadataInfo` instances +* A directory of PHP classes defining Doctrine entities with annotations + +To convert a mapping source you can do everything you need with the `ClassMetadataExporter`. + + [php] + $cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter(); + +Once you have an instance you can start adding mapping sources to convert. + + [php] + $cme->addMappingSource('/path/to/yml', 'yml'); + $cme->addMappingSource('/path/to/xml', 'xml'); + $cme->addMappingSource('/path/to/php', 'php'); + $cme->addMappingSource('/path/to/annotations', 'annotation'); + +Now to convert the added mapping sources you can do so by using the exporter drivers. + + [php] + $metadatas = $cme->getMetadatasForMappingSources(); + + $exporter = $cme->getExporter('yml', '/path/to/export/yml'); + $exporter->setMetadatas($metadatas); + $exporter->export(); + +This functionality functionality is also available from the command line to for +example convert some YAML mapping files to XML. + + $ php doctrine orm:convert-mapping --from=/path/to/yml --to=xml --dest=/path/to/xml + +++ Reverse Engineering + +You can use the same `ClassMetadataExporter` to reverse engineer a database and +generate YAML, XML, etc. from your existing databases. + + [php] + $sm = $em->getConnection()->getSchemaManager(); + + $cme->addMappingSource($sm, 'database'); + $metadatas = $cme->getMetadatasForMappingSources(); + + $exporter = $cme->getExporter('yml', '/path/to/export/yml'); + $exporter->setMetadatas($metadatas); + $exporter->export(); + +From the command line it is very simple to do something like reverse engineer +your existing database to set of YAML mapping files. + + $ php doctrine orm:convert-mapping --from=database --to=yml --dest=/path/to/yml + +> **CAUTION** +> Reverse Engineering is not always working perfectly depending on special cases. +> It will only detect Many-To-One relations (even if they are One-To-One) and +> will try to create entities from Many-To-Many tables. It also has problems +> with naming of foreign keys that have multiple column names. Any Reverse Engineered +> Database-Schema needs considerable manual work to become a useful domain model. diff --git a/orm/manual/en/transactions-and-concurrency.txt b/orm/manual/en/transactions-and-concurrency.txt new file mode 100644 index 000000000..302efd37e --- /dev/null +++ b/orm/manual/en/transactions-and-concurrency.txt @@ -0,0 +1,124 @@ +++ Transaction Demarcation + +Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very important because if not done properly it can have a negative effect on the performance of your application. Many databases and database abstraction layers like PDO by default operate in auto-commit mode, which means that every single SQL statement is wrapped in a small transaction that is immediately committed. Without any explicit transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap and many small transactions degrade the performance of your application. + +For the most part, Doctrine 2 already takes care of proper transaction demarcation for you: All the write operations (INSERT/UPDATE/DELETE) are queued until `EntityManager#flush()` is invoked which wraps all of these changes in a single, small transaction. This is a strategy called "transactional write-behind" that is frequently used in ORM solutions to increase efficiency. + +However, Doctrine 2 also allows you to take over and control transaction demarcation yourself, thereby "widening" the transaction boundaries. This is possible due to transparent nesting of transactions that is described in the following section. + +++ Transaction Nesting + +Each `Doctrine\DBAL\Driver\Connection` instance is wrapped in a `Doctrine\DBAL\Connection` that adds support for transparent nesting of transactions. For that purpose, the Connection class keeps an internal counter that represents the nesting level and is increased/decreased as beginTransaction(), commit() and rollback() are invoked. beginTransaction() increases the nesting level whilst commit() and rollback() decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1, beginTransaction() is invoked on the underlying driver and whenever the nesting level transitions from 1 to 0, commit() or rollback() is invoked on the underlying driver, depending on whether the transition was caused by `Connection#commit()` or `Connection#rollback()`. + +Lets visualize what that means in practice. It means that the first call to `Doctrine\DBAL\Connection#beginTransaction()` will increase the nesting level from 0 to 1 and invoke beginTransaction() on the underlying driver, effectively starting a "real" transaction by suspending auto-commit mode. Any subsequent, nested calls to `Doctrine\DBAL\Connection#beginTransaction()` would only increase the nesting level. + +Here is an example to help visualize how this works: + + [php] + // $conn instanceof Doctrine\DBAL\Connection + try { + $conn->beginTransaction(); // 0 => 1, "real" transaction started + + ... + + try { + $conn->beginTransaction(); // 1 => 2 + + ... + + $conn->commit(); // 2 => 1 + } catch (Exception $e) { + $conn->rollback(); // 2 => 1 + throw $e; + } + + ... + + $conn->commit(); // 1 => 0, "real" transaction committed + } catch (Exception $e) { + $conn->rollback(); // 1 => 0, "real" transaction rollback + throw $e; + } + +What is the benefit of this? It allows reliable and transparent widening of transaction boundaries. Given the following code snippet, without any explicit transaction demarcation: + + [php] + // $em instanceof EntityManager + $user = new User; + $user->setName('George'); + $em->persist($user); + $em->flush(); + +Inside `EntityManager#flush()` something like this happens: + + [php] + try { + $conn->beginTransaction(); // suspend auto-commit + + ... commit all changes to the database ... + + $conn->commit(); + } catch (Exception $e) { + $conn->rollback(); + throw $e; + } + +Since we do not do any custom transaction demarcation in the first snippet, `EntityManager#flush()` will begin and commit/rollback a "real" transaction. Now, if we want to widen the transaction boundaries, say, because we want to include some manual work with a `Doctrine\DBAL\Connection` in the same transaction, we can simply do this: + + [php] + // $em instanceof EntityManager + $conn = $em->getConnection(); + try { + $conn->beginTransaction(); // suspend auto-commit + + // Direct use of the Connection + $conn->insert(...); + + $user = new User; + $user->setName('George'); + $em->persist($user); + $em->flush(); + + $conn->commit(); + } catch (Exception $e) { + $conn->rollback(); + // handle or rethrow + } + +Now, our own code controls the "real" transaction and the transaction demarcation that happens inside `EntityManager#flush()` will merely affect the nesting level. When flush() returns, either by throwing an exception or regularly, the nesting level is the same as before the invocation of flush(), in this case 1, and thus our own $conn->commit() / $conn->rollback() affect the "real" transaction as expected, since we were the ones who started the transaction. + +> **CAUTION** +> Directly invoking `PDO#beginTransaction()`, `PDO#commit()` or `PDO#rollback()` or the +> corresponding methods on the particular `Doctrine\DBAL\Driver\Connection` instance in +> use bybasses the transparent transaction nesting that is provided by +> `Doctrine\DBAL\Connection` and can therefore corrupt the nesting level, causing errors +> with broken transaction boundaries that may be hard to debug. + +++ Optimistic Locking + +Database transactions are fine for concurrency control during a single request. However, a database transaction should not span across requests, the so-called "user think time". Therefore a long-running "business transaction" that spans multiple requests needs to involve several database transactions. Thus, database transactions alone can no longer control concurrency during such a long-running business transaction. Concurrency control becomes the partial responsibility of the application itself. + +Doctrine has integrated support for automatic optimistic locking via a version field. In this approach any entity that should be protected against concurrent modifications during long-running business transactions gets a version field that is either a simple number (mapping type: integer) or a timestamp (mapping type: datetime). When changes to such an entity are persisted at the end of a long-running conversation the version of the entity is compared to the version in the database and if they dont match, an `OptimisticLockException` is thrown, indicating that the entity has been modified by someone else already. + +You designate a version field in an entity as follows. In this example we'll use an integer. + + [php] + class User + { + // ... + /** @Version @Column(type="integer") */ + private $version; + // ... + } + +You could also just as easily use a datetime column and instead of incrementing an integer, a timestamp will be kept up to date. + + + [php] + class User + { + // ... + /** @Version @Column(type="integer") */ + private $version; + // ... + } \ No newline at end of file diff --git a/orm/manual/en/working-with-objects.txt b/orm/manual/en/working-with-objects.txt new file mode 100644 index 000000000..d6c76ad07 --- /dev/null +++ b/orm/manual/en/working-with-objects.txt @@ -0,0 +1,445 @@ +++ Understanding + +In this chapter we will help you understand the `EntityManager` and the `UnitOfWork`. +A Unit of Work is similar to an object-level transaction. A new Unit of Work is +implicity started when an EntityManager is initially created or after +`EntityManager#flush()` has been invoked. A Unit of Work is committed +(and a new one started) by invoking `EntityManager#flush()`. + +A Unit of Work can be manually closed by calling EntityManager#close(). Any +changes to objects within this Unit of Work that have not yet been persisted +are lost. + ++++ The size of a Unit of Work + +The size of a Unit of Work mainly refers to the number of managed entities at +a particular point in time. + ++++ The cost of flush() + +How costly a flush operation is in terms of performance mainly depends on 2 factors: + +* The size of your current Unit of Work +* The configured change tracking policies + +You can get the size of your Unit of Work as follows: + + [php] + $uowSize = $em->getUnitOfWork()->size(); + +The size represents the number of managed entities in the Unit of Work. This +size affects the performance of flush() operations due to change tracking +(see "Change Tracking Policies") and, of course, memory consumption, so you +may want to check it from time to time during development. + +> **CAUTION** +> Do not invoke `flush` after every change to an entity or every single invocation of +> persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the +> performance of your application. Instead form units of work that operate on your objects +> and call `flush` when you are done. While serving a single HTTP request there should +> be no need for invoking `flush` more than 0-2 times. + ++++ Direct access to a Unit of Work + +You can get direct access to the Unit of Work by calling `EntityManager#getUnitOfWork()`. +This will return the UnitOfWork instance the EntityManager is currently using. + + [php] + $uow = $em->getUnitOfWork(); + +> **NOTE** +> Directly manipulating a UnitOfWork is not recommended. When working directly with the +> UnitOfWork API respect methods marked as INTERNAL by not using them and carefully read +> the API documentation. + +++ Persisting entities + +An entity can be made persistent by passing it to the `EntityManager#persist($entity)` +method. By applying the persist operation on some entity, that entity becomes MANAGED, +which means that its persistence is from now on managed by an EntityManager. As a +result the persistent state of such an entity will subsequently be properly +synchronized with the database when `EntityManager#flush()` is invoked. + +> **CAUTION** +> Invoking the `persist` method on an entity does NOT cause an immediate SQL INSERT to be +> issued on the database. Doctrine applies a strategy called "transactional write-behind", +> which means that it will delay most SQL commands until `EntityManager#flush()` is +> invoked which will then issue all necessary SQL statements to synchronize your objects +> with the database in the most efficient way and a single, short transaction, +> taking care of maintaining referential integrity. + +- + +> **CAUTION** +> Generated entity identifiers / primary keys are guaranteed to be available after the +> next invocation of `EntityManager#flush()` that involves the entity in question. +> YOU CAN NOT RELY ON A GENERATED IDENTIFIER TO BE AVAILABLE AFTER INVOKING `persist`! + +Example: + + [php] + $user = new User; + $user->setName('Mr.Right'); + $em->persist($user); + $em->flush(); + // If $user had a generated identifier, it would now be available. + +The semantics of the persist operation, applied on an entity X, are as follows: + +* If X is a new entity, it becomes managed. The entity X will be entered into the database at or before transaction commit or as a result of the flush operation. +* If X is a preexisting managed entity, it is ignored by the persist operation. However, the persist operation is cascaded to entities referenced by X, if the relationships from X to these other entities are mapped with cascade=PERSIST or cascade=ALL (see "Transitive Persistence"). +* If X is a removed entity, it becomes managed. +* If X is a detached entity, an InvalidArgumentException will be thrown. + + +++ Removing entities + +An entity can be removed from persistent storage by passing it to the `EntityManager#remove($entity)` method. By applying the `remove` operation on some entity, that entity becomes REMOVED, which means that its persistent state will be deleted once `EntityManager#flush()` is invoked. The in-memory state of an entity is unaffected by the `remove` operation. + +> **CAUTION** +> Just like `persist`, invoking `remove` on an entity does NOT cause an immediate SQL +> DELETE to be issued on the database. The entity will be deleted on the next invocation +> of `EntityManager#flush()` that involves that entity. + +Example: + + [php] + $em->remove($user); + $em->flush(); + +The semantics of the remove operation, applied to an entity X are as follows: + +* If X is a new entity, it is ignored by the remove operation. However, the remove operation is cascaded to entities referenced by X, if the relationship from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). +* If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). +* If X is a detached entity, an InvalidArgumentException will be thrown. +* If X is a removed entity, it is ignored by the remove operation. +* A removed entity X will be removed from the database at or before transaction commit or as a result of the flush operation. + + +++ Detaching entities + +An entity is detached from an EntityManager and thus no longer managed by +invoking the `EntityManager#detach($entity)` method on it or by cascading +the detach operation to it. Changes made to the detached entity, if any +(including removal of the entity), will not be synchronized to the database +after the entity has been detached. + +Doctrine will not hold on to any references to a detached entity. + +Example: + + [php] + $em->detach($entity); + +The semantics of the detach operation, applied to an entity X are as follows: + +* If X is a managed entity, the detach operation causes it to become detached. The detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X. +* If X is a new or detached entity, it is ignored by the detach operation. +* If X is a removed entity, the detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X. + +There are several situations in which an entity is detached automatically without invoking the `detach` method: + +* When `EntityManager#clear()` is invoked, all entities that are currently managed by the EntityManager instance become detached. +* When serializing an entity. The entity retrieved upon subsequent unserialization will be detached (This is the case for all entities that are serialized and stored in some cache, i.e. when using the Query Result Cache). + +The `detach` operation is usually not as frequently needed and used as `persist` and `remove`. + + +++ Merging entities + +Merging entities refers to the merging of (usually detached) entities into the +context of an EntityManager so that they become managed again. To merge the +state of an entity into an EntityManager use the `EntityManager#merge($entity)` +method. The state of the passed entity will be merged into a managed copy of +this entity and this copy will subsequently be returned. + +Example: + + [php] + $detachedEntity = unserialize($serializedEntity); // some detached entity + $entity = $em->merge($detachedEntity); + // $entity now refers to the fully managed copy returned by the merge operation. + // The EntityManager $em now manages the persistence of $entity as usual. + +> **WARNING** +> When you want to serialize/unserialize entities you have to make all entity properties +> protected, never private. The reason for this is, if you serialize a class that was a proxy +> instance before the private variables won't be serialized and a PHP Notice is thrown. + +The semantics of the merge operation, applied to an entity X, are as follows: + +* If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity or a new managed copy X' of X is created. +* If X is a new entity instance, an InvalidArgumentException will be thrown. +* If X is a removed entity instance, an InvalidArgumentException will be thrown. +* If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been mapped with the cascade element value MERGE or ALL (see "Transitive Persistence"). +* For all entities Y referenced by relationships from X having the cascade element value +MERGE or ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.) +* If X is an entity merged to X', with a reference to another entity Y, where cascade=MERGE or cascade=ALL is not specified, then navigation of the same association from X' yields a reference to a managed object Y' with the same persistent identity as Y. + +The `merge` operation will throw an `OptimisticLockException` if the entity +being merged uses optimistic locking through a version field and the versions +of the entity being merged and the managed copy dont match. This usually means +that the entity has been modified while being detached. + +The `merge` operation is usually not as frequently needed and used as `persist` +and `remove`. The most common scenario for the `merge` operation is to reattach +entities to an EntityManager that come from some cache (and are therefore detached) +and you want to modify and persist such an entity. + +> **NOTE** +> If you load some detached entities from a cache and you do not need to persist or +> delete them or otherwise make use of them without the need for persistence services +> there is no need to use `merge`. I.e. you can simply pass detached objects from a cache +> directly to the view. + + +++ Associations + +Associations between entities are represented just like in regular object-oriented PHP, with references to other objects or collections of objects. When it comes to persistence, it is important to understand three main things: + + * The concept of owning and inverse sides in bidirectional associations as described [here](http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#owning-side-and-inverse-side). + * A collection of entities always only represents the association to the containing entities. If an entity is removed from a collection, the association is removed, not the entity itself. + * Collection-valued persistent fields and properties must be defined in terms of the Doctrine\Common\Collections\Collection interface. [See here](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities:persistent-fields) for more details. + + +++ Establishing Associations + +Establishing an association between two entities is straight-forward. Here are some examples: + + [php] + // Article <- one-to-many -> Comment + $article->getComments()->add($comment); + $comment->setArticle($article); + + // User <- many-to-many -> Groups + $user->getGroups()->add($group); + $group->getUsers()->add($user); + + // User <- one-to-one -> Address + $user->setAddress($address); + $address->setUser($user); + + +Notice how always both sides of the bidirectional association are updated. Unidirectional associations are consequently simpler to handle. + +++ Removing Associations + +Removing an association between two entities is similarly straight-forward. There are two strategies +to do so, by key and by element. Here are some examples: + + [php] + // Remove by Elements + // Article <- one-to-many -> Comment + $article->getComments()->removeElement($comment); + $comment->setArticle(null); + + // User <- many-to-many -> Group + $user->getGroups()->removeElement($group); + $group->getUsers()->removeElement($user); + + // Remove by key + $article->getComments()->remove($ithComment); + $comment->setArticle(null); + + // User <- one-to-one -> Address + $user->setAddress(null); + $address->setUser(null); + + +Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does not allow null values and setAddress(null) will fail for removing the association. If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`. This can also provide better encapsulation as it hides the internal meaning of not having an address. + +Since Doctrine always only looks at the owning side of a bidirectional association, it is essentially not necessary that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. + + +++ Association Management Methods + +It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained. + +The following code shows a simple, idiomatic example for a bidirectional one-to-many association between an Article and its Comments. + + [php] + // Mappings not shown. + class Article { + // The comments of the article. + private $comments; + // ... constructor omitted ... + public function addComment(Comment $comment) { + $this->comments->add($comment); + $comment->setArticle($this); + } + public function getComments() { + return $this->comments; + } + } + class Comment { + // The article the comment refers to. + private $article; + // ... constructor omitted ... + public function setArticle($article) { + $this->article = $article; + } + public function getArticle() { + return $this->article; + } + } + +With the above implementation, it is always ensured that at least the owning side from Doctrine's point of view (Comment) is properly updated. You will notice that `setArticle` does not call `addComment`, thus the bidirectional association is strictly-speaking still incomplete, if a user of the class only invokes `setArticle`. If you naively call `addComment` in `setArticle`, however, you end up with an infinite loop, so more work is needed. As you can see, proper bidirectional association management in plain OOP is a non-trivial task and encapsulating all the details inside the classes can be challenging. + +There is no single, best way for association management. It greatly depends on the requirements of your concrete domain model as well as your preferences. + + +++ Transitive persistence + +Persisting, removing, detaching and merging individual entities can become pretty +cumbersome, especially when a larger object graph with collections is involved. +Therefore Doctrine 2 provides a mechanism for transitive persistence through +cascading of these operations. Each association to another entity or a collection +of entities can be configured to automatically cascade certain operations. By +default, no operations are cascaded. + +The following cascade options exist: + + * persist : Cascades persist operations to the associated entities. + * remove : Cascades remove operations to the associated entities. + * merge : Cascades merge operations to the associated entities. + * detach : Cascades detach operations to the associated entities. + * all : Cascades persist, remove, merge and detach operations to associated entities. + +The following example shows an association to a number of addresses. If persist() +or remove() is invoked on any User entity, it will be cascaded to all associated +Address entities in the $addresses collection. + + [php] + class User + { + //... + /** + * @OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "remove"}) + */ + private $addresses; + //... + } + +Even though automatic cascading is convenient it should be used with care. +Do not blindly apply cascade=all to all associations as it will unnecessarily +degrade the performance of your application. + + +++ Querying + +Doctrine 2 provides the following ways, in increasing level of power and flexibility, to query for persistent objects. You should always start with the simplest one that suits your needs. + ++++ By Primary Key + +The most basic way to query for a persistent object is by its identifier / primary key using the `EntityManager#find($entityName, $id)` method. Here is an example: + + [php] + // $em instanceof EntityManager + $user = $em->find('MyProject\Domain\User', $id); + +The return value is either the found entity instance or null if no instance could be found with the given identifier. + +Essentially, `EntityManager#find()` is just a shortcut for the following: + + [php] + // $em instanceof EntityManager + $user = $em->getRepository('MyProject\Domain\User')->find($id); + +`EntityManager#getRepository($entityName)` returns a repository object which provides many ways to retreive entities of the specified type. By default, the repository instance is of type `Doctrine\ORM\EntityRepository`. You can also use custom repository classes as shown later. + ++++ By Simple Conditions + +To query for one or more entities based on several conditions that form a logical conjunction, use the `findBy` and `findOneBy` methods on a repository as follows: + + [php] + // $em instanceof EntityManager + + // All users that are 20 years old + $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20)); + + // All users that are 20 years old and have a surname of 'Miller' + $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller')); + + // A single user by its nickname + $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); + +An EntityRepository also provides a mechanism for more concise calls through its use of `__call`. Thus, the following two examples are equivalent: + + [php] + // A single user by its nickname + $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); + + // A single user by its nickname (__call magic) + $user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb'); + + ++++ By Eager Loading + +Whenever you query for an entity that has persistent associations and these associations are mapped as EAGER, they will automatically be loaded together with the entity being queried and is thus immediately available to your application. + + ++++ By Lazy Loading + +Whenever you have a managed entity instance at hand, you can traverse and use any associations of that entity that are configured LAZY as if they were in-memory already. Doctrine will automatically load the associated objects on demand through the concept of lazy-loading. + + ++++ By DQL + +The most powerful and flexible method to query for persistent objects is the Doctrine Query Language, an object query language. DQL enables you to query for persistent objects in the language of objects. DQL understands classes, fields, inheritance and associations. +DQL is syntactically very similar to the familar SQL but *it is not SQL*. + +A DQL query is represented by an instance of the `Doctrine\ORM\Query` class. You create a query using `EntityManager#createQuery($dql)`. Here is a simple example: + + [php] + // $em instanceof EntityManager + + // All users with an age between 20 and 30 (inclusive). + $q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30"); + $users = $q->getResult(); + +Note that this query contains no knowledge about the relational schema, only about the object model. DQL supports positional as well as named parameters, many functions, (fetch) joins, aggregates, subqueries and much more. Detailed information about DQL and its syntax as well as the Doctrine\ORM\Query class can be found in [the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language). For programmatically building up queries based on conditions that are only known at runtime, Doctrine provides the special `Doctrine\ORM\QueryBuilder` class. More information on constructing queries with a QueryBuilder can be found [in the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/query-builder). + + ++++ By Native Queries + +As an alternative to DQL or as a fallback for special SQL statements native queries can be used. +Native queries are built by using a hand-crafted SQL query and a ResultSetMapping that describes +how the SQL result set should be transformed by Doctrine. More information about native queries +can be found in [the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/native-sql). + ++++ Custom Repositories + +By default the EntityManager returns a default implementation of `Doctrine\ORM\EntityRepository` when +you call `EntityManager#getRepository($entityClass)`. You can overwrite this behaviour by specifying +the class name of your own Entity Repository in the Annotation, XML or YAML metadata. +In large applications that require lots of specialized DQL queries using a custom repository is +one recommended way of grouping these queries in a central location. + + [php] + namespace MyDomain\Model; + use Doctrine\ORM\EntityRepository; + + /** + * @entity(repositoryClass="MyDomain\Model\UserRepository") + */ + class User + { + + } + + class UserRepository extends EntityRepository + { + public function getAllAdminUsers() + { + return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"') + ->getResult(); + } + } + +You can access your repository now by calling: + + [php] + // $em instanceof EntityManager + + $admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers(); + diff --git a/orm/manual/en/xml-mapping.txt b/orm/manual/en/xml-mapping.txt new file mode 100644 index 000000000..0db537be7 --- /dev/null +++ b/orm/manual/en/xml-mapping.txt @@ -0,0 +1,83 @@ +The XML mapping driver enables you to provide the ORM metadata in form of XML documents. + +The XML driver is backed by an XML Schema document that describes the structure of a mapping document. The most recent version of the XML Schema document is available online at [http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd](http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd). In order to point to the latest version of the document of a particular stable release branch, just append the release number, i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with XML mapping files is to use an IDE/editor that can provide code-completion based on such an XML Schema document. The following is an outline of a XML mapping document with the proper xmlns/xsi setup for the latest code in trunk. + + [xml] + + + ... + + + +The XML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: + + * Each entity/mapped superclass must get its own dedicated XML mapping document. + * The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.). + * All mapping documents should get the extension ".dcm.xml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough. + +- + + [php] + $driver->setFileExtension('.xml'); + +It is recommended to put all XML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the XmlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: + + [php] + // $config instanceof Doctrine\ORM\Configuration + $driver = new XmlDriver(array('/path/to/files')); + $config->setMetadataDriverImpl($driver); + + +++ Example + +As a quick start, here is a small example document that makes use of several common elements: + + [xml] + // Doctrine.Tests.ORM.Mapping.User.dcm.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Be aware that class-names specified in the XML files should be fully qualified. \ No newline at end of file diff --git a/orm/manual/en/yaml-mapping.txt b/orm/manual/en/yaml-mapping.txt new file mode 100644 index 000000000..a5c6b29c7 --- /dev/null +++ b/orm/manual/en/yaml-mapping.txt @@ -0,0 +1,66 @@ +The YAML mapping driver enables you to provide the ORM metadata in form of YAML documents. + +The YAML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: + + * Each entity/mapped superclass must get its own dedicated YAML mapping document. + * The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.). + * All mapping documents should get the extension ".dcm.yml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough. + +- + + [php] + $driver->setFileExtension('.yml'); + +It is recommended to put all YAML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the YamlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: + + [php] + // $config instanceof Doctrine\ORM\Configuration + $driver = new YamlDriver(array('/path/to/files')); + $config->setMetadataDriverImpl($driver); + + +++ Example + +As a quick start, here is a small example document that makes use of several common elements: + + [yml] + # Doctrine.Tests.ORM.Mapping.User.dcm.yml + Doctrine\Tests\ORM\Mapping\User: + type: entity + table: cms_users + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string + length: 50 + oneToOne: + address: + targetEntity: Address + joinColumn: + name: address_id + referencedColumnName: id + oneToMany: + phonenumbers: + targetEntity: Phonenumber + mappedBy: user + cascade: cascadePersist + manyToMany: + groups: + targetEntity: Group + joinTable: + name: cms_users_groups + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + group_id: + referencedColumnName: id + lifecycleCallbacks: + prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] + postPersist: [ doStuffOnPostPersist ] + + Be aware that class-names specified in the YAML files should be fully qualified. \ No newline at end of file diff --git a/rest/manual/en.txt b/rest/manual/en.txt new file mode 100644 index 000000000..e69de29bb From 33ff1df4ecf99fb7dc8b422298f24f13c48cc666 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Tue, 6 Apr 2010 14:30:12 -0400 Subject: [PATCH 002/430] Adding cookbook --- orm/cookbook/en.txt | 8 + orm/cookbook/en/dql-custom-walkers.txt | 174 +++++ .../en/dql-user-defined-functions.txt | 198 +++++ .../en/getting-started-xml-edition.txt | 704 ++++++++++++++++++ ...menting-arrayaccess-for-domain-objects.txt | 89 +++ ...nting-the-notify-changetracking-policy.txt | 48 ++ .../en/implementing-wakeup-or-clone.txt | 63 ++ .../en/integrating-with-codeigniter.txt | 104 +++ orm/cookbook/en/validation-of-entities.txt | 114 +++ 9 files changed, 1502 insertions(+) create mode 100644 orm/cookbook/en.txt create mode 100644 orm/cookbook/en/dql-custom-walkers.txt create mode 100644 orm/cookbook/en/dql-user-defined-functions.txt create mode 100644 orm/cookbook/en/getting-started-xml-edition.txt create mode 100644 orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt create mode 100644 orm/cookbook/en/implementing-the-notify-changetracking-policy.txt create mode 100644 orm/cookbook/en/implementing-wakeup-or-clone.txt create mode 100644 orm/cookbook/en/integrating-with-codeigniter.txt create mode 100644 orm/cookbook/en/validation-of-entities.txt diff --git a/orm/cookbook/en.txt b/orm/cookbook/en.txt new file mode 100644 index 000000000..db1b17496 --- /dev/null +++ b/orm/cookbook/en.txt @@ -0,0 +1,8 @@ ++ Getting Started XML-Edition ++ Implementing ArrayAccess for domain objects ++ Implementing the NOTIFY changetracking policy ++ Validation of Entities ++ Implementing wakeup or clone ++ Integrating with CodeIgniter ++ DQL Custom Walkers ++ DQL User Defined Functions \ No newline at end of file diff --git a/orm/cookbook/en/dql-custom-walkers.txt b/orm/cookbook/en/dql-custom-walkers.txt new file mode 100644 index 000000000..48d4840a4 --- /dev/null +++ b/orm/cookbook/en/dql-custom-walkers.txt @@ -0,0 +1,174 @@ +# Extending DQL in Doctrine 2: Custom AST Walkers + +The Doctrine Query Language (DQL) is a propriotary sql-dialect that substitutes +tables and columns for Entity names and their fields. Using DQL you write a query +against the database using your entities. With the help of the metadata you +can write very concise, compact and powerful queries that are then translated +into SQL by the Doctrine ORM. + +In Doctrine 1 the DQL language was not implemented using a real parser. This +made modifications of the DQL by the user impossible. Doctrine 2 in constrast +has a real parser for the DQL language, which transforms the DQL statement +into an [Abstract Syntax Tree](http://en.wikipedia.org/wiki/Abstract_syntax_tree) +and generates the appropriate SQL statement for it. Since this process is +deterministic Doctrine heavily caches the SQL that is generated from any given DQL query, +which reduces the performance overhead of the parsing process to zero. + +You can modify the Abstract syntax tree by hooking into DQL parsing process +by adding a Custom Tree Walker. A walker is an interface that walks each +node of the Abstract syntax tree, thereby generating the SQL statement. + +There are two types of custom tree walkers that you can hook into the DQL parser: + +- An output walker. This one actually generates the SQL, and there is only ever one of them. We implemented the default SqlWalker implementation for it. +- A tree walker. There can be many tree walkers, they cannot generate the sql, however they can modify the AST before its rendered to sql. + +Now this is all awfully technical, so let me come to some use-cases fast +to keep you motivated. Using walker implementation you can for example: + +* Modify the AST to generate a Count Query to be used with a paginator for any given DQL query. +* Modify the Output Walker to generate vendor-specific SQL (instead of ANSI). +* Modify the AST to add additional where clauses for specific entities (example ACL, country-specific content...) +* Modify the Output walker to pretty print the SQL for debugging purposes. + +In this cookbook-entry I will show examples on the first two points. There +are probably much more use-cases. + +## Generic count query for pagination + +Say you have a blog and posts all with one category and one author. A query +for the front-page or any archive page might look something like: + + [sql] + SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... + +Now in this query the blog post is the root entity, meaning its the one that +is hydrated directly from the query and returned as an array of blog posts. +In contrast the comment and author are loaded for deeper use in the object tree. + +A pagination for this query would want to approximate the number of posts that +match the WHERE clause of this query to be able to predict the number of pages +to show to the user. A draft of the DQL query for pagination would look like: + + [sql] + SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... + +Now you could go and write each of these queries by hand, or you can use a tree +walker to modify the AST for you. Lets see how the API would look for this use-case: + + [php] + $pageNum = 1; + $query = $em->createQuery($dql); + $query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20); + + $totalResults = Paginate::count($query); + $results = $query->getResult(); + +The `Paginate::count(Query $query)` looks like: + + [php] + class Paginate + { + static public function count(Query $query) + { + /* @var $countQuery Query */ + $countQuery = clone $query; + + $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker')); + $countQuery->setFirstResult(null)->setMaxResults(null); + + return $countQuery->getSingleScalarResult(); + } + } + +It clones the query, resets the limit clause first and max results and registers the `CountSqlWalker` +customer tree walker which will modify the AST to execute a count query. The walkers +implementation is: + + [php] + class CountSqlWalker extends TreeWalkerAdapter + { + /** + * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. + * + * @return string The SQL. + */ + public function walkSelectStatement(SelectStatement $AST) + { + $parent = null; + $parentName = null; + foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) { + if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { + $parent = $qComp; + $parentName = $dqlAlias; + break; + } + } + + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, array( + $parent['metadata']->getSingleIdentifierFieldName()) + ); + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + + $AST->selectClause->selectExpressions = array( + new SelectExpression( + new AggregateExpression('count', $pathExpression, true), null + ) + ); + } + } + +This will delete any given select expressions and replace them with a distinct count +query for the root entities primary key. This will only work if your entity has +only one identifier field (composite keys won't work). + +## Modify the Output Walker to generate Vendor specific SQL + +Most RMDBS have vendor-specific features for optimizing select query +execution plans. You can write your own output walker to introduce certain +keywords using the Query Hint API. A query hint can be set via `Query::setHint($name, $value)` +as shown in the previous example with the `HINT_CUSTOM_TREE_WALKERS` query hint. + +We will implement a custom Output Walker that allows to specifiy the SQL_NO_CACHE +query hint. + + [php] + $dql = "SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ..."; + $query = $m->createQuery($dql); + $query->setQueryHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker'); + $query->setQueryHint("mysqlWalker.sqlNoCache", true); + $results = $query->getResult(); + +Our `MysqlWalker` will extend the default `SqlWalker`. We will modify the generation +of the SELECT clause, adding the SQL_NO_CACHE on those queries that need it: + + [php] + class MysqlWalker extends SqlWalker + { + /** + * Walks down a SelectClause AST node, thereby generating the appropriate SQL. + * + * @param $selectClause + * @return string The SQL. + */ + public function walkSelectClause($selectClause) + { + $sql = parent::walkSelectClause($selectClause); + + if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) { + if ($selectClause->isDistinct) { + $sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql); + } else { + $sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql); + } + } + + return $sql; + } + } + +Writing extensions to the Output Walker requires a very deep understanding +of the DQL Parser and Walkers, but may offer your huge benefits with using vendor specific +features. This would still allow you write DQL queries instead of NativeQueries +to make use of vendor specific features. diff --git a/orm/cookbook/en/dql-user-defined-functions.txt b/orm/cookbook/en/dql-user-defined-functions.txt new file mode 100644 index 000000000..d738617c4 --- /dev/null +++ b/orm/cookbook/en/dql-user-defined-functions.txt @@ -0,0 +1,198 @@ +By default DQL supports a limited subset of all the vendor-specific SQL functions +common between all the vendors. However in many cases once you have decided on a +specific database vendor, you will never change it during the life of your project. +This decision for a specific vendor potentially allows you to make use of powerful +SQL features that are unique to the vendor. + +> **Note** +> +> It is worth to mention that Doctrine 2 also allows you to handwrite your SQL instead of extending +> the DQL parser, which is sort of an advanced extension point. You can map arbitrary SQL to your +> objects and gain access to vendor specific functionalities using the `EntityManager#createNativeQuery()` API. + +The DQL Parser has hooks to register functions that can then be used in your DQL queries and transformed into SQL, +allowing to extend Doctrines Query capabilities to the vendors strength. This post explains the +Used-Defined Functions API (UDF) of the Dql Parser and shows some examples to give you +some hints how you would extend DQL. + +There are three types of functions in DQL, those that return a numerical value, +those that return a string and those that return a Date. Your custom method +has to be registered as either one of those. The return type information +is used by the DQL parser to check possible syntax errors during the parsing +process, for example using a string function return value in a math expression. + +## Registering your own DQL functions + +You can register your functions adding them to the ORM configuration: + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->addCustomStringFunction($name, $class); + $config->addCustomNumericFunction($name, $class); + $config->addCustomDatetimeFunction($name, $class); + + $em = EntityManager::create($dbParams, $config); + +The `$name` is the name the function will be referred to in the DQL query. `$class` is a +string of a class-name which has to extend `Doctrine\ORM\Query\Node\FunctionNode`. +This is a class that offers all the necessary API and methods to implement +a UDF. + +In this post we will implement some MySql specific Date calculation methods, +which are quite handy in my opinion: + +## Date Diff + +[Mysql's DateDiff function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff) +takes two dates as argument and calculates the difference in days with `date1-date2`. + +The DQL parser is a top-down recursive descent parser to generate the +Abstract-Syntax Tree (AST) and uses a TreeWalker approach to generate the appropriate +SQL from the AST. This makes reading the Parser/TreeWalker code managable +in a finite amount of time. + +The `FunctionNode` class I referred to earlier requires you to implement +two methods, one for the parsing process (obviously) called `parse` and +one for the TreeWalker process called `getSql()`. I show you the code for +the DateDiff method and discuss it step by step: + + [php] + /** + * DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" + */ + class DateDiff extends FunctionNode + { + // (1) + public $firstDateExpression = null; + public $secondDateExpression = null; + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); // (2) + $parser->match(Lexer::T_OPEN_PARENTHESIS); // (3) + $this->firstDateExpression = $parser->ArithmeticPrimary(); // (4) + $parser->match(Lexer::T_COMMA); // (5) + $this->secondDateExpression = $parser->ArithmeticPrimary(); // (6) + $parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3) + } + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'DATEDIFF(' . + $this->firstDateExpression->dispatch($sqlWalker) . ', ' . + $this->secondDateExpression->dispatch($sqlWalker) . + ')'; // (7) + } + } + +The Parsing process of the DATEDIFF function is going to find two expressions +the date1 and the date2 values, whose AST Node representations will be saved +in the variables of the DateDiff FunctionNode instance at (1). + +The parse() method has to cut the function call "DATEDIFF" and its argument +into pieces. Since the parser detects the function using a lookahead the +T_IDENTIFIER of the function name has to be taken from the stack (2), followed +by a detection of the arguments in (4)-(6). The opening and closing parenthesis +have to be detected also. This happens during the Parsing process and leads +to the generation of a DateDiff FunctionNode somewhere in the AST of the +dql statement. + +The `ArithmeticPrimary` method call is the most common denominator of valid +EBNF tokens taken from the [DQL EBNF grammer](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf) +that matches our requirements for valid input into the DateDiff Dql function. +Picking the right tokens for your methods is a tricky business, but the EBNF +grammer is pretty helpful finding it, as is looking at the Parser source code. + +Now in the TreeWalker process we have to pick up this node and generate SQL +from it, which apprently is quite easy looking at the code in (7). Since +we don't know which type of AST Node the first and second Date expression +are we are just dispatching them back to the SQL Walker to generate SQL from +and then wrap our DATEDIFF function call around this output. + +Now registering this DateDiff FunctionNode with the ORM using: + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff'); + +We can do fancy stuff like: + + [sql] + SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7 + +## Date Add + +Often useful it the ability to do some simple date calculations in your DQL query +using [MySql's DATE_ADD function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add). + +I'll skip the bla and show the code for this function: + + [php] + /** + * DateAddFunction ::= + * "DATE_ADD" "(" ArithmeticPrimary ", INTERVAL" ArithmeticPrimary Identifier ")" + */ + class DateAdd extends FunctionNode + { + public $firstDateExpression = null; + public $intervalExpression = null; + public $unit = null; + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->firstDateExpression = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_COMMA); + $parser->match(Lexer::T_IDENTIFIER); + + $this->intervalExpression = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_IDENTIFIER); + + /* @var $lexer Lexer */ + $lexer = $parser->getLexer(); + $this->unit = $lexer->token['value']; + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'DATE_ADD(' . + $this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' . + $this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit . + ')'; + } + } + +The only difference compared to the DATEDIFF here is, we additionally need the `Lexer` to access +the value of the `T_IDENTIFIER` token for the Date Interval unit, for example the MONTH in: + + [sql] + SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created + +The above method now only supports the specification using `INTERVAL`, to also +allow a real date in DATE_ADD we need to add some decision logic to the parsing +process (makes up for a nice excercise). + +Now as you see, the Parsing process doesn't catch all the possible SQL errors, +here we don't match for all the valid inputs for the interval unit. +However where necessary we rely on the database vendors SQL parser to show us further errors +in the parsing process, for example if the Unit would not be one of the supported values +by MySql. + +## Conclusion + +Now that you all know how you can implement vendor specific SQL functionalities in DQL, +we would be excited to see user extensions that add vendor specific function packages, +for example more math functions, XML + GIS Support, Hashing functions and so on. + +For 2.0 we will come with the current set of functions, however for a future +version we will re-evaluate if we can abstract even more vendor sql functions +and extend the DQL languages scope. + +Code for this Extension to DQL and other Doctrine Extensions can be found +[in my Github DoctrineExtensions repository](http://github.com/beberlei/DoctrineExtensions). \ No newline at end of file diff --git a/orm/cookbook/en/getting-started-xml-edition.txt b/orm/cookbook/en/getting-started-xml-edition.txt new file mode 100644 index 000000000..b9a9632d1 --- /dev/null +++ b/orm/cookbook/en/getting-started-xml-edition.txt @@ -0,0 +1,704 @@ +Doctrine 2 is a project that aims to handle the persistence of the domain model in a non-interfering way. +The Data Mapper pattern is at the heart of this project, aiming for a complete separation of the domain/business logic +from the persistence in a relational database management system. The benefit of Doctrine for the programmer is the +possibility can focus soley on the business and worry about persistence only as a secondary task. This doesn't mean +persistence is not important to Doctrine 2, however it is our believe that there are considerable benefits for object-oriented +programming, if persistence and entities are kept perfectly seperated. + +## What are Entities? + +Entities are leightweight PHP Objects that don't need to extend any abstract base class or interface. +An entity class must not be final or contain final methods. Additionally it must not implement __clone +nor __wakeup or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). + +An entity contains persistable properties. A persistable property is an instance variable of the entity +that contains the data which is persisted and retrieved by Doctrine's data mapping capabilities. + +## An Example Model: Bug Tracker + +For this Getting Started Guide for Doctrine we will implement the Bug Tracker domain model from the [Zend_Db_Table](http://framework.zend.com/manual/en/zend.db.table.html) +documentation. Reading that documentat we can extract the requirements to be: + +* A Bugs has a description, creation date, status, reporter and engineer +* A bug can occour on different products (platforms) +* Products have a name. +* Bug Reporter and Engineers are both Users of the System. +* A user can create new bugs. +* The assigned engineer can close a bug. +* A user can see all his reported or assigned bugs. +* Bugs can be paginated through a list-view. + +> **WARNING** +> +> This tutorial is incrementally building up your Doctrine 2 knowledge and even lets you make some mistakes, to +> show some common pitfalls in mapping Entities to a database. Don't blindly copy-paste the examples here, it +> is not production ready without the additional comments and knowledge this tutorial teaches. + +## A first prototype + +A first simplified design for this domain model might look like the following set of classes: + + [php] + class Bug + { + public $id; + public $description; + public $created; + public $status; + public $products = array(); + public $reporter; + public $engineer; + } + class Product + { + public $id; + public $name; + } + class User + { + public $id; + public $name; + public $reportedBugs = array(); + public $assignedBugs = array(); + } + +> **WARNING** +> +> This is only a prototype, please don't use public properties with Doctrine 2 at all, +> the "Queries for Application Use-Cases" section shows you why. In combination with proxies +> public properties can make up for pretty nasty bugs. + +Because we will focus on the mapping aspect, no effort is being made to encapsulate the business logic in this example. +All peristable properties are public in visibility. We will soon see that this is not the best solution in combination +with Doctrine 2, one restriction that actually forces you to encapsulate your properties. For persistence Doctrine 2 +actually uses Reflection to access the values in all your entities properties. + +Many of the fields are single scalar values, for example the 3 ID fields of the entities, their names, description, +status and change dates. Doctrine 2 can easily handle these single values as can any other ORM. From a point of our +domain model they are ready to be used right now and we will see at a later stage how they are mapped to the database. + +There are also several references between objects in this domain model, whose semantics are discussed case by case at this point +to explain how Doctrine handles them. In general each OneToOne or ManyToOne Relation in the Database is replaced by an +instance of the related object in the domain model. Each OneToMany or ManyToMany Relation is replaced by a collection +of instances in the domain model. + +If you think this through carefully you realize Doctrine 2 will load up the complete database in memory if you access +one object. However by default Doctrine generates Lazy Load proxies of entities or collections of all the relations +that haven't been explicitly retrieved from the database yet. + +To be able to use lazyload with collections, simple PHP arrays have to be replaced by a generic collection +interface Doctrine\Common\Collections\Collection which tries to act as array as much as possible using ArrayAccess, +IteratorAggregate and Countable interfaces. The class \Doctrine\Common\Collections\ArrayCollection is the most simple +implementation of this interface. + +Now that we know this, we have to clear up our domain model to cope with the assumptions about related collections: + + [php] + use Doctrine\Common\Collections\ArrayCollection; + + class Bug + { + public $products = null; + + public function __construct() + { + $this->products = new ArrayCollection(); + } + } + + class User + { + public $reportedBugs = null; + public $assignedBugs = null; + + public function __construct() + { + $this->reportedBugs = new ArrayCollection(); + $this->assignedBugs = new ArrayCollection(); + } + } + +Whenever an entity is recreated from the database, an Collection implementation of the type +Doctrine\ORM\PersistantCollection is injected into your entity instead of an array. Compared +to the ArrayCollection this implementation helps the Doctrine ORM understand the changes that +have happend to the collection which are noteworthy for persistence. + +> **Warning** +> Lazy load proxies always contain an instance of Doctrine's EntityManager and all its dependencies. Therefore a var_dump() +> will possibly dump a very large recursive structure which is impossible to render and read. You have to use +> `Doctrine\Common\Util\Debug::dump()` to restrict the dumping to a human readable level. Additionally you should be aware +> that dumping the EntityManager to a Browser may take several minutes, and the Debug::dump() method just ignores any +> occurences of it in Proxy instances. + +Because we only work with collections for the references we must be careful to implement a bidirectional reference in +the domain model. The concept of owning or inverse side of a relation is central to this notion and should always +be kept in mind. The following assumptions are made about relations and have to be followed to be able to work with Doctrine 2. +These assumptions are not unique to Doctrine 2 but are best practices in handling database relations and Object-Relational Mapping. + +* Changes to Collections are saved or updated, when the entity on the *ownin*g side of the collection is saved or updated. +* Saving an Entity at the inverse side of a relation never triggers a persist operation to changes to the collection. +* In a one-to-one relation the entity holding the foreign key of the related entity on its own database table is *always* the owning side of the relation. +* In a many-to-many relation, both sides can be the owning side of the relation. However in a bi-directional many-tomany relation only one is allowed to be. +* In a many-to-one relation the Many-side is the owning side by default, because it holds the foreign key. +* The OneToMany side of a relation is inverse by default, since the foreign key is saved on the Many side. A OneToMany relation can only be the owning side, if its implemented using a ManyToMany relation with join table and restricting the one side to allow only UNIQUE values per database constraint. + +> **Important** +> +> Consistency of bi-directional references on the inverse side of a relation have to be managed in userland application code. +> Doctrine cannot magically update your collections to be consistent. + +In the case of Users and Bugs we have references back and forth to the assigned and reported bugs from a user, +making this relation bi-directional. We have to change the code to ensure consistency of the bi-directional reference: + + [php] + class Bug + { + protected $engineer; + protected $reporter; + + public function setEngineer($engineer) + { + $engineer->assignedToBug($this); + $this->engineer = $engineer; + } + + public function setReporter($reporter) + { + $reporter->addReportedBug($this); + $this->reporter = $reporter; + } + + public function getEngineer() + { + return $this->engineer; + } + + public function getReporter() + { + return $this->reporter; + } + } + class User + { + public function addReportedBug($bug) + { + $this->reportedBugs[] = $bug; + } + + public function assignedToBug($bug) + { + $this->assignedBugs[] = $bug; + } + } + +I chose to name the inverse methods in past-tense, which should indicate that the actual assigning has already taken +place and the methods are only used for ensuring consistency of the references. You can see from `User::addReportedBug()` +and `User::assignedToBug()` that using this method in userland alone would not add the Bug to the collection of the owning +side in Bug::$reporter or Bug::$engineer. Using these methods and calling Doctrine for persistence would not update +the collections representation in the database. + +Only using `Bug::setEngineer()` or `Bug::setReporter()` correctly saves the relation information. We also set both collection +instance variables to protected, however with PHP 5.3's new features Doctrine is still able to use Reflection to set and get values +from protected and private properties. + +The `Bug::$reporter` and `Bug::$engineer` properties are Many-To-One relations, which point to a User. In a normalized +relational model the foreign key is saved on the Bug's table, hence in our object-relation model the Bug is at the owning +side of the relation. You should always make sure that the use-cases of your domain model should drive which side +is an inverse or owning one in your Doctrine mapping. In our example, whenever a new bug is saved or an engineer is assigned +to the bug, we don't want to update the User to persist the reference, but the Bug. +This is the case with the Bug being at the owning side of the relation. + +Bugs reference Products by a uni-directional ManyToMany relation in the database that points from from Bugs to Products. + + [php] + class Bug + { + protected $products = null; // Set protected for encapsulation + + public function assignToProduct($product) + { + $this->products[] = $product; + } + + public function getProducts() + { + return $this->products; + } + } + +We are now finished with the domain model given the requirements. From the simple model with public properties only +we had to do quite some work to get to a model where we encapsulated the references between the objects to make sure +we don't break its consistent state when using Doctrine. + +However up to now the assumptions Doctrine imposed on our business objects have not restricting us much in our domain +modelling capabilities. Actually we would have encapsulated access to all the properties anyways by using +object-oriented best-practices. + +## Metadata Mappings for our Entities + +Up to now we have only implemented our Entites as Data-Structures without actually telling Doctrine how to persist +them in the database. If perfect in-memory databases would exist, we could now finish the application using these entities +by implementing code to fullfil all the requirements. However the world isn't perfect and we have to persist our +entities in some storage to make sure we don't loose their state. Doctrine currently serves Relational Database Management Systems. +In the future we are thinking to support NoSQL vendors like CouchDb or MongoDb, however this is still far in the future. + +The next step for persistance with Doctrine is to describe the structure of our domain model entities to Doctrine +using a metadata language. The metadata language describes how entities, their properties and references should be +persisted and what constraints should be applied to them. + +Metadata for entities are loaded using a `Doctrine\ORM\Mapping\Driver\Driver` implementation and Doctrine 2 already comes +with XML, YAML and Annotations Drivers. In this Getting Started Guide I will use the XML Mapping Driver. I think XML +beats YAML because of schema validation, and my favorite IDE netbeans offers me auto-completion for the XML mapping files +which is awesome to work with and you don't have to look up all the different metadata mapping commands all the time. + +Since we haven't namespaced our three entities, we have to implement three mapping files called Bug.dcm.xml, +Product.dcm.xml and User.dcm.xml and put them into a distinct folder for mapping configurations. + +The first discussed definition will be for the Product, since it is the most simple one: + + [xml] + + + + + + + + + + + + +The toplevel `entity` definition tag specifies information about the class and table-name. The +primitive type `Product::$name` is defined as `field` attributes. The Id property is defined with the `id` tag. +The id has a `generator` tag nested inside which defines that the primary key generation mechanism +automatically uses the database platforms native id generation strategy, for example AUTO INCREMENT +in the case of MySql or Sequences in the case of PostgreSql and Oracle. + +We then go on specifying the definition of a Bug: + + [xml] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Here again we have the entity, id and primitive type definitions. +The column names are used from the Zend_Db_Table examples and have different names than the properties +on the Bug class. Additionally for the "created" field it is specified that it is of the Type "DATETIME", +which translates the YYYY-mm-dd HH:mm:ss Database format into a PHP DateTime instance and back. + +After the field definitions the two qualified references to the user entity are defined. They are created by +the `many-to-one` tag. The class name of the related entity has to be specified with the `target-entity` +attribute, which is enough information for the database mapper to access the foreign-table. The +`join-column` tags are used to specifiy how the foreign and referend columns are named, an information +Doctrine needs to construct joins between those two entities correctly. + +The last missing property is the `Bug::$products` collection. It holds all products where the specific +bug is occouring in. Again you have to define the `target-entity` and `field` attributes on the `many-to-many` +tag. Furthermore you have to specifiy the details of the many-to-many join-table and its foreign key columns. +The definition is rather complex, however relying on the XML auto-completion I got it working easily, although +I forget the schema details all the time. + +The last missing definition is that of the User entity: + + [xml] + + + + + + + + + + + + + + + + +Here are some new things to mention about the `one-to-many` tags. Remember that we discussed about +the inverse and owning side. Now both reportedBugs and assignedBugs are inverse relations, +which means the join details have already been defined on the owning side. Therefore we only +have to specify the property on the Bug class that holds the owning sides. + +This example has a fair overview of the most basic features of the metadata definition language. + +## Obtaining the EntityManager + +Doctrine's public interface is the EntityManager, it provides the access point to the complete +lifecycle management of your entities and transforms entities from and back to persistence. You +have to configure and create it to use your entities with Doctrine 2. I will show the configuration +steps and then discuss them step by step: + + [php] + // Setup Autoloader (1) + require '/path/to/lib/Doctrine/Common/ClassLoader.php'; + $loader = new Doctrine\Common\ClassLoader("Doctrine", '/path/to/Doctrine/trunk/lib/'); + $loader->register(); + + $config = new Doctrine\ORM\Configuration(); // (2) + + // Proxy Configuration (3) + $config->setProxyDir(__DIR__.'/lib/MyProject/Proxies'); + $config->setProxyNamespace('MyProject\Proxies'); + $config->setAutoGenerateProxyClasses((APPLICATION_ENV == "development")); + + // Mapping Configuration (4) + $driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings"); + $config->setMetadataDriverImpl($driverImpl); + + // Caching Configuration (5) + if (APPLICATION_ENV == "develoment") { + $cache = new \Doctrine\Common\Cache\ArayCache(); + } else { + $cache = new \Doctrine\Common\Cache\ApcCache(); + } + $config->setMetadataCacheImpl($cache); + $config->setQueryCacheImpl($cache); + + // database configuration parameters (6) + $conn = array( + 'driver' => 'pdo_sqlite', + 'path' => __DIR__ . '/db.sqlite', + ); + + // obtaining the entity manager (7) + $evm = new Doctrine\Common\EventManager() + $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config, $evm); + +The first block sets up the autoloading capabilities of Doctrine. I am registering the Doctrine +namespace to the given path. To add your own namespace you can instantiate another `CloassLoader` +with different namespace and path arguments. There is no requirement to use the Doctrine `ClassLoader` +for your autoloading needs, you can use whatever suits you best. + +The second block contains of the instantiation of the ORM Configuration object. Besides the +configuration shown in the next blocks there are several others with are all explained +in the [Configuration section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). + +The Proxy Configuration is a required block for your application, you have to specifiy where +Doctrine writes the PHP code for Proxy Generation. Proxies are children of your entities generated +by Doctrine to allow for type-safe lazy loading. We will see in a later chapter how exactly this works. +Besides the path to the proxies we also specifiy which namespace they will reside under aswell as +a flag `autoGenerateProxyClasses` indicating that proxies should be re-generated on each request, +which is recommended for development. In production this should be prevented at all costs, +the proxy class generation can be quite costly. + +The fourth block contains the mapping driver details. We will use XML Mapping in this example, so +we configure the `XmlDriver` instance with a path to mappings configuration folder where we put +the Bug.dcm.xml, Product.dcm.xml and User.dcm.xml. + +In the 5th block the caching configuration is set. In production we use caching only on a per request-basis +using the ArrayCache. In production it is literally required to use Apc, Memcache or XCache to get the full +speed out of Doctrine. Internally Doctrine uses caching heavily for the Metadata and DQL Query Language +so make sure you use a caching mechanism. + +The 6th block shows the configuration options required to connect to a database, in my case a file-based +sqlite database. All the configuration options for all the shipped drivers are given in the [DBAL Configuration +section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/dbal). + +The last block shows how the `EntityManager` is obtained from a factory method, Here we also pass +in an `EventManager` instance which is optional. However using the EventManager you can hook in to the lifecycle +of entities, which is a common use-case, so you know how to configure it already. + +## Generating the Database Schema + +Now that we have defined the Metadata Mappings and bootstrapped the EntityManager +we want to generate the relational database schema from it. +Doctrine has a Command-Line-Interface that allows you to access the SchemaTool, a component that generates +the required tables to work with the metadata. + +For the commandline tool to work a cli-config.php file has to be present in the project root directry, +where you will execute the doctrine command. Its a fairly simple file: + + [php] + $cliConfig = new Doctrine\Common\Cli\Configuration(); + $cliConfig->setAttribute('em', $entityManager); + +You can then use your favorite console tool to call: + + [console] + doctrine@my-desktop> cd myproject/ + doctrine@my-desktop> doctrine orm:schema-tool --create + +During the development you probably need to re-create the database several times when changing the Entity +metadata. You can then either re-create the database, or use the update functionality: + + [console] + doctrine@my-desktop> doctrine orm:schema-tool --re-create + doctrine@my-desktop> doctrine orm:schema-tool --update + +The updating of databases uses a Diff Algorithm for a given Database Schema, a cornerstone of the `Doctrine\DBAL` +package, which can even be used without the Doctrine ORM package. However its not available in SQLite since +it does not support ALTER TABLE. + +## Writing Entities into the Database + +Having created the schema we can now start and save entities in the database. For starters we need a create user use-case: + + [php] + $newUsername = "beberlei"; + + $user = new User(); + $user->name = $newUsername; + + $entityManager->persist($user); + $entityManager->flush(); + +Having a user, he can create products: + + [php] + $newProductName = "My Product"; + + $product = new Product(); + $product->name = $newProductName; + + $entityManager->persist($product); + $entityManager->flush(); + +So what is happening in those two snippets? In both examples the class creation is pretty standard, the interesting +bits are the communication with the `EntityManager`. To notify the EntityManager that a new entity should be inserted +into the database you have to call `persist()`. However the EntityManager does not act on this, its merely notified. +You have to explicitly call `flush()` to have the EntityManager write those two entities to the database. + +You might wonder why does this distinction between persist notification and flush exist? Doctrine 2 uses the +UnitOfWork pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one single fast transaction, which +is executed when flushing. This way the write-performance is unbelievable fast. Also in more complex scenarios +than those two before you can request updates on many different entities and all flush them at once. + +Doctrine's UnitOfWork even detects entities that have been retrieved from the database and changed when calling +flush, so that you only have to keep track of those entities that are new or to be removed and pass them to +`EntityManager#persist()` and `EntityManager#remove()` respectively. + +We are now getting to the "Create a New Bug" requirement and the code for this scenario may look like this: + + [php] + $reporter = $entityManager->find("User", $theReporterId); + $engineer = $entityManager->find("User", $theDefaultEngineerId); + + $bug = new Bug(); + $bug->description = "Something does not work!"; + $bug->created = new DateTime("now"); + $bug->status = "NEW"; + + foreach ($productIds AS $productId) { + $product = $entityManager->find("Product", $productId); + $bug->assignToProduct($product); + } + + $bug->setReporter($reporter); + $bug->setEngineer($engineer); + + $entityManager->persist($bug); + $entityManager->flush(); + + echo "Your new Bug Id: ".$bug->id."\n"; + +This is the first contact with the read API of the EntityManager, showing that a call to +`EntityManager#find($name, $id)` returns a single instance of an entity queried by primary key. +Besides this we see the persist + flush pattern again to save the Bug into the database. + +See how simple relating Bug, Reporter, Engineer and Products is done by using the discussed methods +in the "A first prototype" section. The UnitOfWork will detect this relations when flush +is called and relate them in the database appropriately. + +## Queries for Application Use-Cases + +Using the previous examples we can fill up the database quite a bit, however we now need to discuss how to query the underlying +mapper for the required view representations. When opening the application, bugs can be paginated through a list-view, which is the first +read-only use-case: + + [php] + $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC"; + + $query = $entityManager->createQuery($dql); + $query->setMaxResults(30); + $bugs = $query->getResult(); + + foreach($bugs AS $bug) { + echo $bug->description." - ".$bug->created->format('d.m.Y')."\n"; + echo " Reported by: ".$bug->getReporter()->name."\n"; + echo " Assigned to: ".$bug->getEngineer()->name."\n"; + foreach($bug->getProducts() AS $product) { + echo " Platform: ".$product->name."\n"; + } + echo "\n"; + } + +The DQL Query in this example fetches the 30 most recent bugs with their respective engineer and reporter +in one single SQL statement. The console output of this script is then: + + Something does not work! - 02.04.2010 + Reported by: beberlei + Assigned to: beberlei + Platform: My Product + +> **NOTE** +> +> **Dql is not Sql** +> +> You may wonder why we start writing SQL at the beginning of this use-case. Don't we use an ORM to get rid +> of all the endless hand-writing of SQL? Doctrine introduces DQL which is best described +> as **object-query-language** and is a dialect of [OQL](http://en.wikipedia.org/wiki/Object_Query_Language) and +> similar to [HQL](http://www.hibernate.org) or [JPQL](http://en.wikipedia.org/wiki/Java_Persistence_Query_Language). +> It does not know the concept of columns and tables, but only those +> of Entity-Class and property. Using the Metadata we defined before it allows for very short distinctive +> and powerful queries. +> +> An important reason why DQL is favourable to the Query API of most ORMs is its similarity to SQL. The DQL language +> allows query constructs that most ORMs don't, GROUP BY even with HAVING, Subselects, Fetch-Joins of nested +> classes, mixed results with entities and scalar data such as COUNT() results and much more. Using +> DQL you should seldom come to the point where you want to throw your ORM into the dumpster, because it +> doesn't support some the more powerful SQL concepts. +> +> Besides handwriting DQL you can however also use the `QueryBuilder` retrieved by calling `$entityManager->createQueryBuilder()` +> which is a Query Object around the DQL language. +> +> As a last resort you can however also use Native SQL and a description of the result set to retrieve +> entities from the database. DQL boils down to a Native SQL statement and a `ResultSetMapping` instance itself. +> Using Native SQL you could even use stored procedures for data retrieval, or make use of advanced non-portable +> database queries like PostgreSql's recursive queries. + +The next Use-Case is displaying a Bug by primary key. This could be done using DQL as in the previous example with a where clause, +however there is a convenience method on the Entity Manager that handles loading by primary key, which we have already +seen in the write scenarios: + + [php] + $bug = $entityManager->find("Bug", (int)$theBugId); + +However we will soon see another problem with our entities using this approach. Try displaying the engineer's name: + + [php] + echo "Bug: ".$bug->description."\n"; + echo "Engineer: ".$bug->getEngineer()->name."\n"; + +It will be null! What is happening? It worked in the previous example, so it can't be a problem with the persistance +code of Doctrine. You walked in the public property trap. Since we only retrieved the bug by primary key both the +engineer and reporter are not immediately loaded from the database but are replaced by LazyLoading proxies. Sample +code of this proxy generated code can be found in the specified Proxy Directory, it looks like: + + [php] + namespace MyProject\Proxies; + + /** + * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. + */ + class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy + { + // .. lazy load code here + + public function addReportedBug($bug) + { + $this->_load(); + return parent::addReportedBug($bug); + } + + public function assignedToBug($bug) + { + $this->_load(); + return parent::assignedToBug($bug); + } + } + +See how upon each method call the proxy is lazily loaded from the database? Using public properties however +we never call a method and Doctrine has no way to hook into the PHP Engine to detect this access and trigger +the lazy load. We need to revise our entities, make all the properties private or protected and add getters +and setters to get a working example: + + [php] + echo "Bug: ".$bug->getDescription()."\n"; + echo "Engineer: ".$bug->getEngineer()->getName()."\n"; + + /** + Bug: Something does not work! + Engineer: beberlei + */ + +For the next use-case we want to retrieve the dashboard view, a list of all open bugs the user reported or +was assigned to. This will be achieved using DQL again, this time with some WHERE clauses and usage of bound parameters: + + [php] + $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ". + "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC"; + + $myBugs = $entityManager->createQuery($dql) + ->setParameter(1, $theUserId) + ->setMaxResults(15) + ->getResult(); + + foreach ($myBugs AS $bug) { + echo $bug->getDescription()."\n"; + } + +That is it for the read-scenarios of this example, we will continue with the last missing bit, engineers +being able to close a bug. + +## Updating Entities + +There is a single use-case missing from the requirements, Engineers should be able to close a bug. This looks like: + + [php] + $bug = $entityManager->find("Bug", (int)$theBugId); + $bug->close(); + + $entityManager->flush(); + +When retrieving the Bug from the database it is inserted into the IdentityMap inside the UnitOfWork of Doctrine. +This means your Bug with exactly this id can only exist once during the whole request no matter how often you +call `EntityManager#find()`. It even detects entities that are hydrated using DQL and are already present in +the Identity Map. + +When flush is called the EntityManager loops over all the entities in the identity map and performs a comparison +between the values originally retrieved from the database and those values the entity currently has. If at +least one of these properties is different the entity is scheduled for an UPDATE against the database. +Only the changed columns are updated, which offers a pretty good performance improvement compared to updating +all the properties. + +This tutorial is over here, I hope you had fun. Additional content will be added to this tutorial +incrementally, topics will include: + + * Entity Repositories + * More on Association Mappings + * Lifecycle Events triggered in the UnitOfWork + * Ordering of Collections + +Additional details on all the topics discussed here can be found in the respective manual chapters. diff --git a/orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt b/orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt new file mode 100644 index 000000000..4f295420d --- /dev/null +++ b/orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt @@ -0,0 +1,89 @@ + +This recipe will show you how to implement ArrayAccess for your domain objects in order to allow more uniform access, for example in templates. In these examples we will implement ArrayAccess on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. + +++ Option 1 + +In this implementation we will make use of PHPs highly dynamic nature to dynamically access properties of a subtype in a supertype at runtime. Note that this implementation has 2 main caveats: + +* It will not work with private fields +* It will not go through any getters/setters + +- + + [php] + abstract class DomainObject implements ArrayAccess + { + public function offsetExists($offset) { + return isset($this->$offset); + } + + public function offsetSet($offset, $value) { + $this->$offset = $value; + } + + public function offsetGet($offset) { + return $this->$offset; + } + + public function offsetUnset($offset) { + $this->$offset = null; + } + } + + +++ Option 2 + +In this implementation we will dynamically invoke getters/setters. Again we use PHPs dynamic nature to invoke methods on a subtype from a supertype at runtime. This implementation has the following caveats: + +* It relies on a naming convention +* The semantics of offsetExists can differ +* offsetUnset will not work with typehinted setters + +- + + [php] + abstract class DomainObject implements ArrayAccess + { + public function offsetExists($offset) { + // In this example we say that exists means it is not null + $value = $this->{"get$offset"}(); + return $value !== null; + } + + public function offsetSet($offset, $value) { + $this->{"set$offset"}($value); + } + + public function offsetGet($offset) { + return $this->{"get$offset"}(); + } + + public function offsetUnset($offset) { + $this->{"set$offset"}(null); + } + } + +++ Read-only + +You can slightly tweak option 1 or option 2 in order to make array access read-only. This will also circumvent some of the caveats of each option. Simply make offsetSet and offsetUnset throw an exception (i.e. BadMethodCallException). + + [php] + abstract class DomainObject implements ArrayAccess + { + public function offsetExists($offset) { + // option 1 or option 2 + } + + public function offsetSet($offset, $value) { + throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!"); + } + + public function offsetGet($offset) { + // option 1 or option 2 + } + + public function offsetUnset($offset) { + throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!"); + } + } + diff --git a/orm/cookbook/en/implementing-the-notify-changetracking-policy.txt b/orm/cookbook/en/implementing-the-notify-changetracking-policy.txt new file mode 100644 index 000000000..025375487 --- /dev/null +++ b/orm/cookbook/en/implementing-the-notify-changetracking-policy.txt @@ -0,0 +1,48 @@ + +The NOTIFY changetracking policy is the most effective changetracking policy provided by Doctrine but it requires some boilerplate code. This recipe will show you how this boilerplate code should look like. We will implement it on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. + +++ Implementing NotifyPropertyChanged + +The NOTIFY 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. + + [php] + use Doctrine\Common\NotifyPropertyChanged, + Doctrine\Common\PropertyChangedListener; + + abstract class DomainObject implements NotifyPropertyChanged + { + private $_listeners = array(); + + public function addPropertyChangedListener(PropertyChangedListener $listener) { + $this->_listeners[] = $listener; + } + + /** Notifies listeners of a change. */ + protected function _onPropertyChanged($propName, $oldValue, $newValue) { + if ($this->_listeners) { + foreach ($this->_listeners as $listener) { + $listener->propertyChanged($this, $propName, $oldValue, $newValue); + } + } + } + } + +Then, in each property setter of concrete, derived domain classes, you need to invoke _onPropertyChanged as follows to notify listeners: + + [php] + // Mapping not shown, either in annotations, xml or yaml as usual + class MyEntity extends DomainObject + { + private $data; + // ... other fields as usual + + public function setData($data) { + if ($data != $this->data) { // check: is it actually modified? + $this->_onPropertyChanged('data', $this->data, $data); + $this->data = $data; + } + } + } + +The check whether the new value is different from the old one is not mandatory but recommended. That way you can avoid unnecessary updates and also have full control over when you consider a property changed. + diff --git a/orm/cookbook/en/implementing-wakeup-or-clone.txt b/orm/cookbook/en/implementing-wakeup-or-clone.txt new file mode 100644 index 000000000..efdefa7c3 --- /dev/null +++ b/orm/cookbook/en/implementing-wakeup-or-clone.txt @@ -0,0 +1,63 @@ + +As explained in the [restrictions for entity classes in the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities), +it is usually not allowed for an entity to implement `__wakeup` or `__clone`, because Doctrine +makes special use of them. However, it is quite easy to make use of these methods in a safe way +by guarding the custom wakeup or clone code with an entity identity check, as demonstrated in the following sections. + +++ Safely implementing __wakeup + +To safely implement `__wakeup`, simply enclose your implementation code in an identity check +as follows: + + [php] + class MyEntity + { + private $id; // This is the identifier of the entity. + //... + + public function __wakeup() + { + // If the entity has an identity, proceed as normal. + if ($this->id) { + // ... Your code here as normal ... + } + // otherwise do nothing, do NOT throw an exception! + } + + //... + } + +++ Safely implementing __clone + +Safely implementing `__clone` is pretty much the same: + + [php] + class MyEntity + { + private $id; // This is the identifier of the entity. + //... + + public function __clone() + { + // If the entity has an identity, proceed as normal. + if ($this->id) { + // ... Your code here as normal ... + } + // otherwise do nothing, do NOT throw an exception! + } + + //... + } + +++ Summary + +As you have seen, it is quite easy to safely make use of `__wakeup` and `__clone` in your entities +without adding any really Doctrine-specific or Doctrine-dependant code. + +These implementations are possible and safe because when Doctrine invokes these methods, +the entities never have an identity (yet). Furthermore, it is possibly a good idea to check +for the identity in your code anyway, since it's rarely the case that you want to unserialize +or clone an entity with no identity. + + + diff --git a/orm/cookbook/en/integrating-with-codeigniter.txt b/orm/cookbook/en/integrating-with-codeigniter.txt new file mode 100644 index 000000000..23165176a --- /dev/null +++ b/orm/cookbook/en/integrating-with-codeigniter.txt @@ -0,0 +1,104 @@ +This is recipe for using Doctrine 2 in your [CodeIgniter](http://www.codeigniter.com) framework. + +Here is how to set it up: + +Make a CodeIgniter library that is both a wrapper and a bootstrap for Doctrine 2. + +++ Setting up the file structure + +Here are the steps: + +* Add a php file to your system/application/libraries folder called Doctrine.php. This is going to be your wrapper/bootstrap for the D2 entity manager. +* Put the Doctrine folder (the one that contains Common, DBAL, and ORM) inside that same libraries folder. +* Your system/application/libraries folder now looks like this: + + system/applications/libraries + -Doctrine + -Doctrine.php + -index.html + +* If you want, open your config/autoload.php file and autoload your Doctrine library. + + [php] + $autoload['libraries'] = array('doctrine'); + +++ Creating your Doctrine CodeIgniter library + +Now, here is what your Doctrine.php file should look like. Customize it to your needs. + + [php] + use Doctrine\Common\ClassLoader, + Doctrine\ORM\Configuration, + Doctrine\ORM\EntityManager, + Doctrine\Common\Cache\ArrayCache, + Doctrine\DBAL\Logging\EchoSqlLogger; + + class Doctrine { + + public $em = null; + + public function __construct() + { + // load database configuration from CodeIgniter + require_once APPPATH.'config/database.php'; + + // Set up class loading. You could use different autoloaders, provided by your favorite framework, + // if you want to. + require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php'; + + $doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries'); + $doctrineClassLoader->register(); + $entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" )); + $entitiesClassLoader->register(); + $proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies'); + $proxiesClassLoader->register(); + + // Set up caches + $config = new Configuration; + $cache = new ArrayCache; + $config->setMetadataCacheImpl($cache); + $config->setQueryCacheImpl($cache); + + // Proxy configuration + $config->setProxyDir(APPPATH.'/models/proxies'); + $config->setProxyNamespace('Proxies'); + + // Set up logger + $logger = new EchoSqlLogger; + $config->setSqlLogger($logger); + + $config->setAutoGenerateProxyClasses( TRUE ); + + // Database connection information + $connectionOptions = array( + 'driver' => 'pdo_mysql', + 'user' => $db['default']['username'], + 'password' => $db['default']['password'], + 'host' => $db['default']['hostname'], + 'dbname' => $db['default']['database'] + ); + + // Create EntityManager + $this->em = EntityManager::create($connectionOptions, $config); + } + } + +Please note that this is a development configuration; for a production system you'll want to use a real caching system like APC, get rid of EchoSqlLogger, and turn off autoGenerateProxyClasses. + +For more details, consult the [Doctrine 2 Configuration documentation](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). + +++ Now to use it + +Whenever you need a reference to the entity manager inside one of your controllers, views, or models you can do this: + + [php] + $em = $this->doctrine->em; + +That's all there is to it. Once you get the reference to your EntityManager do your Doctrine 2.0 voodoo as normal. + +Note: If you do not choose to autoload the Doctrine library, you will need to put this line before you get a reference to it: + + [php] + $this->load->library('doctrine'); + +Good luck! diff --git a/orm/cookbook/en/validation-of-entities.txt b/orm/cookbook/en/validation-of-entities.txt new file mode 100644 index 000000000..739a608d2 --- /dev/null +++ b/orm/cookbook/en/validation-of-entities.txt @@ -0,0 +1,114 @@ +Doctrine 2 does not ship with any internal validators, the reason being that +we think all the frameworks out there already ship with quite decents ones that can be integrated +into your Domain easily. What we offer are hooks to execute any kind of validation. + +> **Note** +> You don't need to validate your entities in the lifecycle events. Its only +> one of many options. Of course you can also perform validations in value setters +> or any other method of your entities that are used in your code. + +Entities can register lifecycle evnt methods with Doctrine that are called on +different occasions. For validation we would need to hook into the +events called before persisting and updating. Even though we don't support +validation out of the box, the implementation is even simpler than in Doctrine 1 +and you will get the additional benefit of being able to re-use your validation +in any other part of your domain. + +Say we have an `Order` with several `OrderLine` instances. We never want to +allow any customer to order for a larger sum than he is allowed to: + + [php] + class Order + { + public function assertCustomerAllowedBuying() + { + $orderLimit = $this->customer->getOrderLimit(); + + $amount = 0; + foreach ($this->orderLines AS $line) { + $amount += $line->getAmount(); + } + + if ($amount > $orderLimit) { + throw new CustomerOrderLimitExceededException(); + } + } + } + +Now this is some pretty important piece of business logic in your code, enforcing +it at any time is important so that customers with a unknown reputation don't +owe your business too much money. + +We can enforce this constraint in any of the metadata drivers. First Annotations: + + [php] + /** + * @Entity + * @HasLifecycleCallbacks + */ + class Order + { + /** + * @PrePersist @PreUpdate + */ + public function assertCustomerAllowedBuying() {} + } + +In XML Mappings: + + [xml] + + + + + + + + + +YAML needs some little change yet, to allow multiple lifecycle events for one method, +this will happen before Beta 1 though. + +Now validation is performed whenever you call `EntityManager#persist($order)` +or when you call `EntityManager#flush()` and an order is about to be updated. +Any Exception that happens in the lifecycle callbacks will be catched by the +EntityManager and the current transaction is rolled back. + +Of course you can do any type of primitive checks, not null, email-validation, string size, +integer and date ranges in your validation callbacks. + + [php] + class Order + { + /** + * @PrePersist @PreUpdate + */ + public function validate() + { + if (!($this->plannedShipDate instanceof DateTime)) { + throw new ValidateException(); + } + + if ($this->plannedShipDate->format('U') < time()) { + throw new ValidateException(); + } + + if ($this->customer == null) { + throw new OrderRequiresCustomerException(); + } + } + } + +What is nice about lifecycle events is, you can also re-use the methods at other places +in your domain, for example in combination with your form library. +Additionally there is no limitation in the number of methods you register +on one particular event, i.e. you can register multiple methods for validation in "PrePersist" +or "PreUpdate" or mix and share them in any combinations between those two events. + +There is no limit to what you can and can't validate in "PrePersist" and "PreUpdate" aslong as +you don't create new entity instances. This was already discussed in the previous +blog post on the Versionable extension, which requires another type of event called "onFlush". + +Further readings: + +* [Doctrine 2 Manual: Events](http://www.doctrine-project.org/documentation/manual/2_0/en/events#lifecycle-events) \ No newline at end of file From 1e7193134ae103146c33e1f2eb76aca07becd83f Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Tue, 6 Apr 2010 14:36:40 -0400 Subject: [PATCH 003/430] - --- cookbook/en.txt | 8 + cookbook/en/dql-custom-walkers.txt | 174 ++++ cookbook/en/dql-user-defined-functions.txt | 198 +++++ cookbook/en/getting-started-xml-edition.txt | 704 +++++++++++++++ ...menting-arrayaccess-for-domain-objects.txt | 89 ++ ...nting-the-notify-changetracking-policy.txt | 48 ++ cookbook/en/implementing-wakeup-or-clone.txt | 63 ++ cookbook/en/integrating-with-codeigniter.txt | 104 +++ cookbook/en/validation-of-entities.txt | 114 +++ dbal/manual/en.txt | 1 - dbal/manual/en/dbal.txt | 245 ------ manual/en.txt | 20 + manual/en/annotations-reference.txt | 557 ++++++++++++ manual/en/architecture.txt | 81 ++ manual/en/association-mapping.txt | 429 ++++++++++ manual/en/basic-mapping.txt | 279 ++++++ manual/en/batch-processing.txt | 122 +++ manual/en/best-practices.txt | 77 ++ manual/en/caching.txt | 350 ++++++++ manual/en/configuration.txt | 407 +++++++++ manual/en/dql-doctrine-query-language.txt | 803 ++++++++++++++++++ manual/en/events.txt | 304 +++++++ manual/en/improving-performance.txt | 25 + manual/en/inheritance-mapping.txt | 141 +++ manual/en/introduction.txt | 222 +++++ manual/en/native-sql.txt | 196 +++++ manual/en/query-builder.txt | 356 ++++++++ manual/en/tools.txt | 192 +++++ manual/en/transactions-and-concurrency.txt | 124 +++ manual/en/working-with-objects.txt | 445 ++++++++++ manual/en/xml-mapping.txt | 83 ++ manual/en/yaml-mapping.txt | 66 ++ migrations/manual/en.txt | 0 rest/manual/en.txt | 0 34 files changed, 6781 insertions(+), 246 deletions(-) create mode 100644 cookbook/en.txt create mode 100644 cookbook/en/dql-custom-walkers.txt create mode 100644 cookbook/en/dql-user-defined-functions.txt create mode 100644 cookbook/en/getting-started-xml-edition.txt create mode 100644 cookbook/en/implementing-arrayaccess-for-domain-objects.txt create mode 100644 cookbook/en/implementing-the-notify-changetracking-policy.txt create mode 100644 cookbook/en/implementing-wakeup-or-clone.txt create mode 100644 cookbook/en/integrating-with-codeigniter.txt create mode 100644 cookbook/en/validation-of-entities.txt delete mode 100644 dbal/manual/en.txt delete mode 100644 dbal/manual/en/dbal.txt create mode 100644 manual/en.txt create mode 100644 manual/en/annotations-reference.txt create mode 100644 manual/en/architecture.txt create mode 100644 manual/en/association-mapping.txt create mode 100644 manual/en/basic-mapping.txt create mode 100644 manual/en/batch-processing.txt create mode 100644 manual/en/best-practices.txt create mode 100644 manual/en/caching.txt create mode 100644 manual/en/configuration.txt create mode 100755 manual/en/dql-doctrine-query-language.txt create mode 100644 manual/en/events.txt create mode 100644 manual/en/improving-performance.txt create mode 100644 manual/en/inheritance-mapping.txt create mode 100644 manual/en/introduction.txt create mode 100644 manual/en/native-sql.txt create mode 100644 manual/en/query-builder.txt create mode 100644 manual/en/tools.txt create mode 100644 manual/en/transactions-and-concurrency.txt create mode 100644 manual/en/working-with-objects.txt create mode 100644 manual/en/xml-mapping.txt create mode 100644 manual/en/yaml-mapping.txt delete mode 100644 migrations/manual/en.txt delete mode 100644 rest/manual/en.txt diff --git a/cookbook/en.txt b/cookbook/en.txt new file mode 100644 index 000000000..db1b17496 --- /dev/null +++ b/cookbook/en.txt @@ -0,0 +1,8 @@ ++ Getting Started XML-Edition ++ Implementing ArrayAccess for domain objects ++ Implementing the NOTIFY changetracking policy ++ Validation of Entities ++ Implementing wakeup or clone ++ Integrating with CodeIgniter ++ DQL Custom Walkers ++ DQL User Defined Functions \ No newline at end of file diff --git a/cookbook/en/dql-custom-walkers.txt b/cookbook/en/dql-custom-walkers.txt new file mode 100644 index 000000000..48d4840a4 --- /dev/null +++ b/cookbook/en/dql-custom-walkers.txt @@ -0,0 +1,174 @@ +# Extending DQL in Doctrine 2: Custom AST Walkers + +The Doctrine Query Language (DQL) is a propriotary sql-dialect that substitutes +tables and columns for Entity names and their fields. Using DQL you write a query +against the database using your entities. With the help of the metadata you +can write very concise, compact and powerful queries that are then translated +into SQL by the Doctrine ORM. + +In Doctrine 1 the DQL language was not implemented using a real parser. This +made modifications of the DQL by the user impossible. Doctrine 2 in constrast +has a real parser for the DQL language, which transforms the DQL statement +into an [Abstract Syntax Tree](http://en.wikipedia.org/wiki/Abstract_syntax_tree) +and generates the appropriate SQL statement for it. Since this process is +deterministic Doctrine heavily caches the SQL that is generated from any given DQL query, +which reduces the performance overhead of the parsing process to zero. + +You can modify the Abstract syntax tree by hooking into DQL parsing process +by adding a Custom Tree Walker. A walker is an interface that walks each +node of the Abstract syntax tree, thereby generating the SQL statement. + +There are two types of custom tree walkers that you can hook into the DQL parser: + +- An output walker. This one actually generates the SQL, and there is only ever one of them. We implemented the default SqlWalker implementation for it. +- A tree walker. There can be many tree walkers, they cannot generate the sql, however they can modify the AST before its rendered to sql. + +Now this is all awfully technical, so let me come to some use-cases fast +to keep you motivated. Using walker implementation you can for example: + +* Modify the AST to generate a Count Query to be used with a paginator for any given DQL query. +* Modify the Output Walker to generate vendor-specific SQL (instead of ANSI). +* Modify the AST to add additional where clauses for specific entities (example ACL, country-specific content...) +* Modify the Output walker to pretty print the SQL for debugging purposes. + +In this cookbook-entry I will show examples on the first two points. There +are probably much more use-cases. + +## Generic count query for pagination + +Say you have a blog and posts all with one category and one author. A query +for the front-page or any archive page might look something like: + + [sql] + SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... + +Now in this query the blog post is the root entity, meaning its the one that +is hydrated directly from the query and returned as an array of blog posts. +In contrast the comment and author are loaded for deeper use in the object tree. + +A pagination for this query would want to approximate the number of posts that +match the WHERE clause of this query to be able to predict the number of pages +to show to the user. A draft of the DQL query for pagination would look like: + + [sql] + SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... + +Now you could go and write each of these queries by hand, or you can use a tree +walker to modify the AST for you. Lets see how the API would look for this use-case: + + [php] + $pageNum = 1; + $query = $em->createQuery($dql); + $query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20); + + $totalResults = Paginate::count($query); + $results = $query->getResult(); + +The `Paginate::count(Query $query)` looks like: + + [php] + class Paginate + { + static public function count(Query $query) + { + /* @var $countQuery Query */ + $countQuery = clone $query; + + $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker')); + $countQuery->setFirstResult(null)->setMaxResults(null); + + return $countQuery->getSingleScalarResult(); + } + } + +It clones the query, resets the limit clause first and max results and registers the `CountSqlWalker` +customer tree walker which will modify the AST to execute a count query. The walkers +implementation is: + + [php] + class CountSqlWalker extends TreeWalkerAdapter + { + /** + * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. + * + * @return string The SQL. + */ + public function walkSelectStatement(SelectStatement $AST) + { + $parent = null; + $parentName = null; + foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) { + if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { + $parent = $qComp; + $parentName = $dqlAlias; + break; + } + } + + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, array( + $parent['metadata']->getSingleIdentifierFieldName()) + ); + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + + $AST->selectClause->selectExpressions = array( + new SelectExpression( + new AggregateExpression('count', $pathExpression, true), null + ) + ); + } + } + +This will delete any given select expressions and replace them with a distinct count +query for the root entities primary key. This will only work if your entity has +only one identifier field (composite keys won't work). + +## Modify the Output Walker to generate Vendor specific SQL + +Most RMDBS have vendor-specific features for optimizing select query +execution plans. You can write your own output walker to introduce certain +keywords using the Query Hint API. A query hint can be set via `Query::setHint($name, $value)` +as shown in the previous example with the `HINT_CUSTOM_TREE_WALKERS` query hint. + +We will implement a custom Output Walker that allows to specifiy the SQL_NO_CACHE +query hint. + + [php] + $dql = "SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ..."; + $query = $m->createQuery($dql); + $query->setQueryHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker'); + $query->setQueryHint("mysqlWalker.sqlNoCache", true); + $results = $query->getResult(); + +Our `MysqlWalker` will extend the default `SqlWalker`. We will modify the generation +of the SELECT clause, adding the SQL_NO_CACHE on those queries that need it: + + [php] + class MysqlWalker extends SqlWalker + { + /** + * Walks down a SelectClause AST node, thereby generating the appropriate SQL. + * + * @param $selectClause + * @return string The SQL. + */ + public function walkSelectClause($selectClause) + { + $sql = parent::walkSelectClause($selectClause); + + if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) { + if ($selectClause->isDistinct) { + $sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql); + } else { + $sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql); + } + } + + return $sql; + } + } + +Writing extensions to the Output Walker requires a very deep understanding +of the DQL Parser and Walkers, but may offer your huge benefits with using vendor specific +features. This would still allow you write DQL queries instead of NativeQueries +to make use of vendor specific features. diff --git a/cookbook/en/dql-user-defined-functions.txt b/cookbook/en/dql-user-defined-functions.txt new file mode 100644 index 000000000..d738617c4 --- /dev/null +++ b/cookbook/en/dql-user-defined-functions.txt @@ -0,0 +1,198 @@ +By default DQL supports a limited subset of all the vendor-specific SQL functions +common between all the vendors. However in many cases once you have decided on a +specific database vendor, you will never change it during the life of your project. +This decision for a specific vendor potentially allows you to make use of powerful +SQL features that are unique to the vendor. + +> **Note** +> +> It is worth to mention that Doctrine 2 also allows you to handwrite your SQL instead of extending +> the DQL parser, which is sort of an advanced extension point. You can map arbitrary SQL to your +> objects and gain access to vendor specific functionalities using the `EntityManager#createNativeQuery()` API. + +The DQL Parser has hooks to register functions that can then be used in your DQL queries and transformed into SQL, +allowing to extend Doctrines Query capabilities to the vendors strength. This post explains the +Used-Defined Functions API (UDF) of the Dql Parser and shows some examples to give you +some hints how you would extend DQL. + +There are three types of functions in DQL, those that return a numerical value, +those that return a string and those that return a Date. Your custom method +has to be registered as either one of those. The return type information +is used by the DQL parser to check possible syntax errors during the parsing +process, for example using a string function return value in a math expression. + +## Registering your own DQL functions + +You can register your functions adding them to the ORM configuration: + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->addCustomStringFunction($name, $class); + $config->addCustomNumericFunction($name, $class); + $config->addCustomDatetimeFunction($name, $class); + + $em = EntityManager::create($dbParams, $config); + +The `$name` is the name the function will be referred to in the DQL query. `$class` is a +string of a class-name which has to extend `Doctrine\ORM\Query\Node\FunctionNode`. +This is a class that offers all the necessary API and methods to implement +a UDF. + +In this post we will implement some MySql specific Date calculation methods, +which are quite handy in my opinion: + +## Date Diff + +[Mysql's DateDiff function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff) +takes two dates as argument and calculates the difference in days with `date1-date2`. + +The DQL parser is a top-down recursive descent parser to generate the +Abstract-Syntax Tree (AST) and uses a TreeWalker approach to generate the appropriate +SQL from the AST. This makes reading the Parser/TreeWalker code managable +in a finite amount of time. + +The `FunctionNode` class I referred to earlier requires you to implement +two methods, one for the parsing process (obviously) called `parse` and +one for the TreeWalker process called `getSql()`. I show you the code for +the DateDiff method and discuss it step by step: + + [php] + /** + * DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" + */ + class DateDiff extends FunctionNode + { + // (1) + public $firstDateExpression = null; + public $secondDateExpression = null; + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); // (2) + $parser->match(Lexer::T_OPEN_PARENTHESIS); // (3) + $this->firstDateExpression = $parser->ArithmeticPrimary(); // (4) + $parser->match(Lexer::T_COMMA); // (5) + $this->secondDateExpression = $parser->ArithmeticPrimary(); // (6) + $parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3) + } + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'DATEDIFF(' . + $this->firstDateExpression->dispatch($sqlWalker) . ', ' . + $this->secondDateExpression->dispatch($sqlWalker) . + ')'; // (7) + } + } + +The Parsing process of the DATEDIFF function is going to find two expressions +the date1 and the date2 values, whose AST Node representations will be saved +in the variables of the DateDiff FunctionNode instance at (1). + +The parse() method has to cut the function call "DATEDIFF" and its argument +into pieces. Since the parser detects the function using a lookahead the +T_IDENTIFIER of the function name has to be taken from the stack (2), followed +by a detection of the arguments in (4)-(6). The opening and closing parenthesis +have to be detected also. This happens during the Parsing process and leads +to the generation of a DateDiff FunctionNode somewhere in the AST of the +dql statement. + +The `ArithmeticPrimary` method call is the most common denominator of valid +EBNF tokens taken from the [DQL EBNF grammer](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf) +that matches our requirements for valid input into the DateDiff Dql function. +Picking the right tokens for your methods is a tricky business, but the EBNF +grammer is pretty helpful finding it, as is looking at the Parser source code. + +Now in the TreeWalker process we have to pick up this node and generate SQL +from it, which apprently is quite easy looking at the code in (7). Since +we don't know which type of AST Node the first and second Date expression +are we are just dispatching them back to the SQL Walker to generate SQL from +and then wrap our DATEDIFF function call around this output. + +Now registering this DateDiff FunctionNode with the ORM using: + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff'); + +We can do fancy stuff like: + + [sql] + SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7 + +## Date Add + +Often useful it the ability to do some simple date calculations in your DQL query +using [MySql's DATE_ADD function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add). + +I'll skip the bla and show the code for this function: + + [php] + /** + * DateAddFunction ::= + * "DATE_ADD" "(" ArithmeticPrimary ", INTERVAL" ArithmeticPrimary Identifier ")" + */ + class DateAdd extends FunctionNode + { + public $firstDateExpression = null; + public $intervalExpression = null; + public $unit = null; + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->firstDateExpression = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_COMMA); + $parser->match(Lexer::T_IDENTIFIER); + + $this->intervalExpression = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_IDENTIFIER); + + /* @var $lexer Lexer */ + $lexer = $parser->getLexer(); + $this->unit = $lexer->token['value']; + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'DATE_ADD(' . + $this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' . + $this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit . + ')'; + } + } + +The only difference compared to the DATEDIFF here is, we additionally need the `Lexer` to access +the value of the `T_IDENTIFIER` token for the Date Interval unit, for example the MONTH in: + + [sql] + SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created + +The above method now only supports the specification using `INTERVAL`, to also +allow a real date in DATE_ADD we need to add some decision logic to the parsing +process (makes up for a nice excercise). + +Now as you see, the Parsing process doesn't catch all the possible SQL errors, +here we don't match for all the valid inputs for the interval unit. +However where necessary we rely on the database vendors SQL parser to show us further errors +in the parsing process, for example if the Unit would not be one of the supported values +by MySql. + +## Conclusion + +Now that you all know how you can implement vendor specific SQL functionalities in DQL, +we would be excited to see user extensions that add vendor specific function packages, +for example more math functions, XML + GIS Support, Hashing functions and so on. + +For 2.0 we will come with the current set of functions, however for a future +version we will re-evaluate if we can abstract even more vendor sql functions +and extend the DQL languages scope. + +Code for this Extension to DQL and other Doctrine Extensions can be found +[in my Github DoctrineExtensions repository](http://github.com/beberlei/DoctrineExtensions). \ No newline at end of file diff --git a/cookbook/en/getting-started-xml-edition.txt b/cookbook/en/getting-started-xml-edition.txt new file mode 100644 index 000000000..b9a9632d1 --- /dev/null +++ b/cookbook/en/getting-started-xml-edition.txt @@ -0,0 +1,704 @@ +Doctrine 2 is a project that aims to handle the persistence of the domain model in a non-interfering way. +The Data Mapper pattern is at the heart of this project, aiming for a complete separation of the domain/business logic +from the persistence in a relational database management system. The benefit of Doctrine for the programmer is the +possibility can focus soley on the business and worry about persistence only as a secondary task. This doesn't mean +persistence is not important to Doctrine 2, however it is our believe that there are considerable benefits for object-oriented +programming, if persistence and entities are kept perfectly seperated. + +## What are Entities? + +Entities are leightweight PHP Objects that don't need to extend any abstract base class or interface. +An entity class must not be final or contain final methods. Additionally it must not implement __clone +nor __wakeup or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). + +An entity contains persistable properties. A persistable property is an instance variable of the entity +that contains the data which is persisted and retrieved by Doctrine's data mapping capabilities. + +## An Example Model: Bug Tracker + +For this Getting Started Guide for Doctrine we will implement the Bug Tracker domain model from the [Zend_Db_Table](http://framework.zend.com/manual/en/zend.db.table.html) +documentation. Reading that documentat we can extract the requirements to be: + +* A Bugs has a description, creation date, status, reporter and engineer +* A bug can occour on different products (platforms) +* Products have a name. +* Bug Reporter and Engineers are both Users of the System. +* A user can create new bugs. +* The assigned engineer can close a bug. +* A user can see all his reported or assigned bugs. +* Bugs can be paginated through a list-view. + +> **WARNING** +> +> This tutorial is incrementally building up your Doctrine 2 knowledge and even lets you make some mistakes, to +> show some common pitfalls in mapping Entities to a database. Don't blindly copy-paste the examples here, it +> is not production ready without the additional comments and knowledge this tutorial teaches. + +## A first prototype + +A first simplified design for this domain model might look like the following set of classes: + + [php] + class Bug + { + public $id; + public $description; + public $created; + public $status; + public $products = array(); + public $reporter; + public $engineer; + } + class Product + { + public $id; + public $name; + } + class User + { + public $id; + public $name; + public $reportedBugs = array(); + public $assignedBugs = array(); + } + +> **WARNING** +> +> This is only a prototype, please don't use public properties with Doctrine 2 at all, +> the "Queries for Application Use-Cases" section shows you why. In combination with proxies +> public properties can make up for pretty nasty bugs. + +Because we will focus on the mapping aspect, no effort is being made to encapsulate the business logic in this example. +All peristable properties are public in visibility. We will soon see that this is not the best solution in combination +with Doctrine 2, one restriction that actually forces you to encapsulate your properties. For persistence Doctrine 2 +actually uses Reflection to access the values in all your entities properties. + +Many of the fields are single scalar values, for example the 3 ID fields of the entities, their names, description, +status and change dates. Doctrine 2 can easily handle these single values as can any other ORM. From a point of our +domain model they are ready to be used right now and we will see at a later stage how they are mapped to the database. + +There are also several references between objects in this domain model, whose semantics are discussed case by case at this point +to explain how Doctrine handles them. In general each OneToOne or ManyToOne Relation in the Database is replaced by an +instance of the related object in the domain model. Each OneToMany or ManyToMany Relation is replaced by a collection +of instances in the domain model. + +If you think this through carefully you realize Doctrine 2 will load up the complete database in memory if you access +one object. However by default Doctrine generates Lazy Load proxies of entities or collections of all the relations +that haven't been explicitly retrieved from the database yet. + +To be able to use lazyload with collections, simple PHP arrays have to be replaced by a generic collection +interface Doctrine\Common\Collections\Collection which tries to act as array as much as possible using ArrayAccess, +IteratorAggregate and Countable interfaces. The class \Doctrine\Common\Collections\ArrayCollection is the most simple +implementation of this interface. + +Now that we know this, we have to clear up our domain model to cope with the assumptions about related collections: + + [php] + use Doctrine\Common\Collections\ArrayCollection; + + class Bug + { + public $products = null; + + public function __construct() + { + $this->products = new ArrayCollection(); + } + } + + class User + { + public $reportedBugs = null; + public $assignedBugs = null; + + public function __construct() + { + $this->reportedBugs = new ArrayCollection(); + $this->assignedBugs = new ArrayCollection(); + } + } + +Whenever an entity is recreated from the database, an Collection implementation of the type +Doctrine\ORM\PersistantCollection is injected into your entity instead of an array. Compared +to the ArrayCollection this implementation helps the Doctrine ORM understand the changes that +have happend to the collection which are noteworthy for persistence. + +> **Warning** +> Lazy load proxies always contain an instance of Doctrine's EntityManager and all its dependencies. Therefore a var_dump() +> will possibly dump a very large recursive structure which is impossible to render and read. You have to use +> `Doctrine\Common\Util\Debug::dump()` to restrict the dumping to a human readable level. Additionally you should be aware +> that dumping the EntityManager to a Browser may take several minutes, and the Debug::dump() method just ignores any +> occurences of it in Proxy instances. + +Because we only work with collections for the references we must be careful to implement a bidirectional reference in +the domain model. The concept of owning or inverse side of a relation is central to this notion and should always +be kept in mind. The following assumptions are made about relations and have to be followed to be able to work with Doctrine 2. +These assumptions are not unique to Doctrine 2 but are best practices in handling database relations and Object-Relational Mapping. + +* Changes to Collections are saved or updated, when the entity on the *ownin*g side of the collection is saved or updated. +* Saving an Entity at the inverse side of a relation never triggers a persist operation to changes to the collection. +* In a one-to-one relation the entity holding the foreign key of the related entity on its own database table is *always* the owning side of the relation. +* In a many-to-many relation, both sides can be the owning side of the relation. However in a bi-directional many-tomany relation only one is allowed to be. +* In a many-to-one relation the Many-side is the owning side by default, because it holds the foreign key. +* The OneToMany side of a relation is inverse by default, since the foreign key is saved on the Many side. A OneToMany relation can only be the owning side, if its implemented using a ManyToMany relation with join table and restricting the one side to allow only UNIQUE values per database constraint. + +> **Important** +> +> Consistency of bi-directional references on the inverse side of a relation have to be managed in userland application code. +> Doctrine cannot magically update your collections to be consistent. + +In the case of Users and Bugs we have references back and forth to the assigned and reported bugs from a user, +making this relation bi-directional. We have to change the code to ensure consistency of the bi-directional reference: + + [php] + class Bug + { + protected $engineer; + protected $reporter; + + public function setEngineer($engineer) + { + $engineer->assignedToBug($this); + $this->engineer = $engineer; + } + + public function setReporter($reporter) + { + $reporter->addReportedBug($this); + $this->reporter = $reporter; + } + + public function getEngineer() + { + return $this->engineer; + } + + public function getReporter() + { + return $this->reporter; + } + } + class User + { + public function addReportedBug($bug) + { + $this->reportedBugs[] = $bug; + } + + public function assignedToBug($bug) + { + $this->assignedBugs[] = $bug; + } + } + +I chose to name the inverse methods in past-tense, which should indicate that the actual assigning has already taken +place and the methods are only used for ensuring consistency of the references. You can see from `User::addReportedBug()` +and `User::assignedToBug()` that using this method in userland alone would not add the Bug to the collection of the owning +side in Bug::$reporter or Bug::$engineer. Using these methods and calling Doctrine for persistence would not update +the collections representation in the database. + +Only using `Bug::setEngineer()` or `Bug::setReporter()` correctly saves the relation information. We also set both collection +instance variables to protected, however with PHP 5.3's new features Doctrine is still able to use Reflection to set and get values +from protected and private properties. + +The `Bug::$reporter` and `Bug::$engineer` properties are Many-To-One relations, which point to a User. In a normalized +relational model the foreign key is saved on the Bug's table, hence in our object-relation model the Bug is at the owning +side of the relation. You should always make sure that the use-cases of your domain model should drive which side +is an inverse or owning one in your Doctrine mapping. In our example, whenever a new bug is saved or an engineer is assigned +to the bug, we don't want to update the User to persist the reference, but the Bug. +This is the case with the Bug being at the owning side of the relation. + +Bugs reference Products by a uni-directional ManyToMany relation in the database that points from from Bugs to Products. + + [php] + class Bug + { + protected $products = null; // Set protected for encapsulation + + public function assignToProduct($product) + { + $this->products[] = $product; + } + + public function getProducts() + { + return $this->products; + } + } + +We are now finished with the domain model given the requirements. From the simple model with public properties only +we had to do quite some work to get to a model where we encapsulated the references between the objects to make sure +we don't break its consistent state when using Doctrine. + +However up to now the assumptions Doctrine imposed on our business objects have not restricting us much in our domain +modelling capabilities. Actually we would have encapsulated access to all the properties anyways by using +object-oriented best-practices. + +## Metadata Mappings for our Entities + +Up to now we have only implemented our Entites as Data-Structures without actually telling Doctrine how to persist +them in the database. If perfect in-memory databases would exist, we could now finish the application using these entities +by implementing code to fullfil all the requirements. However the world isn't perfect and we have to persist our +entities in some storage to make sure we don't loose their state. Doctrine currently serves Relational Database Management Systems. +In the future we are thinking to support NoSQL vendors like CouchDb or MongoDb, however this is still far in the future. + +The next step for persistance with Doctrine is to describe the structure of our domain model entities to Doctrine +using a metadata language. The metadata language describes how entities, their properties and references should be +persisted and what constraints should be applied to them. + +Metadata for entities are loaded using a `Doctrine\ORM\Mapping\Driver\Driver` implementation and Doctrine 2 already comes +with XML, YAML and Annotations Drivers. In this Getting Started Guide I will use the XML Mapping Driver. I think XML +beats YAML because of schema validation, and my favorite IDE netbeans offers me auto-completion for the XML mapping files +which is awesome to work with and you don't have to look up all the different metadata mapping commands all the time. + +Since we haven't namespaced our three entities, we have to implement three mapping files called Bug.dcm.xml, +Product.dcm.xml and User.dcm.xml and put them into a distinct folder for mapping configurations. + +The first discussed definition will be for the Product, since it is the most simple one: + + [xml] + + + + + + + + + + + + +The toplevel `entity` definition tag specifies information about the class and table-name. The +primitive type `Product::$name` is defined as `field` attributes. The Id property is defined with the `id` tag. +The id has a `generator` tag nested inside which defines that the primary key generation mechanism +automatically uses the database platforms native id generation strategy, for example AUTO INCREMENT +in the case of MySql or Sequences in the case of PostgreSql and Oracle. + +We then go on specifying the definition of a Bug: + + [xml] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Here again we have the entity, id and primitive type definitions. +The column names are used from the Zend_Db_Table examples and have different names than the properties +on the Bug class. Additionally for the "created" field it is specified that it is of the Type "DATETIME", +which translates the YYYY-mm-dd HH:mm:ss Database format into a PHP DateTime instance and back. + +After the field definitions the two qualified references to the user entity are defined. They are created by +the `many-to-one` tag. The class name of the related entity has to be specified with the `target-entity` +attribute, which is enough information for the database mapper to access the foreign-table. The +`join-column` tags are used to specifiy how the foreign and referend columns are named, an information +Doctrine needs to construct joins between those two entities correctly. + +The last missing property is the `Bug::$products` collection. It holds all products where the specific +bug is occouring in. Again you have to define the `target-entity` and `field` attributes on the `many-to-many` +tag. Furthermore you have to specifiy the details of the many-to-many join-table and its foreign key columns. +The definition is rather complex, however relying on the XML auto-completion I got it working easily, although +I forget the schema details all the time. + +The last missing definition is that of the User entity: + + [xml] + + + + + + + + + + + + + + + + +Here are some new things to mention about the `one-to-many` tags. Remember that we discussed about +the inverse and owning side. Now both reportedBugs and assignedBugs are inverse relations, +which means the join details have already been defined on the owning side. Therefore we only +have to specify the property on the Bug class that holds the owning sides. + +This example has a fair overview of the most basic features of the metadata definition language. + +## Obtaining the EntityManager + +Doctrine's public interface is the EntityManager, it provides the access point to the complete +lifecycle management of your entities and transforms entities from and back to persistence. You +have to configure and create it to use your entities with Doctrine 2. I will show the configuration +steps and then discuss them step by step: + + [php] + // Setup Autoloader (1) + require '/path/to/lib/Doctrine/Common/ClassLoader.php'; + $loader = new Doctrine\Common\ClassLoader("Doctrine", '/path/to/Doctrine/trunk/lib/'); + $loader->register(); + + $config = new Doctrine\ORM\Configuration(); // (2) + + // Proxy Configuration (3) + $config->setProxyDir(__DIR__.'/lib/MyProject/Proxies'); + $config->setProxyNamespace('MyProject\Proxies'); + $config->setAutoGenerateProxyClasses((APPLICATION_ENV == "development")); + + // Mapping Configuration (4) + $driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings"); + $config->setMetadataDriverImpl($driverImpl); + + // Caching Configuration (5) + if (APPLICATION_ENV == "develoment") { + $cache = new \Doctrine\Common\Cache\ArayCache(); + } else { + $cache = new \Doctrine\Common\Cache\ApcCache(); + } + $config->setMetadataCacheImpl($cache); + $config->setQueryCacheImpl($cache); + + // database configuration parameters (6) + $conn = array( + 'driver' => 'pdo_sqlite', + 'path' => __DIR__ . '/db.sqlite', + ); + + // obtaining the entity manager (7) + $evm = new Doctrine\Common\EventManager() + $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config, $evm); + +The first block sets up the autoloading capabilities of Doctrine. I am registering the Doctrine +namespace to the given path. To add your own namespace you can instantiate another `CloassLoader` +with different namespace and path arguments. There is no requirement to use the Doctrine `ClassLoader` +for your autoloading needs, you can use whatever suits you best. + +The second block contains of the instantiation of the ORM Configuration object. Besides the +configuration shown in the next blocks there are several others with are all explained +in the [Configuration section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). + +The Proxy Configuration is a required block for your application, you have to specifiy where +Doctrine writes the PHP code for Proxy Generation. Proxies are children of your entities generated +by Doctrine to allow for type-safe lazy loading. We will see in a later chapter how exactly this works. +Besides the path to the proxies we also specifiy which namespace they will reside under aswell as +a flag `autoGenerateProxyClasses` indicating that proxies should be re-generated on each request, +which is recommended for development. In production this should be prevented at all costs, +the proxy class generation can be quite costly. + +The fourth block contains the mapping driver details. We will use XML Mapping in this example, so +we configure the `XmlDriver` instance with a path to mappings configuration folder where we put +the Bug.dcm.xml, Product.dcm.xml and User.dcm.xml. + +In the 5th block the caching configuration is set. In production we use caching only on a per request-basis +using the ArrayCache. In production it is literally required to use Apc, Memcache or XCache to get the full +speed out of Doctrine. Internally Doctrine uses caching heavily for the Metadata and DQL Query Language +so make sure you use a caching mechanism. + +The 6th block shows the configuration options required to connect to a database, in my case a file-based +sqlite database. All the configuration options for all the shipped drivers are given in the [DBAL Configuration +section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/dbal). + +The last block shows how the `EntityManager` is obtained from a factory method, Here we also pass +in an `EventManager` instance which is optional. However using the EventManager you can hook in to the lifecycle +of entities, which is a common use-case, so you know how to configure it already. + +## Generating the Database Schema + +Now that we have defined the Metadata Mappings and bootstrapped the EntityManager +we want to generate the relational database schema from it. +Doctrine has a Command-Line-Interface that allows you to access the SchemaTool, a component that generates +the required tables to work with the metadata. + +For the commandline tool to work a cli-config.php file has to be present in the project root directry, +where you will execute the doctrine command. Its a fairly simple file: + + [php] + $cliConfig = new Doctrine\Common\Cli\Configuration(); + $cliConfig->setAttribute('em', $entityManager); + +You can then use your favorite console tool to call: + + [console] + doctrine@my-desktop> cd myproject/ + doctrine@my-desktop> doctrine orm:schema-tool --create + +During the development you probably need to re-create the database several times when changing the Entity +metadata. You can then either re-create the database, or use the update functionality: + + [console] + doctrine@my-desktop> doctrine orm:schema-tool --re-create + doctrine@my-desktop> doctrine orm:schema-tool --update + +The updating of databases uses a Diff Algorithm for a given Database Schema, a cornerstone of the `Doctrine\DBAL` +package, which can even be used without the Doctrine ORM package. However its not available in SQLite since +it does not support ALTER TABLE. + +## Writing Entities into the Database + +Having created the schema we can now start and save entities in the database. For starters we need a create user use-case: + + [php] + $newUsername = "beberlei"; + + $user = new User(); + $user->name = $newUsername; + + $entityManager->persist($user); + $entityManager->flush(); + +Having a user, he can create products: + + [php] + $newProductName = "My Product"; + + $product = new Product(); + $product->name = $newProductName; + + $entityManager->persist($product); + $entityManager->flush(); + +So what is happening in those two snippets? In both examples the class creation is pretty standard, the interesting +bits are the communication with the `EntityManager`. To notify the EntityManager that a new entity should be inserted +into the database you have to call `persist()`. However the EntityManager does not act on this, its merely notified. +You have to explicitly call `flush()` to have the EntityManager write those two entities to the database. + +You might wonder why does this distinction between persist notification and flush exist? Doctrine 2 uses the +UnitOfWork pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one single fast transaction, which +is executed when flushing. This way the write-performance is unbelievable fast. Also in more complex scenarios +than those two before you can request updates on many different entities and all flush them at once. + +Doctrine's UnitOfWork even detects entities that have been retrieved from the database and changed when calling +flush, so that you only have to keep track of those entities that are new or to be removed and pass them to +`EntityManager#persist()` and `EntityManager#remove()` respectively. + +We are now getting to the "Create a New Bug" requirement and the code for this scenario may look like this: + + [php] + $reporter = $entityManager->find("User", $theReporterId); + $engineer = $entityManager->find("User", $theDefaultEngineerId); + + $bug = new Bug(); + $bug->description = "Something does not work!"; + $bug->created = new DateTime("now"); + $bug->status = "NEW"; + + foreach ($productIds AS $productId) { + $product = $entityManager->find("Product", $productId); + $bug->assignToProduct($product); + } + + $bug->setReporter($reporter); + $bug->setEngineer($engineer); + + $entityManager->persist($bug); + $entityManager->flush(); + + echo "Your new Bug Id: ".$bug->id."\n"; + +This is the first contact with the read API of the EntityManager, showing that a call to +`EntityManager#find($name, $id)` returns a single instance of an entity queried by primary key. +Besides this we see the persist + flush pattern again to save the Bug into the database. + +See how simple relating Bug, Reporter, Engineer and Products is done by using the discussed methods +in the "A first prototype" section. The UnitOfWork will detect this relations when flush +is called and relate them in the database appropriately. + +## Queries for Application Use-Cases + +Using the previous examples we can fill up the database quite a bit, however we now need to discuss how to query the underlying +mapper for the required view representations. When opening the application, bugs can be paginated through a list-view, which is the first +read-only use-case: + + [php] + $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC"; + + $query = $entityManager->createQuery($dql); + $query->setMaxResults(30); + $bugs = $query->getResult(); + + foreach($bugs AS $bug) { + echo $bug->description." - ".$bug->created->format('d.m.Y')."\n"; + echo " Reported by: ".$bug->getReporter()->name."\n"; + echo " Assigned to: ".$bug->getEngineer()->name."\n"; + foreach($bug->getProducts() AS $product) { + echo " Platform: ".$product->name."\n"; + } + echo "\n"; + } + +The DQL Query in this example fetches the 30 most recent bugs with their respective engineer and reporter +in one single SQL statement. The console output of this script is then: + + Something does not work! - 02.04.2010 + Reported by: beberlei + Assigned to: beberlei + Platform: My Product + +> **NOTE** +> +> **Dql is not Sql** +> +> You may wonder why we start writing SQL at the beginning of this use-case. Don't we use an ORM to get rid +> of all the endless hand-writing of SQL? Doctrine introduces DQL which is best described +> as **object-query-language** and is a dialect of [OQL](http://en.wikipedia.org/wiki/Object_Query_Language) and +> similar to [HQL](http://www.hibernate.org) or [JPQL](http://en.wikipedia.org/wiki/Java_Persistence_Query_Language). +> It does not know the concept of columns and tables, but only those +> of Entity-Class and property. Using the Metadata we defined before it allows for very short distinctive +> and powerful queries. +> +> An important reason why DQL is favourable to the Query API of most ORMs is its similarity to SQL. The DQL language +> allows query constructs that most ORMs don't, GROUP BY even with HAVING, Subselects, Fetch-Joins of nested +> classes, mixed results with entities and scalar data such as COUNT() results and much more. Using +> DQL you should seldom come to the point where you want to throw your ORM into the dumpster, because it +> doesn't support some the more powerful SQL concepts. +> +> Besides handwriting DQL you can however also use the `QueryBuilder` retrieved by calling `$entityManager->createQueryBuilder()` +> which is a Query Object around the DQL language. +> +> As a last resort you can however also use Native SQL and a description of the result set to retrieve +> entities from the database. DQL boils down to a Native SQL statement and a `ResultSetMapping` instance itself. +> Using Native SQL you could even use stored procedures for data retrieval, or make use of advanced non-portable +> database queries like PostgreSql's recursive queries. + +The next Use-Case is displaying a Bug by primary key. This could be done using DQL as in the previous example with a where clause, +however there is a convenience method on the Entity Manager that handles loading by primary key, which we have already +seen in the write scenarios: + + [php] + $bug = $entityManager->find("Bug", (int)$theBugId); + +However we will soon see another problem with our entities using this approach. Try displaying the engineer's name: + + [php] + echo "Bug: ".$bug->description."\n"; + echo "Engineer: ".$bug->getEngineer()->name."\n"; + +It will be null! What is happening? It worked in the previous example, so it can't be a problem with the persistance +code of Doctrine. You walked in the public property trap. Since we only retrieved the bug by primary key both the +engineer and reporter are not immediately loaded from the database but are replaced by LazyLoading proxies. Sample +code of this proxy generated code can be found in the specified Proxy Directory, it looks like: + + [php] + namespace MyProject\Proxies; + + /** + * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. + */ + class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy + { + // .. lazy load code here + + public function addReportedBug($bug) + { + $this->_load(); + return parent::addReportedBug($bug); + } + + public function assignedToBug($bug) + { + $this->_load(); + return parent::assignedToBug($bug); + } + } + +See how upon each method call the proxy is lazily loaded from the database? Using public properties however +we never call a method and Doctrine has no way to hook into the PHP Engine to detect this access and trigger +the lazy load. We need to revise our entities, make all the properties private or protected and add getters +and setters to get a working example: + + [php] + echo "Bug: ".$bug->getDescription()."\n"; + echo "Engineer: ".$bug->getEngineer()->getName()."\n"; + + /** + Bug: Something does not work! + Engineer: beberlei + */ + +For the next use-case we want to retrieve the dashboard view, a list of all open bugs the user reported or +was assigned to. This will be achieved using DQL again, this time with some WHERE clauses and usage of bound parameters: + + [php] + $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ". + "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC"; + + $myBugs = $entityManager->createQuery($dql) + ->setParameter(1, $theUserId) + ->setMaxResults(15) + ->getResult(); + + foreach ($myBugs AS $bug) { + echo $bug->getDescription()."\n"; + } + +That is it for the read-scenarios of this example, we will continue with the last missing bit, engineers +being able to close a bug. + +## Updating Entities + +There is a single use-case missing from the requirements, Engineers should be able to close a bug. This looks like: + + [php] + $bug = $entityManager->find("Bug", (int)$theBugId); + $bug->close(); + + $entityManager->flush(); + +When retrieving the Bug from the database it is inserted into the IdentityMap inside the UnitOfWork of Doctrine. +This means your Bug with exactly this id can only exist once during the whole request no matter how often you +call `EntityManager#find()`. It even detects entities that are hydrated using DQL and are already present in +the Identity Map. + +When flush is called the EntityManager loops over all the entities in the identity map and performs a comparison +between the values originally retrieved from the database and those values the entity currently has. If at +least one of these properties is different the entity is scheduled for an UPDATE against the database. +Only the changed columns are updated, which offers a pretty good performance improvement compared to updating +all the properties. + +This tutorial is over here, I hope you had fun. Additional content will be added to this tutorial +incrementally, topics will include: + + * Entity Repositories + * More on Association Mappings + * Lifecycle Events triggered in the UnitOfWork + * Ordering of Collections + +Additional details on all the topics discussed here can be found in the respective manual chapters. diff --git a/cookbook/en/implementing-arrayaccess-for-domain-objects.txt b/cookbook/en/implementing-arrayaccess-for-domain-objects.txt new file mode 100644 index 000000000..4f295420d --- /dev/null +++ b/cookbook/en/implementing-arrayaccess-for-domain-objects.txt @@ -0,0 +1,89 @@ + +This recipe will show you how to implement ArrayAccess for your domain objects in order to allow more uniform access, for example in templates. In these examples we will implement ArrayAccess on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. + +++ Option 1 + +In this implementation we will make use of PHPs highly dynamic nature to dynamically access properties of a subtype in a supertype at runtime. Note that this implementation has 2 main caveats: + +* It will not work with private fields +* It will not go through any getters/setters + +- + + [php] + abstract class DomainObject implements ArrayAccess + { + public function offsetExists($offset) { + return isset($this->$offset); + } + + public function offsetSet($offset, $value) { + $this->$offset = $value; + } + + public function offsetGet($offset) { + return $this->$offset; + } + + public function offsetUnset($offset) { + $this->$offset = null; + } + } + + +++ Option 2 + +In this implementation we will dynamically invoke getters/setters. Again we use PHPs dynamic nature to invoke methods on a subtype from a supertype at runtime. This implementation has the following caveats: + +* It relies on a naming convention +* The semantics of offsetExists can differ +* offsetUnset will not work with typehinted setters + +- + + [php] + abstract class DomainObject implements ArrayAccess + { + public function offsetExists($offset) { + // In this example we say that exists means it is not null + $value = $this->{"get$offset"}(); + return $value !== null; + } + + public function offsetSet($offset, $value) { + $this->{"set$offset"}($value); + } + + public function offsetGet($offset) { + return $this->{"get$offset"}(); + } + + public function offsetUnset($offset) { + $this->{"set$offset"}(null); + } + } + +++ Read-only + +You can slightly tweak option 1 or option 2 in order to make array access read-only. This will also circumvent some of the caveats of each option. Simply make offsetSet and offsetUnset throw an exception (i.e. BadMethodCallException). + + [php] + abstract class DomainObject implements ArrayAccess + { + public function offsetExists($offset) { + // option 1 or option 2 + } + + public function offsetSet($offset, $value) { + throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!"); + } + + public function offsetGet($offset) { + // option 1 or option 2 + } + + public function offsetUnset($offset) { + throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!"); + } + } + diff --git a/cookbook/en/implementing-the-notify-changetracking-policy.txt b/cookbook/en/implementing-the-notify-changetracking-policy.txt new file mode 100644 index 000000000..025375487 --- /dev/null +++ b/cookbook/en/implementing-the-notify-changetracking-policy.txt @@ -0,0 +1,48 @@ + +The NOTIFY changetracking policy is the most effective changetracking policy provided by Doctrine but it requires some boilerplate code. This recipe will show you how this boilerplate code should look like. We will implement it on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. + +++ Implementing NotifyPropertyChanged + +The NOTIFY 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. + + [php] + use Doctrine\Common\NotifyPropertyChanged, + Doctrine\Common\PropertyChangedListener; + + abstract class DomainObject implements NotifyPropertyChanged + { + private $_listeners = array(); + + public function addPropertyChangedListener(PropertyChangedListener $listener) { + $this->_listeners[] = $listener; + } + + /** Notifies listeners of a change. */ + protected function _onPropertyChanged($propName, $oldValue, $newValue) { + if ($this->_listeners) { + foreach ($this->_listeners as $listener) { + $listener->propertyChanged($this, $propName, $oldValue, $newValue); + } + } + } + } + +Then, in each property setter of concrete, derived domain classes, you need to invoke _onPropertyChanged as follows to notify listeners: + + [php] + // Mapping not shown, either in annotations, xml or yaml as usual + class MyEntity extends DomainObject + { + private $data; + // ... other fields as usual + + public function setData($data) { + if ($data != $this->data) { // check: is it actually modified? + $this->_onPropertyChanged('data', $this->data, $data); + $this->data = $data; + } + } + } + +The check whether the new value is different from the old one is not mandatory but recommended. That way you can avoid unnecessary updates and also have full control over when you consider a property changed. + diff --git a/cookbook/en/implementing-wakeup-or-clone.txt b/cookbook/en/implementing-wakeup-or-clone.txt new file mode 100644 index 000000000..efdefa7c3 --- /dev/null +++ b/cookbook/en/implementing-wakeup-or-clone.txt @@ -0,0 +1,63 @@ + +As explained in the [restrictions for entity classes in the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities), +it is usually not allowed for an entity to implement `__wakeup` or `__clone`, because Doctrine +makes special use of them. However, it is quite easy to make use of these methods in a safe way +by guarding the custom wakeup or clone code with an entity identity check, as demonstrated in the following sections. + +++ Safely implementing __wakeup + +To safely implement `__wakeup`, simply enclose your implementation code in an identity check +as follows: + + [php] + class MyEntity + { + private $id; // This is the identifier of the entity. + //... + + public function __wakeup() + { + // If the entity has an identity, proceed as normal. + if ($this->id) { + // ... Your code here as normal ... + } + // otherwise do nothing, do NOT throw an exception! + } + + //... + } + +++ Safely implementing __clone + +Safely implementing `__clone` is pretty much the same: + + [php] + class MyEntity + { + private $id; // This is the identifier of the entity. + //... + + public function __clone() + { + // If the entity has an identity, proceed as normal. + if ($this->id) { + // ... Your code here as normal ... + } + // otherwise do nothing, do NOT throw an exception! + } + + //... + } + +++ Summary + +As you have seen, it is quite easy to safely make use of `__wakeup` and `__clone` in your entities +without adding any really Doctrine-specific or Doctrine-dependant code. + +These implementations are possible and safe because when Doctrine invokes these methods, +the entities never have an identity (yet). Furthermore, it is possibly a good idea to check +for the identity in your code anyway, since it's rarely the case that you want to unserialize +or clone an entity with no identity. + + + diff --git a/cookbook/en/integrating-with-codeigniter.txt b/cookbook/en/integrating-with-codeigniter.txt new file mode 100644 index 000000000..23165176a --- /dev/null +++ b/cookbook/en/integrating-with-codeigniter.txt @@ -0,0 +1,104 @@ +This is recipe for using Doctrine 2 in your [CodeIgniter](http://www.codeigniter.com) framework. + +Here is how to set it up: + +Make a CodeIgniter library that is both a wrapper and a bootstrap for Doctrine 2. + +++ Setting up the file structure + +Here are the steps: + +* Add a php file to your system/application/libraries folder called Doctrine.php. This is going to be your wrapper/bootstrap for the D2 entity manager. +* Put the Doctrine folder (the one that contains Common, DBAL, and ORM) inside that same libraries folder. +* Your system/application/libraries folder now looks like this: + + system/applications/libraries + -Doctrine + -Doctrine.php + -index.html + +* If you want, open your config/autoload.php file and autoload your Doctrine library. + + [php] + $autoload['libraries'] = array('doctrine'); + +++ Creating your Doctrine CodeIgniter library + +Now, here is what your Doctrine.php file should look like. Customize it to your needs. + + [php] + use Doctrine\Common\ClassLoader, + Doctrine\ORM\Configuration, + Doctrine\ORM\EntityManager, + Doctrine\Common\Cache\ArrayCache, + Doctrine\DBAL\Logging\EchoSqlLogger; + + class Doctrine { + + public $em = null; + + public function __construct() + { + // load database configuration from CodeIgniter + require_once APPPATH.'config/database.php'; + + // Set up class loading. You could use different autoloaders, provided by your favorite framework, + // if you want to. + require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php'; + + $doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries'); + $doctrineClassLoader->register(); + $entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" )); + $entitiesClassLoader->register(); + $proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies'); + $proxiesClassLoader->register(); + + // Set up caches + $config = new Configuration; + $cache = new ArrayCache; + $config->setMetadataCacheImpl($cache); + $config->setQueryCacheImpl($cache); + + // Proxy configuration + $config->setProxyDir(APPPATH.'/models/proxies'); + $config->setProxyNamespace('Proxies'); + + // Set up logger + $logger = new EchoSqlLogger; + $config->setSqlLogger($logger); + + $config->setAutoGenerateProxyClasses( TRUE ); + + // Database connection information + $connectionOptions = array( + 'driver' => 'pdo_mysql', + 'user' => $db['default']['username'], + 'password' => $db['default']['password'], + 'host' => $db['default']['hostname'], + 'dbname' => $db['default']['database'] + ); + + // Create EntityManager + $this->em = EntityManager::create($connectionOptions, $config); + } + } + +Please note that this is a development configuration; for a production system you'll want to use a real caching system like APC, get rid of EchoSqlLogger, and turn off autoGenerateProxyClasses. + +For more details, consult the [Doctrine 2 Configuration documentation](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). + +++ Now to use it + +Whenever you need a reference to the entity manager inside one of your controllers, views, or models you can do this: + + [php] + $em = $this->doctrine->em; + +That's all there is to it. Once you get the reference to your EntityManager do your Doctrine 2.0 voodoo as normal. + +Note: If you do not choose to autoload the Doctrine library, you will need to put this line before you get a reference to it: + + [php] + $this->load->library('doctrine'); + +Good luck! diff --git a/cookbook/en/validation-of-entities.txt b/cookbook/en/validation-of-entities.txt new file mode 100644 index 000000000..739a608d2 --- /dev/null +++ b/cookbook/en/validation-of-entities.txt @@ -0,0 +1,114 @@ +Doctrine 2 does not ship with any internal validators, the reason being that +we think all the frameworks out there already ship with quite decents ones that can be integrated +into your Domain easily. What we offer are hooks to execute any kind of validation. + +> **Note** +> You don't need to validate your entities in the lifecycle events. Its only +> one of many options. Of course you can also perform validations in value setters +> or any other method of your entities that are used in your code. + +Entities can register lifecycle evnt methods with Doctrine that are called on +different occasions. For validation we would need to hook into the +events called before persisting and updating. Even though we don't support +validation out of the box, the implementation is even simpler than in Doctrine 1 +and you will get the additional benefit of being able to re-use your validation +in any other part of your domain. + +Say we have an `Order` with several `OrderLine` instances. We never want to +allow any customer to order for a larger sum than he is allowed to: + + [php] + class Order + { + public function assertCustomerAllowedBuying() + { + $orderLimit = $this->customer->getOrderLimit(); + + $amount = 0; + foreach ($this->orderLines AS $line) { + $amount += $line->getAmount(); + } + + if ($amount > $orderLimit) { + throw new CustomerOrderLimitExceededException(); + } + } + } + +Now this is some pretty important piece of business logic in your code, enforcing +it at any time is important so that customers with a unknown reputation don't +owe your business too much money. + +We can enforce this constraint in any of the metadata drivers. First Annotations: + + [php] + /** + * @Entity + * @HasLifecycleCallbacks + */ + class Order + { + /** + * @PrePersist @PreUpdate + */ + public function assertCustomerAllowedBuying() {} + } + +In XML Mappings: + + [xml] + + + + + + + + + +YAML needs some little change yet, to allow multiple lifecycle events for one method, +this will happen before Beta 1 though. + +Now validation is performed whenever you call `EntityManager#persist($order)` +or when you call `EntityManager#flush()` and an order is about to be updated. +Any Exception that happens in the lifecycle callbacks will be catched by the +EntityManager and the current transaction is rolled back. + +Of course you can do any type of primitive checks, not null, email-validation, string size, +integer and date ranges in your validation callbacks. + + [php] + class Order + { + /** + * @PrePersist @PreUpdate + */ + public function validate() + { + if (!($this->plannedShipDate instanceof DateTime)) { + throw new ValidateException(); + } + + if ($this->plannedShipDate->format('U') < time()) { + throw new ValidateException(); + } + + if ($this->customer == null) { + throw new OrderRequiresCustomerException(); + } + } + } + +What is nice about lifecycle events is, you can also re-use the methods at other places +in your domain, for example in combination with your form library. +Additionally there is no limitation in the number of methods you register +on one particular event, i.e. you can register multiple methods for validation in "PrePersist" +or "PreUpdate" or mix and share them in any combinations between those two events. + +There is no limit to what you can and can't validate in "PrePersist" and "PreUpdate" aslong as +you don't create new entity instances. This was already discussed in the previous +blog post on the Versionable extension, which requires another type of event called "onFlush". + +Further readings: + +* [Doctrine 2 Manual: Events](http://www.doctrine-project.org/documentation/manual/2_0/en/events#lifecycle-events) \ No newline at end of file diff --git a/dbal/manual/en.txt b/dbal/manual/en.txt deleted file mode 100644 index 8383139ae..000000000 --- a/dbal/manual/en.txt +++ /dev/null @@ -1 +0,0 @@ -+ DBAL \ No newline at end of file diff --git a/dbal/manual/en/dbal.txt b/dbal/manual/en/dbal.txt deleted file mode 100644 index 452f45646..000000000 --- a/dbal/manual/en/dbal.txt +++ /dev/null @@ -1,245 +0,0 @@ -The Doctrine 2 database layer can be used independently of the object-relational mapping. It offers a leightweight abstraction layer around a PDO like API and allows optional access to lots of convenience functionality aswell as the ability to generate platform independent DQL and DDL statements. - -++ Configuration - -You can create a Doctrine Connection by using the `Doctrine\DBAL\DriverManager` class. - - [php] - $config = new \Doctrine\DBAL\Configuration(); - //.. - - $connectionParams = array( - 'dbname' => 'mydb', - 'user' => 'user', - 'password' => 'secret', - 'host' => 'locahlost', - 'driver' => 'pdo_mysql', - ); - $conn = DriverManager::getConnection($connectionParams); - -The `DriverManager` returns an instance of `Doctrine\DBAL\Connection` which is a wrapper around any configured database driver, for example the PDO Mysql driver in the previous example. - -+++ Connection Options - -The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array -or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the -DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The following option keys can be specified -to create your connection: - -++++ Driver Management Options: - -* driver - Allows to specify the default drivers shipped with Doctrine 2, 'pdo_mysql', 'pdo_sqlite', 'pdo_pgsql, and 'oci'. -* driverClass - If no 'driver' is specified this allows usage of a userland implementation of Doctrine\DBAL\Driver. -* pdo - If PDO is already instantiated for Mysql, SqLite or PgSQL this key can be used to pass this instance into Doctrine. -* wrapperClass - By default Doctrine\DBAL\Connection is wrapped around each driver, however this option allows to specify a userland sub-class. - -++++ Driver Configuration Options: - -Common Configuration Options across all database drivers: - -* platform - An instance of `Doctrine\DBAL\Platforms\AbstractPlatform`. This is only required for userland implementations, each driver shipped with Doctrine 2 has a default platform. -* user - Username required to connect to the database. -* password - Password required to connect to the database. -* driverOptions - Array of options passed to the driver instance on calling to Driver::connect. - -Driver Configuration Options are different for each Database Driver, here are some of the db specific ones: - -* host - Database host (Mysql, Pgsql, Oracle) -* port - Database port (Mysql, Pgsql, Oracle) -* dbname - Name of the database/schema to connect to. (Mysql, Pgsql, Oracle) -* unix_socket - Name of the socket used to connect to the database. (Mysql) -* charset - The charset used when connecting to the database. (Oracle) -* path - The filesystem path to the database (Sqlite) -* memory - True if the sqlite database should be in memory. (Sqlite) - -+++ DBAL Events - -Both `Doctrine\DBAL\DriverManager` and `Doctrine\DBAL\Connection` accept an instance of `Doctrine\Common\EventManager`. -The EventManager has a couple of events inside the DBAL layer that are triggered for the user to listen to: - -++++ PostConnect Event - -`Doctrine\DBAL\Events::postConnect` is triggered right after the connection to the database is established. It allows -to specify any relevant connection specific options and gives access to the `Doctrine\DBAL\Connection` instance -that is responsible for the connection management via an instance of `Doctrine\DBAL\Event\ConnectionEventArgs` -event arguments instance. - -Doctrine is already shipped with two implementations for the "PostConnect" event: - -* `Doctrine\DBAL\Event\Listeners\OracleSessionInit` allows to specify any number of Oracle Session related enviroment variables that are set right after the connection is established. -* `Doctrine\DBAL\Event\Listeners\MysqlSessionInit` allows to specify the Charset and Collation of the Client Connection if these options are not configured correctly on the MySQL server side. - -You can register events by subscribing them to the `EventManager` instance passed to the Connection factory: - - [php] - $evm = new EventManager(), - $evm->addEventSubscriber(new MysqlSessionInit('UTF-8')); - - $conn = DriverManager::getConnection($connectionParams, null, $evm); - -++ DBAL API - -+++ DBAL Architecture - -The DBAL is seperated into several different packages that perfectly seperate responsibilities of the different RDBMS layers. - -* **Drivers** abstract a PHP specific database API by enforcing two interfaces `\Doctrine\DBAL\Driver\Driver` and `\Doctrine\DBAL\Driver\Statement` which require exactly the same methods as PDO. -* **Platforms** abstract the generation of queries and which database features a platform supports. The `\Doctrine\DBAL\Platforms\AbstractPlatform` defines the common denominator of what a database platform has to publish to the userland, to be fully supportable by Doctrine. This includes the SchemaTool, Transaction Isolation and many other features. The Database platform for MySQL for example can be used by all 3 mysql extensions, PDO, Mysqli and ext/mysql. -* **Logging** holds the interface and some implementations for debugging of Doctrine SQL query execution during a request. -* **Schema** offers an API for each database platform to execute DDL statements against your platform or retrieve metadata about it. It also holds the Schema Abstraction Layer which is used by the different Schema Management facilities of Doctrine DBAL and ORM. -* **Types** offers an abstraction layer for the converting and generation of types between Databases and PHP. - -+++ Data Retrieval and Manipulation - - The following methods exist for executing queries against your configured database, three very generic methods and some advanced retrievial methods: - -* `prepare($sql)` - Prepare a given sql statement and return the `\Doctrine\DBAL\Driver\Statement` instance. -* `executeUpdate($sql, array $params)` - Executes a prepared statement with the given sql and parameters and returns the affected rows count. -* `execute($sql, array $params)` - Creates a prepared statement for the given sql and passes the parameters to the execute method, then returning the statement. -* `fetchAll($sql, array $params)` - Execute the query and fetch all results into an array. -* `fetchArray($sql, array $params)` - Numeric index retrieval of first result row of the given query. -* `fetchBoth($sql, array $params)` - Both numeric and assoc column name retrieval of the first result row. -* `fetchColumn($sql, array $params, $colnum)` - Retrieve only the given column of the first result row. -* `fetchRow($sql, array $params)` - Retrieve assoc row of the first result row. -* `select($sql, $limit, $offset)` - Modify the given query with a limit clause. - -There are also convenience methods for data manipulation queries: - -* `delete($tableName, array $identifier)` - Delete all rows of a table matching the given identifier, where keys are column names. -* `insert($tableName, array $data)` - Insert a row into the given table name using the key value pairs of data. -* `update($tableName, array $data, array $identifier)` - Update all rows for the matching key value identifiers with the given data. - -By default the Doctrine DBAL does no escaping. Escaping is a very tricky business to do automagically, therefore there is none by default. The ORM internally escapes all your values, because it has lots of metadata available about the current context. When you use the Doctrine DBAL as standalone, you have to take care of this yourself. The following methods help you with it: - -* `quote($input, $type=null)` - Quote a value -* `quoteIdentifier($identifier)`- Quote an identifier according to the platform details. - -+++ Transactions - -Doctrine handles transactions with a PDO like API, having methods for `beginTransaction()`, `commit()` and `rollBack()`. For consistency across different drivers Doctrine also handles the nesting of transactions internally. You can call `beginTransaction()` more than once, and only a matching amount of calls to `commit()` triggers the commit to the database. The Doctrine connectionalso has a method to set the transaction isolation level of the connection as supported by the underlying database. - - [php] - class Connection - { - /** - * Constant for transaction isolation level READ UNCOMMITTED. - */ - const TRANSACTION_READ_UNCOMMITTED = 1; - - /** - * Constant for transaction isolation level READ COMMITTED. - */ - const TRANSACTION_READ_COMMITTED = 2; - - /** - * Constant for transaction isolation level REPEATABLE READ. - */ - const TRANSACTION_REPEATABLE_READ = 3; - - /** - * Constant for transaction isolation level SERIALIZABLE. - */ - const TRANSACTION_SERIALIZABLE = 4; - } - -A transaction with Doctrine DBAL might then look like: - - [php] - $conn->setTransactionIsolationLevel(Connection::TRANSACTION_SERIALIZABLE); - - try{ - $conn->beginTransaction(); - // do stuff - $conn->commit(); - } catch(\Exception $e) { - $conn->rollback(); - } - -++ Schema Representation - -Doctrine has a very powerful abstraction of database schemas. It offers an object-oriented representation of a database schema with support for all the details of Tables, Sequences, Indexes and Foreign Keys. These Schema instances generate a representation that is equal for all the supported platforms. Internally this functionality is used by the ORM Schema Tool to offer you create, drop and update database schema methods from your Doctrine ORM Metadata model. Up to very specific functionality of your database system this allows you to generate SQL code that makes your Domain model work. - -You will be pleased to hear, that Schema representation is completly decoupled from the Doctrine ORM though, that is you can also use it in any other project to implement database migrations or for SQL schema generation for any metadata model that your application has. You can easily generate a Schema, as a simple example shows: - - [php] - $schema = new \Doctrine\DBAL\Schema\Schema(); - $myTable = $schema->createTable("my_table"); - $myTable->addColumn("id", "integer", array("unsigned" => true)); - $myTable->addColumn("username", "string", array("length" => 32)); - $myTable->setPrimaryKey(array("id")); - $myTable->addUniqueIndex(array("username")); - $schema->createSequence("my_table_seq"); - - $myForeign = $schema->createTable("my_foreign"); - $myForeign->addColumn("id", "integer"); - $myForeign->addColumn("user_id", "integer"); - $myForeign->addForeignKeyConstraint($myTable, array("user_id"), array("id"), array("onUpdate" => "CASCADE")); - - $queries = $schema->toSql($myPlatform); // get queries to create this schema. - $dropSchema = $schema->toDropSql($myPlatform); // get queries to safely delete this schema. - -Now if you want to compare this schema with another schema, you can use the `Comparator` class to get instances of `SchemaDiff`, `TableDiff` and `ColumnDiff`, aswell as information about other foreign key, sequence and index changes. - - [php] - $comparator = new \Doctrine\DBAL\Schema\Comparator(); - $schemaDiff = $comparator->compare($fromSchema, $toSchema); - - $queries = $schemaDiff->toSql($myPlatform); // queries to get from one to another schema. - $saveQueries = $schemaDiff->toSaveSql($myPlatform); - -The Save Diff mode is a specific mode that prevents the deletion of tables and sequences that might occour when making a diff of your schema. This is often necessary when your target schema is not complete but only describes a subset of your application. - -All methods that generate SQL queries for you make much effort to get the order of generation correct, so that no problems will ever occour with missing links of foreign keys. - -++ Platforms - -Platforms abstract query generation and specifics of the RDBMS featuresets. In most cases you don't need to interact with this package alot, but there might be certain cases when you are programming database independent where you want to access the platform to generate queries for you. - -The platform can be accessed from any `Doctrine\DBAL\Connection` instance by calling the `getDatabasePlatform()` method. - -You can use your own platform by specifying the 'platform' key with an instance of your own platform: - - [php] - $myPlatform = new MyPlatform(); - $options = array( - 'driver' => 'pdo_sqlite', - 'path' => 'database.sqlite', - 'platform' => $myPlatform - ); - -This way you can optimize your schema or generated SQL code with features that might not be portable for instance, -however are required for your special needs. - -++ Schema Manager - -A Schema Manager instance helps you with the abstraction of the generation of SQL assets such as Tables, Sequences, Foreign Keys and Indexes. You can use any of the Schema Asset classes `Table`, `Sequence`, `ForeignKeyConstraint` and `Index` for use with the methods of the style `dropAndCreate(AssetName)($asset)`, `drop(AssetName)($asset)` and `create(AssetName)($asset)`. - -You also have methods to retrieve instances of those types from the current database you are connected to. These methods are: - -* `listDatabases()` -* `listSequences()` -* `listTableColumns($tableName)` -* `listTableDetails($tableName)` -* `listTableForeignKeys($tableName)` -* `listTableIndexes($tableName)` -* `listTables()` -* `listUsers()` -* `listViews()` - -For a complete representation of the current database you can use the `createSchema()` method which returns an instance of Schema, which you can use in conjunction with the SchemaTool or Schema Comparator. - -++ Supporting other Databases - -To support a database which is not currently shipped with Doctrine you have to implement the following interfaces and abstract classes: - -* `\Doctrine\DBAL\Driver\Driver` -* `\Doctrine\DBAL\Driver\Statement` -* `\Doctrine\DBAL\Platforms\AbstractPlatform` -* `\Doctrine\DBAL\Schema\AbstractSchemaManager` - -For an already supported platform but unsupported driver you only need to implement the first two interfaces, since the SQL Generation and Schema Management is already supported by the respective platform and schema instances. You can also make use of several Abstract Unittests in the `\Doctrine\Tests\DBAL` package to check if your platform behaves like all the others which is necessary for SchemaTool support, namely: - -* `\Doctrine\Tests\DBAL\Platforms\AbstractPlatformTestCase` -* `\Doctrine\Tests\DBAL\Functional\Schema\AbstractSchemaManagerTestCase` - -We would be very happy if any support for new databases would be contributed back to Doctrine to make it an even better product. diff --git a/manual/en.txt b/manual/en.txt new file mode 100644 index 000000000..b17808194 --- /dev/null +++ b/manual/en.txt @@ -0,0 +1,20 @@ ++ Introduction ++ Architecture ++ Configuration ++ Basic Mapping ++ Association Mapping ++ Inheritance Mapping ++ Working with objects ++ Transactions and Concurrency ++ Events ++ Batch processing ++ DQL (Doctrine Query Language) ++ Query Builder ++ Native SQL ++ XML Mapping ++ YAML Mapping ++ Annotations Reference ++ Caching ++ Improving Performance ++ Tools ++ Best Practices \ No newline at end of file diff --git a/manual/en/annotations-reference.txt b/manual/en/annotations-reference.txt new file mode 100644 index 000000000..cf5eece3b --- /dev/null +++ b/manual/en/annotations-reference.txt @@ -0,0 +1,557 @@ +In this chapter a reference of every Doctrine 2 Annotation is given with short explanations on their context and usage. + +++ Index + +* [@Column](#ann_column) +* [@ChangeTrackingPolicy](#ann_changetrackingpolicy) +* [@DiscriminatorColumn](#ann_discriminatorcolumn) +* [@DiscriminatorMap](#ann_discriminatormap) +* [@Entity](#ann_entity) +* [@GeneratedValue](#ann_generatedvalue) +* [@HasLifecycleCallbacks](#ann_haslifecyclecallbacks) +* [@Index](#ann_indexes) +* [@Id](#ann_id) +* [@InheritanceType](#ann_inheritancetype) +* [@JoinColumn](#ann_joincolumn) +* [@JoinTable](#ann_jointable) +* [@ManyToOne](#ann_manytoone) +* [@ManyToMany](#ann_manytomany) +* [@MappedSuperclass](#ann_mappedsuperclass) +* [@OneToOne](#ann_onetoone) +* [@OneToMany](#ann_onetomany) +* [@OrderBy](#ann_orderby) +* [@PostLoad](#ann_postload) +* [@PostPersist](#ann_postpersist) +* [@PostRemove](#ann_postremove) +* [@PostUpdate](#ann_postupdate) +* [@PrePersist](#ann_prepersist) +* [@PreRemove](#ann_preremove) +* [@PreUpdate](#ann_preupdate) +* [@SequenceGenerator](#ann_sequencegenerator) +* [@Table](#ann_table) +* [@UniqueConstraint](#ann_uniqueconstraint) +* [@Version](#ann_version) + +++ Reference + + ++++ @Column + +Marks an annotated instance variable as "persistent". It has to be inside the instance variables PHP DocBlock comment. +Any value hold inside this variable will be saved to and loaded from the database as part of the lifecycle of the instance variables entity-class. + +Required attributes: + +* type - Name of the Doctrine Type which is converted between PHP and Database representation. + +Optional attributes: + +* name - By default the property name is used for the database column name also, however the 'name' attribute allows you to determine the column name. +* length - Used by the "string" type to determine its maximum length in the database. Doctrine does not validate the length of a string values for you. +* precision - The precision for a decimal (exact numeric) column (Applies only for decimal column) +* scale - The scale for a decimal (exact numeric) column (Applies only for decimal column) +* unique - Boolean value to determine if the value of the column should be unique accross all rows of the underlying entities table. +* nullable - Determines if NULL values allowed for this column. +* columnDefinition - DDL SQL snippet that starts after the column name and specificies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. However you should make careful use of this feature and the consequences. Additionally you should remember that the "type" attribute still handles the conversion between PHP and Database values. If you use this attribute on a column that is used for joins between tables you should also take a look at [@JoinColumn](#ann_joincolumn). + +Examples: + + /** + * @Column(type="string", length=32, unique=true, nullable=false) + */ + protected $username; + + /** + * @Column(type="string", columnDefinition="CHAR(2) NOT NULL") + */ + protected $country; + + /** + * @Column(type="decimal", precision=2, scale=1) + */ + protected $height; + + ++++ @ChangeTrackingPolicy + +The Change Tracking Policy annotation allows to specify how the Doctrine 2 UnitOfWork should detect changes +in properties of entities during flush. By default each entity is checked according to a deferred implict +strategy, which means upon flush UnitOfWork compares all the properties of an entity to a previously stored +snapshot. This works out of the box, however you might want to tweak the flush performance where using +another change tracking policy is an interesting option. + +The [details on all the available change tracking policies](/../configuration#change-tracking-policies) +can be found in the configuration section. + +Example: + + /** + * @Entity + * @ChangeTrackingPolicy("DEFERRED_IMPLICIT") + * @ChangeTrackingPolicy("DEFERRED_EXPLICIT") + * @ChangeTrackingPolicy("NOTIFY") + */ + class User {} + + ++++ @DiscrimnatorColumn + +This annotation is a required annotation for the topmost/super class of an inheritance hierachy. It specifies +the details of the column which saves the name of the class, which the entity is actually instantiated as. + +Required attributes: + +* name - The column name of the discriminator. This name is also used during Array hydration as key to specify the class-name. + +Optional attributes: + +* type - By default this is string. +* length - By default this is 255. + + ++++ @DiscriminatorMap + +The discrimnator map is a required annotation on the top-most/super class in an inheritance hierachy. It takes +an array as only argument which defines which class should be saved under which name in the database. Keys +are the database value and values are the classes, either as fully- or as unqualified class names depending +if the classes are in the namespace or not. + + /** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + + + ++++ @Entity + +Required annotation to mark a PHP class as Entity. Doctrine manages the persistence of all classes marked as entity. + +Optional attributes: + +* repositoryClass - Specifies the FQCN of a subclass of the Doctrine\ORM\EntityRepository. Use of repositories for entites is encouraged to keep specialized DQL and SQL operations separated from the Model/Domain Layer. + +Example: + + /** + * @Entity(repositoryClass="MyProject\UserRepository") + */ + class User + { + //... + } + + ++++ @GeneratedValue + +Specifies which strategy is used for identifier generation for an instance variable which is annotated by [@Id](#ann_id). +This annotation is optional and only has meaning when used in conjunction with @Id. + +If this annotation is not specified with @Id the NONE strategy is used as default. + +Required attributes: + +* strategy - Set the name of the identifier generation strategy. Valid values are AUTO, SEQUENCE, TABLE, IDENTITY and NONE. + +Example: + + /** + * @Id + * @Column(type="integer") + * @generatedValue(strategy="IDENTITY") + */ + protected $id = null; + + ++++ @HasLifecycleCallbacks + +Annotation which has to be set on the entity-class PHP DocBlock to notify Doctrine that this entity has entity life-cycle +callback annotations set on at least one of its methods. Using @PostLoad, @PrePersist, @PostPersist, @PreRemove, @PostRemove, +@PreUpdate or @PostUpdate without this marker annotation will make Doctrine ignore the callbacks. + +Example: + + /** + * @Entity + * @HasLifecycleCallbacks + */ + class User + { + /** + * @PostPersist + */ + public function sendOptinMail() {} + } + + ++++ @Index + +Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the +SchemaTool to generate a database index on the specified table columns. It only has meaning in the SchemaTool +schema generation context. + +Required attributes: + +* name - Name of the Index +* columns - Array of columns. + +Example: + + /** + * @Entity + * @Table(name="ecommerce_products",indexes={@index(name="search_idx", columns={"name", "email"})}) + */ + class ECommerceProduct + { + } + + ++++ @Id + +The annotated instance variable will be marked as entity identifier, the primary key in the database. +This annotation is a marker only and has no required or optional attributes. For entites that have multiple +identifier columns each column has to be marked with @Id. + +Example: + + /** + * @Id + * @Column(type="integer") + */ + protected $id = null; + + ++++ @InheritanceType + +In an inheritance hierachy you have to use this annotation on the topmost/super class to define which +strategy should be used for inheritance. Currently Single Table and Class Table Inheritance are supported. + +This annotation has always been used in conjunction with the [@DiscriminatorMap](#ann_discriminatormap) and +[@DiscriminatorColumn](#ann_discriminatorcolumn) annotations. + +Examples: + + /** + * @Entity + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + + /** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + + ++++ @JoinColumn + +This annotation is used in the context of relations in [@ManyToOne](#ann_manytoone), [@OneToOne](#ann_onetoone) fields +and in the Context of [@JoinTable](#ann_jointable) nested inside a @ManyToMany. This annotation is not required. +If its not specified the attributes *name* and *referencedColumnName* are infered from the table and primary key names. + +Required attributes: + +* name - Column name that holds the foreign key identifier for this relation. In the context of @JoinTable it specifies the column name in the join table. +* referencedColumnName - Name of the primary key identifier that is used for joining of this relation. + +Optional attributes: + +* unique - Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false. +* nullable - Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true. +* onDelete - Cascade Action (Database-level) +* onUpdate - Cascade Action (Database-level) +* columnDefinition - DDL SQL snippet that starts after the column name and specificies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on @JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a "columnDefinition" attribute on [@Column](#ann_column) also sets the related @JoinColumn's columnDefinition. This is necessary to make foreign keys work. + +Example: + + /** + * @OneToOne(targetEntity="Customer") + * @JoinColumn(name="customer_id", referencedColumnName="id") + */ + private $customer; + + ++++ @JoinColumns + +An array of @JoinColumn annotations for a [@ManyToOne](#ann_manytoone) or [@OneToOne](#ann_onetoone) relation +with an entity that has multiple identifiers. + + ++++ @JoinTable + +Using [@OneToMany](#ann_onetomany) or [@ManyToMany](#ann_manytomany) on the owning side of the relation requires to specifiy +the @JoinTable annotation which describes the details of the database join table. If you do not specify @JoinTable on +these relations reasonable mapping defaults apply using the affected table and the column names. + +Required attributes: + +* name - Database name of the join-table +* joinColumns - An array of @JoinColumn annotations describing the join-relation between the owning entites table and the join table. +* inverseJoinColumns - An array of @JoinColumn annotations describing the join-relation between the inverse entities table and the join table. + +Optional attributes: + +* schema - Database schema name of this table. + +Example: + + /** + * @ManyToMany(targetEntity="Phonenumber") + * @JoinTable(name="users_phonenumbers", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} + * ) + */ + public $phonenumbers; + + ++++ @ManyToOne + +Defines that the annotated instance variable holds a reference that describes a many-to-one relationship between two entities. + +Required attributes: + +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. + +Optional attributes: + +* cascade - Cascade Option +* fetch - One of LAZY or EAGER + +Example: + + /** + * @ManyToOne(targetEntity="Cart", cascade="ALL", fetch="EAGER") + */ + private $cart; + + ++++ @ManyToMany + +Defines an instance variable holds a many-to-many relationship between two entities. [@JoinTable](#ann_jointable) +is an additional, optional annotation that has reasonable default configuration values using the table +and names of the two related entities. + +Required attributes: + +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. + +Optional attributes: + +* mappedBy - This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship. +* cascade - Cascade Option +* fetch - One of LAZY or EAGER + +Example: + + /** + * Owning Side + * + * @ManyToMany(targetEntity="Group") + * @JoinTable(name="user_groups", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} + * ) + */ + private $groups; + + /** + * Inverse Side + * + * @ManyToMany(targetEntity="User", mappedBy="groups") + */ + private $features; + + ++++ @MappedSuperclass + +An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information +for its subclasses, but which is not itself an entity. This annotation is specified on the Class docblock +and has no additional attributes. + +The @MappedSuperclass annotation cannot be used in conjunction with @Entity. See the Inheritance Mapping +section for [more details on the restrictions of mapped superclasses](/../inheritance-mapping#mapped-superclasses). + + ++++ @OnetoOne + +The @OneToOne annotation works almost exactly as the [@ManyToOne](#ann_manytoone) with one additional option +that can be specified. The configuration defaults for [@JoinColumn](#ann_joincolumn) using the target entity table and primary key column names +apply here too. + +Required attributes: + +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. + +Optional attributes: + +* cascade - Cascade Option +* fetch - One of LAZY or EAGER +* orphanRemoval - Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any +owning instance, should be removed by Doctrine. Defaults to false. + + ++++ @OneToMany + + ++++ @OrderBy + +Optional annotation that can be specified with a [@ManyToMany](#ann_manytomany) or [@OneToMany](#ann_onetomany) +annotation to specify by which criteria the collection should be retrieved from the database by using an ORDER BY +clause. + +This annotation requires a single non-attributed value with an DQL snippet: + +Example: + + /** + * @ManyToMany(targetEntity="Group") + * @OrderBy({"name" = "ASC"}) + */ + private $groups; + +The DQL Snippet in OrderBy is only allowed to consist of unqualified, +unquoted field names and of an optional ASC/DESC positional statement. +Multiple Fields are separated by a comma (,). The referenced field +names have to exist on the `targetEntity` class of the `@ManyToMany` or +`@OneToMany` annotation. + + ++++ @PostLoad + +Marks a method on the entity to be called as a @PostLoad event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PostPersist + +Marks a method on the entity to be called as a @PostPersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PostRemove + +Marks a method on the entity to be called as a @PostRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PostUpdate + +Marks a method on the entity to be called as a @PostUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PrePersist + +Marks a method on the entity to be called as a @PrePersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PreRemove + +Marks a method on the entity to be called as a @PreRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @PreUpdate + +Marks a method on the entity to be called as a @PreUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. + + ++++ @SequenceGenerator + +For the use with @generatedValue(strategy="SEQUENCE") this annotation allows to specifiy details about the sequence, +such as the increment size and initial values of the sequence. + +Required attributes: + +* sequenceName - Name of the sequence + +Optional attributes: + +* allocationSize - Increment the sequence by the allocation size when its fetched. A value larger than 1 allows to optimize for scenarios where you create more than one new entity per request. Defaults to 10 +* initialValue - Where does the sequence start, defaults to 1. + +Example: + + /** + * @Id + * @GeneratedValue(strategy="SEQUENCE") + * @Column(type="integer") + * @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100) + */ + protected $id = null; + + ++++ @Table + +Annotation describes the table an entity is persisted in. It is placed on the entity-class PHP DocBlock and is optional. +If it is not specified the table name will default to the entities unqualified classname. + +Required attributes: + +* name - Name of the table + +Optional attributes: + +* schema - Database schema name of this table. +* indexes - Array of @Index annotations +* uniqueConstraints - Array of @UniqueConstraint annotations. + +Example: + + /** + * @Entity + * @Table(name="user", + * uniqueConstraints={@UniqueConstraint(name="user_unique",columns={"username"})}, + * indexes={@Index(name="user_idx", columns={"email"})} + * ) + */ + class User { } + + ++++ @UniqueConstraint + +Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the +SchemaTool to generate a database unique constraint on the specified table columns. It only has meaning in the SchemaTool +schema generation context. + +Required attributes: + +* name - Name of the Index +* columns - Array of columns. + +Example: + + /** + * @Entity + * @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "email"})}) + */ + class ECommerceProduct + { + } + + ++++ @Version + +Marker annotation that defines a specified column as version attribute used in an optimistic locking scenario. +It only works on [@Column](#ann_column) annotations that have the type integer or datetime. + +Example: + + /** + * @column(type="integer") + * @version + */ + protected $version; \ No newline at end of file diff --git a/manual/en/architecture.txt b/manual/en/architecture.txt new file mode 100644 index 000000000..45f10d160 --- /dev/null +++ b/manual/en/architecture.txt @@ -0,0 +1,81 @@ +This chapter gives an overview of the overall architecture, terminology and constraints of +Doctrine 2. It is recommended to read this chapter carefully. + +++ Entities + +An entity is a lightweight persistent domain object. An entity can be any regular +php class that obeys to the following restrictions: + +* An entity class must not be final or contain final methods. +* An entity class must not implement `__clone` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). +* An entity class must not implement `__wakeup` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). + Also consider implementing [Serializable](http://de3.php.net/manual/en/class.serializable.php]) instead. +* Any two entity classes in a class hierarchy that inherit directly or indirectly from one another must not have a mapped property with the same name. + That is, if B inherits from A then B must not have a mapped field with the same name as an already mapped field that is inherited from A. + +Entities support inheritance, polymorphic associations, and polymorphic queries. +Both abstract and concrete classes can be entities. Entities may extend non-entity +classes as well as entity classes, and non-entity classes may extend entity classes. + +> **TIP** +> The constructor of an entity is only ever invoked when *you* construct a new instance +> with the *new* keyword. Doctrine never calls entity constructors, thus you are free to use +> them as you wish and even have it require arguments of any type. + ++++ Entity states + +An entity instance can be characterized as being NEW, MANAGED, DETACHED or REMOVED. + +* A NEW entity instance has no persistent identity, and is not yet associated with an EntityManager and a UnitOfWork (i.e. those just created with the "new" operator). +* A MANAGED entity instance is an instance with a persistent identity that is associated with an EntityManager and whose persistence is thus managed. +* A DETACHED entity instance is an instance with a persistent identity that is not (or no longer) associated with an EntityManager and a UnitOfWork. +* A REMOVED entity instance is an instance with a persistent identity, associated with an EntityManager, that will be removed from the database upon transaction commit. + ++++ Persistent fields + +The persistent state of an entity is represented by instance variables. An +instance variable must be directly accessed only from within the methods of the +entity by the entity instance itself. Instance variables must not be accessed by +clients of the entity. The state of the entity is available to clients only through +the entity’s methods, i.e. accessor methods (getter/setter methods) or other +business methods. + +Collection-valued persistent fields and properties must be defined in terms of +the `Doctrine\Common\Collections\Collection` interface. The collection +implementation type may be used by the application to initialize fields or +properties before the entity is made persistent. Once the entity becomes +managed (or detached), subsequent access must be through the interface type. + ++++ Serializing entities + +Serializing entities can be problematic and is not really recommended, at least not as long as an +entity instance still holds references to proxy objects or is still managed by an EntityManager. +If you intend to serialize (and unserialize) entity instances that still hold references to proxy objects +you may run into problems with private properties because of technical limitations. +Proxy objects implement `__sleep` and it is not possible for `__sleep` to return names of +private properties in parent classes. On ther other hand it is not a solution for proxy objects +to implement `Serializable` because Serializable does not work well with any potential cyclic +object references (at least we did not find a way yet, if you did, please contact us). + +++ The EntityManager + +The `EntityManager` class is a central access point to the ORM functionality +provided by Doctrine 2. The `EntityManager` API is used to manage the persistence +of your objects and to query for persistent objects. + ++++ Transactional write-behind + +An `EntityManager` and the underlying `UnitOfWork` employ a strategy called +"transactional write-behind" that delays the execution of SQL statements in +order to execute them in the most efficient way and to execute them at the end +of a transaction so that all write locks are quickly released. You should see +Doctrine as a tool to synchronize your in-memory objects with the database in +well defined units of work. Work with your objects and modify them as usual and +when you're done call `EntityManager#flush()` to make your changes persistent. + ++++ The Unit of Work + +Internally an `EntityManager` uses a `UnitOfWork`, which is a typical +implementation of the [Unit of Work pattern](http://martinfowler.com/eaaCatalog/unitOfWork.html), to keep track of all the things that need to be done +the next time `flush` is invoked. You usually do not directly interact with +a `UnitOfWork` but with the `EntityManager` instead. \ No newline at end of file diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt new file mode 100644 index 000000000..5190f6a14 --- /dev/null +++ b/manual/en/association-mapping.txt @@ -0,0 +1,429 @@ +This chapter explains how associations between entities are mapped with Doctrine. We start out with an explanation of the concept of owning and inverse sides which is important to understand when working with bidirectional associations. Please read these explanations carefully. + +++ Owning Side and Inverse Side + +When mapping bidirectional associations it is important to understand the concept of the owning and inverse sides. The following general rules apply: + +* Relationships may be bidirectional or unidirectional. +* A bidirectional relationship has both an owning side and an inverse side. +* A unidirectional relationship only has an owning side. +* The owning side of a relationship determines the updates to the relationship in the database. + + +The following rules apply to *bidirectional* associations: + +* The inverse side of a bidirectional relationship must refer to its owning side by use of the mappedBy attribute of the OneToOne, OneToMany, or ManyToMany mapping declaration. The mappedBy attribute designates the field in the entity that is the owner of the relationship. +* The owning side of a bidirectional relationship must refer to its inverse side by use of the inversedBy attribute of the OneToOne, ManyToOne, or ManyToMany mapping declaration. The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. +* The many side of OneToMany/ManyToOne bidirectional relationships *must* be the owning side, hence the mappedBy element can not be specified on the ManyToOne side. +* For OneToOne bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key (@JoinColumn(s)). +* For ManyToMany bidirectional relationships either side may be the owning side (the side that defines the @JoinTable and/or does not make use of the mappedBy attribute, thus using a default join table). + +Especially important is the following: + +**The owning side of a relationship determines the updates to the relationship in the database**. + +To fully understand this, remember how bidirectional associations are maintained +in the object world. There are 2 references on each side of the association +and these 2 references both represent the same association but can change +independently of one another. Of course, in a correct application the semantics +of the bidirectional association are properly maintained by the application +developer (that's his responsiblity). Doctrine needs to know which of +these two in-memory references is the one that should be persisted and which +not. This is what the owning/inverse concept is mainly used for. + +**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)** + +The owning side of a bidirectional association is the side Doctrine "looks at" when determining +the state of the association, and consequently whether there is anything to do to update the association +in the database. + +> **NOTE** +> "Owning side" and "inverse side" are technical concepts of the ORM technology, not concepts +> of your domain model. What you consider as the owning side in your domain model can be different +> from what the owning side is for Doctrine. These are unrelated. + +++ Collections + +In all the examples of many-valued associations in this manual we will make use of a `Collection` interface and a corresponding default implementation `ArrayCollection` that are defined in the `Doctrine\Common\Collections` namespace. Why do we need that? Doesn't that couple my domain model to Doctrine? Unfortunately, PHP arrays, while being great for many things, do not make up for good collections of business objects, especially not in the context of an ORM. The reason is that plain PHP arrays can not be transparently extended / instrumented in PHP code, which is necessary for a lot of advanced ORM features. The classes / interfaces that come closest to an OO collection are ArrayAccess and ArrayObject but until instances of these types can be used in all places where a plain array can be used (something that may happen in PHP6) their useability is fairly limited. You "can" type-hint on `ArrayAccess` instead of `Collection`, since the Collection interface extends `ArrayAccess`, but this will severely limit you in the way you can work with the collection, because the `ArrayAccess` API is (intentionally) very primitive and more importantly because you can not pass this collection to all the useful PHP array functions, which makes it very hard to work with. + +> **CAUTION** +> The Collection interface and ArrayCollection class, like everything else in the +> Doctrine\Common namespace, are neither part of the ORM, nor the DBAL, it is a plain PHP +> class that has no outside dependencies apart from dependencies on PHP itself (and the +> SPL). Therefore using this class in your domain classes and elsewhere does not introduce +> a coupling to the persistence layer. The Collection class, like everything else in the +> Common namespace, is not part of the persistence layer. You could even copy that class +> over to your project if you want to remove Doctrine from your project and all your +> domain classes will work the same as before. + +++ Mapping Defaults + +The @JoinColumn and @JoinTable definitions are usually optional and have sensible default values. The defaults for a join column in a one-to-one/many-to-one association is as follows: + + name: "_id" + referencedColumnName: "id" + +As an example, consider this mapping: + + [php] + /** @OneToOne(targetEntity="Shipping") */ + private $shipping; + +This is essentially the same as the following, more verbose, mapping: + + [php] + /** + * @OneToOne(targetEntity="Shipping") + * @JoinColumn(name="shipping_id", referencedColumnName="id") + */ + private $shipping; + + +The @JoinTable definition used for many-to-many mappings has similar defaults. As an example, consider this mapping: + + [php] + class User { + //... + /** @ManyToMany(targetEntity="Group") */ + private $groups; + //... + } + +This is essentially the same as the following, more verbose, mapping: + + [php] + class User { + //... + /** + * @ManyToMany(targetEntity="Group") + * @JoinTable(name="User_Group", + * joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")} + * ) + */ + private $groups; + //... + } + +In that case, the name of the join table defaults to a combination of the simple, unqualified class names of the participating classes, separated by an underscore character. The names of the join columns default to the simple, unqualified class name of the targeted class followed by "_id". The referencedColumnName always defaults to "id", just as in one-to-one or many-to-one mappings. + +If you accept these defaults, you can reduce the mapping code to a minimum. + +++ One-To-One, Unidirectional + +A unidirectional one-to-one association is very common. Here is an example of a `Product` that has one `Shipping` object associated to it. The `Shipping` side does not reference back to the `Product` so it is unidirectional. + + [php] + /** @Entity */ + class Product + { + // ... + + /** + * @OneToOne(targetEntity="Shipping") + * @JoinColumn(name="shipping_id", referencedColumnName="id") + */ + private $shipping; + + // ... + } + + /** @Entity */ + class Shipping + { + // ... + } + +Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. + +++ One-To-One, Bidirectional + +Here is a one-to-one relationship between a `Customer` and a `Cart`. The `Cart` +has a reference back to the `Customer` so it is bidirectional. + + [php] + /** @Entity */ + class Customer + { + // ... + + /** + * @OneToOne(targetEntity="Cart", mappedBy="customer") + */ + private $cart; + + // ... + } + + /** @Entity */ + class Cart + { + // ... + + /** + * @OneToOne(targetEntity="Customer", inversedBy="cart") + * @JoinColumn(name="customer_id", referencedColumnName="id") + */ + private $customer; + + // ... + } + +Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. + +++ One-To-One, Self-referencing + +You can easily have self referencing one-to-one relationships like below. + + [php] + /** @Entity */ + class Customer + { + // ... + + /** + * @OneToOne(targetEntity="Customer") + * @JoinColumn(name="mentor_id", referencedColumnName="id") + */ + private $mentor; + + // ... + } + +Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. + +++ One-To-Many, Unidirectional with Join Table + +A unidirectional one-to-many association can be mapped through a join table. From Doctrine's point of view, it is simply mapped as a unidirectional many-to-many whereby a unique constraint on one of the join columns enforces the one-to-many cardinality. +The following example sets up such a unidirectional one-to-many association: + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToMany(targetEntity="Phonenumber") + * @JoinTable(name="users_phonenumbers", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} + * ) + */ + private $phonenumbers; + + // ... + } + + /** @Entity */ + class Phonenumber + { + // ... + } + +> **NOTE** +> One-To-Many uni-directional relations with join-table only work using the @ManyToMany annotation and a unique-constraint. + +++ One-To-Many, Bidirectional + +Bidirectional one-to-many associations are very common. The following code shows an example with a Product and a Feature class: + + [php] + /** @Entity */ + class Product + { + // ... + /** + * @OneToMany(targetEntity="Feature", mappedBy="product") + */ + private $features; + // ... + } + + /** @Entity */ + class Feature + { + // ... + /** + * @ManyToOne(targetEntity="Product", inversedBy="features") + * @JoinColumn(name="product_id", referencedColumnName="id") + */ + private $product; + // ... + } + +Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. + +++ One-To-Many, Self-referencing + +You can also setup a one-to-many association that is self-referencing. In this example we +setup a hierarchy of `Category` objects by creating a self referencing relationship. +This effectively models a hierarchy of categories and from the database perspective is known as an adjacency list approach. + + [php] + /** @Entity */ + class Category + { + // ... + /** + * @OneToMany(targetEntity="Category", mappedBy="parent") + */ + private $children; + + /** + * @ManyToOne(targetEntity="Category", inversedBy="children") + * @JoinColumn(name="parent_id", referencedColumnName="id") + */ + private $parent; + // ... + } + +Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. + +++ Many-To-Many, Unidirectional + +Real many-to-many associations are less common. The following example shows a unidirectional association between User and Group entities: + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToMany(targetEntity="Group") + * @JoinTable(name="users_groups", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} + * ) + */ + private $groups; + + // ... + } + + /** @Entity */ + class Group + { + // ... + } + +> **NOTE** +> Why are many-to-many associations less common? Because frequently you want to associate +> additional attributes with an association, in which case you introduce an association +> class. Consequently, the direct many-to-many association disappears and is replaced +> by one-to-many/many-to-one associations between the 3 participating classes. + +++ Many-To-Many, Bidirectional + +Here is a similar many-to-many relationship as above except this one is bidirectional. + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToMany(targetEntity="Group", inversedBy="users") + * @JoinTable(name="users_groups", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} + * ) + */ + private $groups; + + // ... + } + + /** @Entity */ + class Group + { + // ... + /** + * @ManyToMany(targetEntity="User", mappedBy="groups") + */ + private $users; + // ... + } + +++ Many-To-Many, Self-referencing + +You can even have a self-referencing many-to-many association. A common scenario is where a `User` has friends and the target entity of that relationship is a `User` so it is self referencing. In this example it is bidirectional so `User` has a field named `$friendsWithMe` and `$myFriends`. + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToMany(targetEntity="User", mappedBy="myFriends") + */ + private $friendsWithMe; + + /** + * @ManyToMany(targetEntity="User", inversedBy="friendsWithMe") + * @JoinTable(name="friends", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")} + * ) + */ + private $myFriends; + + // ... + } + +++ Ordering To-Many Collections + +In many use-cases you will want to sort collections when they are retrieved from the database. +In userland you do this as long as you haven't initially saved an entity with its associations +into the database. To retrieve a sorted collection from the database you can use the +`@OrderBy` annotation with an collection that specifies an DQL snippet that is appended +to all queries with this collection. + +Additional to any `@OneToMany` or `@ManyToMany` annotation you can specify the `@OrderBy` +in the following way: + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToMany(targetEntity="Group") + * @OrderBy({"name" = "ASC"}) + */ + private $groups; + } + +The DQL Snippet in OrderBy is only allowed to consist of unqualified, +unquoted field names and of an optional ASC/DESC positional statement. +Multiple Fields are separated by a comma (,). The referenced field +names have to exist on the `targetEntity` class of the `@ManyToMany` or +`@OneToMany` annotation. + +The semantics of this feature can be described as follows. + +* `@OrderBy` acts as an implicit ORDER BY clause for the given fields, that is appended +to all the explicitly given ORDER BY items. +* All collections of the ordered type are always retrieved in an ordered fashion. +* To keep the database impact low, these implicit ORDER BY items are only added +to an DQL Query if the collection is fetch joined in the DQL query. + +Given our previously defined Example: + + [sql] + -- Would not add ORDER BY, since g is not fetch joined + SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10 + + -- However + SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 + -- would internally be rewritten to + SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC + + -- You can't reverse the order, an explicit: + SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC + -- is internally be rewritten to + SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt new file mode 100644 index 000000000..17910de81 --- /dev/null +++ b/manual/en/basic-mapping.txt @@ -0,0 +1,279 @@ +This chapter explains the basic mapping of objects and properties. Mapping of associations will be covered in the next chapter "Association Mapping". + +++ Mapping Drivers + +Doctrine provides several different ways for specifying object-relational mapping metadata: + +* Docblock Annotations +* XML +* YAML + +This manual usually uses docblock annotations in all the examples that are spread throughout all chapters. There are dedicated chapters for XML and YAML mapping, respectively. + +> **NOTE** +> If you're wondering which mapping driver gives the best performance, the answer is: +> None. Once the metadata of a class has been read from the source (annotations, xml or +> yaml) it is stored in an instance of the `Doctrine\ORM\Mapping\ClassMetadata` class +> and these instances are stored in the metadata cache. Therefore at the end of the day +> all drivers perform equally well. If you're not using a metadata cache (not +> recommended!) then the XML driver might have a slight edge in performance due to the +> powerful native XML support in PHP. + +++ Introduction to Docblock Annotations + +You've probably used docblock annotations in some form already, most likely to provide documentation metadata for a tool like `PHPDocumentor` (@author, @link, ...). Docblock annotations are a tool to embed metadata inside the documentation section which can then be processed by some tool. Doctrine 2 generalizes the concept of docblock annotations so that they can be used for any kind of metadata and so that it is easy to define new docblock annotations. In order to allow more involved annotation values and to reduce the chances of clashes with other docblock annotations, the Doctrine 2 docblock annotations feature an alternative syntax that is heavily inspired by the Annotation syntax introduced in Java 5. + +The implementation of these enhanced docblock annotations is located in the `Doctrine\Common\Annotations` namespace and therefore part of the Common package. Doctrine 2 docblock annotations support namespaces and nested annotations among other things. The Doctrine 2 ORM defines its own set of docblock annotations for supplying object-relational mapping metadata. + +> **NOTE** +> If you're not comfortable with the concept of docblock annotations, don't worry, as +> mentioned earlier Doctrine 2 provides XML and YAML alternatives and you could easily +> implement your own favourite mechanism for defining ORM metadata. + +++ Persistent classes + +In order to mark a class for object-relational persistence it needs to be designated as an entity. This can be done through the `@Entity` marker annotation. + + [php] + /** @Entity */ + class MyPersistentClass + { + //... + } + +By default, the entity will be persisted to a table with the same name as the class name. In order to change that, you can use the `@Table` annotation as follows: + + [php] + /** + * @Entity + * @Table(name="my_persistent_class") + */ + class MyPersistentClass + { + //... + } + +Now instances of MyPersistentClass will be persisted into a table named `my_persistent_class`. + +++ Doctrine Mapping Types + +A Doctrine Mapping Type defines the mapping between a PHP type and an SQL type. All Doctrine Mapping Types that ship with Doctrine are fully portable between different RDBMS. You can even write your own custom mapping types that might or might not be portable, which is explained later in this chapter. + +For example, the Doctrine Mapping Type `string` defines the mapping from a PHP string to an SQL VARCHAR (or VARCHAR2 etc. depending on the RDBMS brand). Here is a quick overview of the built-in mapping types: + +* `string`: Type that maps an SQL VARCHAR to a PHP string. +* `integer`: Type that maps an SQL INT to a PHP integer. +* `smallint`: Type that maps a database SMALLINT to a PHP integer. +* `bigint`: Type that maps a database BIGINT to a PHP string. +* `boolean`: Type that maps an SQL boolean to a PHP boolean. +* `decimal`: Type that maps an SQL DECIMAL to a PHP double. +* `date`: Type that maps an SQL DATETIME to a PHP DateTime object. +* `time`: Type that maps an SQL TIME to a PHP DateTime object. +* `datetime`: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object. +* `text`: Type that maps an SQL CLOB to a PHP string. + +> **NOTE** +> Doctrine Mapping Types are NOT SQL types and NOT PHP types! They are mapping types +> between 2 types. + +++ Property Mapping + +After a class has been marked as an entity it can specify mappings for its instance fields. Here we will only look at simple fields that hold scalar values like strings, numbers, etc. Associations to other objects are covered in the chapter "Association Mapping". + +To mark a property for relational persistence the `@Column` docblock annotation is used. This annotation usually requires at least 1 attribute to be set, the `type`. The `type` attribute specifies the Doctrine Mapping Type to use for the field. If the type is not specified, 'string' is used as the default mapping type since it is the most flexible. + +Example: + + [php] + /** @Entity */ + class MyPersistentClass + { + /** @Column(type="integer") */ + private $id; + /** @Column(length=50) */ + private $name; // type defaults to string + //... + } + +In that example we mapped the field `id` to the column `id` using the mapping type `integer` and the field `name` is mapped to the column `name` with the default mapping type `string`. As you can see, by default the column names are assumed to be the same as the field names. To specify a different name for the column, you can use the `name` attribute of the Column annotation as follows: + + [php] + /** @Column(name="db_name") */ + private $name; + +The Column annotation has some more attributes. Here is a complete list: + +* `type`: (optional, defaults to 'string') The mapping type to use for the column. +* `name`: (optional, defaults to field name) The name of the column in the database. +* `length`: (optional, default 255) The length of the column in the database. (Applies only if a string-valued column is used). +* `unique`: (optional, default FALSE) Whether the column is a unique key. +* `nullable`: (optional, default FALSE) Whether the database column is nullable. +* `precision`: (optional, default 0) The precision for a decimal (exact numeric) column. (Applies only if a decimal column is used.) +* `scale`: (optional, default 0) The scale for a decimal (exact numeric) column. (Applies only if a decimal column is used.) + +++ Custom Mapping Types + +Doctrine allows you to create new mapping types. This can come in handy when you're missing a specific mapping type +or when you want to replace the existing implementation of a mapping type. + +In order to create a new mapping type you need to subclass `Doctrine\DBAL\Types\Type` and implement/override +the methods as you wish. Here is an example skeleton of such a custom type class: + + [php] + namespace My\Project\Types; + + use Doctrine\DBAL\Types\Type; + use Doctrine\DBAL\Platforms\AbstractPlatform; + + /** + * My custom datatype. + */ + class MyType extends Type + { + public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + // return the SQL used to create your column type. To create a portable column type, use the $platform. + } + + public function convertToPHPValue($value, AbstractPlatform $platform) + { + // This is executed when the value is read from the database. Make your conversions here, optionally using the $platform. + } + + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + // This is executed when the value is written to the database. Make your conversions here, optionally using the $platform. + } + } + +Restrictions to keep in mind: + +* If the value of the field is *NULL* the method `convertToDatabaseValue()` is not called. +* The `UnitOfWork` never passes values to the database convert method that did not change in the request. + +When you have implemented the type you still need to let Doctrine know about it. This can be achieved +through the `Doctrine\DBAL\Configuration#setCustomTypes(array $types)` method. + +> **NOTE** +> `Doctrine\ORM\Configuration` is a subclass of `Doctrine\DBAL\Configuration`, so the +> methods are available on your ORM Configuration instance as well. + +Here is an example: + + [php] + // in bootstrapping code + + // ... + + use Doctrine\DBAL\Types\Type; + + // ... + + // Register my type + Type::addType('mytype', 'My\Project\Types\MyType'); + +As can be seen above, when registering the custom types in the configuration you specify a unique name +for the mapping type and map that to the corresponding fully qualified class name. Now you can use your new type in your mapping like this: + + [php] + class MyPersistentClass + { + /** @Column(type="mytype") */ + private $field; + } + +++ Identifiers / Primary Keys + +Every entity class needs an identifier/primary key. You designate the field that serves as the identifier with the `@Id` marker annotation. Here is an example: + + [php] + class MyPersistentClass + { + /** @Id @Column(type="integer") */ + private $id; + //... + } + +Without doing anything else, the identifier is assumed to be manually assigned. That means your code would need to properly set the identifier property before passing a new entity to `EntityManager#persist($entity)`. + +A common alternative strategy is to use a generated value as the identifier. To do this, you use the `@GeneratedValue` annotation like this: + + [php] + class MyPersistentClass + { + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + private $id; + } + +This tells Doctrine to automatically generate a value for the identifier. How this value is generated is specified by the `strategy` attribute, which is optional and defaults to 'AUTO'. A value of `AUTO` tells Doctrine to use the generation strategy that is preferred by the currently used database platform. For MySql, for example, Doctrine would use the `IDENTITY` strategy which means a typical AUTO_INCREMENT column. For PostgreSql it would choose to use the `SEQUENCE` strategy which would result in using a database sequence. + ++++ Id Generation Strategies + +The previous example showed how to use the default Id generation strategy without knowing the underlying database with the AUTO-detection strategy. +It is also possible to specifiy the Id Generation strategy more explicitly, which allows to make use of some additional features. + +Here is the list of possible generation strategies: + +* `AUTO` (default): Tells Doctrine to pick the strategy that is preferred by the used database platform. This strategy provides full portability. +* `NONE`: Tells Doctrine that you generated the entities primary key value in userland before `EntityManager#persist()` is called. +* `SEQUENCE`: Tells Doctrine to use a database sequence for ID generation. This strategy does currently not provide full portability. Sequences are supported by Oracle and PostgreSql. +* `IDENTITY`: Tells Doctrine to use special identity columns in the database that usually generate a value on insertion of a row (i.e. MySql AUTO_INCREMENT). This strategy does currently not provide full portability. IDENTITY is supported by MySql, Sqlite and MsSql. +* `TABLE`: Tells Doctrine to use a separate table for ID generation. This strategy provides full portability. ***This strategy is not yet implemented!*** + +++++ Sequence Generator + +The Sequence Generator can be used in conjunction with Oracle or Postgres Platforms and allows some additional configuration options besides +specifiying the sequence's name: + + [php] + class User { + /** + * @Id + * @GeneratedValue(strategy="SEQUENCE") + * @SequenceGenerator(name="tablename_seq", initialValue=1, allocationSize=100) + */ + protected $id = null; + } + +The initial value specifies at which value the sequence should start. + +Allocation Size is a powerful feature to optimize INSERT performance of Doctrine. The allocation size specifies +by how much values the sequence is incremented whenever the next value is retrieved. If this is larger than one +Doctrine 2 can generate Id values for the allocationSizes amount of entities. In the above example with +`allocationSize=100` Doctrine 2 would only need to access the sequence once to generate the identifiers for +100 new entities. + +> **CAUTION** + +> Allocation Size is detected by SchemaTool and transformed into an "INCREMENT BY " clause +> in the CREATE SEQUENCE statement. For a database schema created by you (and not SchemaTool) you have to +> make sure that the allocationSize configuration option is never larger than the actual sequences INCREMENT BY value. + ++++ Composite Keys + +Doctrine 2 allows to use composite primary keys. There are however some restrictions oposed to using a single identifier. +The use of the `@GeneratedValue` annotation is only supported for simple (not composite) primary keys, which means +you can only use composite keys if you generate the primary key values yourself before calling `EntityManager#persist()` +on the entity. + +To designate a composite primary key / identifier, simply put the @Id marker annotation on all fields that make up the primary key. + +++ Quoting Reserved Words + +It may sometimes be necessary to quote a column or table name because it conflicts with a reserved word of the particular RDBMS in use. This is often referred to as "Identifier Quoting". To let Doctrine know that you would like a table or column name to be quoted in all SQL statements, enclose the table or column name in backticks. Here is an example: + + [php] + /** @Column(name="`number`", type="integer") */ + private $number; + +Doctrine will then quote this column name in all SQL statements according to the used database platform. + +> **CAUTION** +> Identifier Quoting is not supported for join column names or discriminator column names. + +> **CAUTION** +> Identifier Quoting is a feature that is mainly intended to support legacy database +> schemas. The use of reserved words and identifier quoting is generally discouraged. \ No newline at end of file diff --git a/manual/en/batch-processing.txt b/manual/en/batch-processing.txt new file mode 100644 index 000000000..b9c3619d1 --- /dev/null +++ b/manual/en/batch-processing.txt @@ -0,0 +1,122 @@ +This chapter shows you how to accomplish bulk inserts, updates and deletes with Doctrine in an efficient way. The main problem with bulk operations is usually not to run out of memory and this is especially what the strategies presented here provide help with. + +> **CAUTION** +> An ORM tool is not primarily well-suited for mass inserts, updates or deletions. +> Every RDBMS has its own, most effective way of dealing with such operations and if +> the options outlined below are not sufficient for your purposes we recommend you +> use the tools for your particular RDBMS for these bulk operations. + +++ Bulk Inserts + +Bulk inserts in Doctrine are best performed in batches, taking advantage of the transactional write-behind behavior of an `EntityManager`. The following code shows an example for inserting 10000 objects with a batch size of 20. You may need to experiment with the batch size to find the size that works best for you. Larger batch sizes mean more prepared statement reuse internally but also mean more work during `flush`. + + [php] + $batchSize = 20; + for ($i = 1; $i <= 10000; ++$i) { + $user = new CmsUser; + $user->setStatus('user'); + $user->setUsername('user' . $i); + $user->setName('Mr.Smith-' . $i); + $em->persist($user); + if (($i % $batchSize) == 0) { + $em->flush(); + $em->clear(); // Detaches all objects from Doctrine! + } + } + +++ Bulk Updates + +There are 2 possibilities for bulk updates with Doctrine. + ++++ DQL UPDATE + +The by far most efficient way for bulk updates is to use a DQL UPDATE query. Example: + + [php] + $q = $em->createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9'); + $numUpdated = $q->execute(); + ++++ Iterating results + +An alternative solution for bulk updates is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this, combining the iteration with the batching strategy that was already used for bulk inserts: + + [php] + $batchSize = 20; + $i = 0; + $q = $em->createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + foreach($iterableResult AS $row) { + $user = $row[0]; + $user->increaseCredit(); + $user->calculateNewBonuses(); + if (($i % $batchSize) == 0) { + $em->flush(); // Executes all updates. + $em->clear(); // Detaches all objects from Doctrine! + } + ++$i; + } + +> **NOTE** +> Iterating results is not possible with queries that fetch-join a collection-valued +> association. The nature of such SQL result sets is not suitable for incremental +> hydration. + +++ Bulk Deletes + +There are two possibilities for bulk deletes with Doctrine. You can either issue +a single DQL DELETE query or you can iterate over results removing them one at a time. + ++++ DQL DELETE + +The by far most efficient way for bulk deletes is to use a DQL DELETE query. + +Example: + + [php] + $q = $em->createQuery('delete from MyProject\Model\Manager m where m.salary > 100000'); + $numDeleted = $q->execute(); + ++++ Iterating results + +An alternative solution for bulk deletes is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this: + + [php] + $batchSize = 20; + $i = 0; + $q = $em->createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + while (($row = $iterableResult->next()) !== false) { + $em->remove($row[0]); + if (($i % $batchSize) == 0) { + $em->flush(); // Executes all deletions. + $em->clear(); // Detaches all objects from Doctrine! + } + ++$i; + } + +> **NOTE** +> Iterating results is not possible with queries that fetch-join a collection-valued +> association. The nature of such SQL result sets is not suitable for incremental +> hydration. + +++ Iterating Large Results for Data-Processing + +You can use the `iterate()` method just to iterate over a large result and no UPDATE or DELETE +intention. The `IterableResult` instance returned from `$query->iterate()` implements the +Iterator interface so you can process a large result without memory problems using the +following approach: + + [php] + $q = $this->_em->createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + foreach ($iterableResult AS $row) { + // do stuff with the data in the row, $row[0] is always the object + + // detach from Doctrine, so that it can be Garbage-Collected immediately + $this->_em->detach($row[0]); + } + +> **NOTE** +> Iterating results is not possible with queries that fetch-join a collection-valued +> association. The nature of such SQL result sets is not suitable for incremental +> hydration. \ No newline at end of file diff --git a/manual/en/best-practices.txt b/manual/en/best-practices.txt new file mode 100644 index 000000000..9c095a5ae --- /dev/null +++ b/manual/en/best-practices.txt @@ -0,0 +1,77 @@ + +> **NOTE** +> The best practices mentioned here that affect database design generally refer to best +> practices when working with Doctrine and do not necessarily reflect best practices for +> database design in general. + +++ Don't use public properties on entities + +It is very important that you don't map public properties on entities, but only protected or private ones. +The reason for this is simple, whenever you access a public property of a proxy object that hasn't been initialized +yet the return value will be null. Doctrine cannot hook into this process and magically make the entity lazy load. + +This can create situations where it is very hard to debug the current failure. We therefore urge you to map only +private and protected properties on entities and use getter methods or magic __get() to access them. + +++ Constrain relationships as much as possible + +It is important to constrain relationships as much as possible. This means: + +* Impose a traversal direction (avoid bidirectional associations if possible) +* Eliminate nonessential associations + +This has several benefits: + +* Reduced coupling in your domain model +* Simpler code in your domain model (no need to maintain bidirectionality properly) +* Less work for Doctrine + +++ Avoid composite keys + +Even though Doctrine fully supports composite keys it is best not to use them if possible. Composite keys require additional work by Doctrine and thus have a higher probability of errors. + +++ Use events judiciously + +The event system of Doctrine is great and fast. Even though making heavy use of events, especially lifecycle events, can have a negative impact on the performance of your application. Thus you should use events judiciously. + +++ Use cascades judiciously + +Automatic cascades of the persist/remove/merge/etc. operations are very handy but should be used wisely. Do NOT simply add all cascades to all associations. Think about which cascades actually do make sense for you for a particular association, given the scenarios it is most likely used in. + +++ Don't use special characters + +Avoid using any non-ASCII characters in class, field, table or column names. Doctrine itself is not unicode-safe in many places and will not be until PHP itself is fully unicode-aware (PHP6). + +++ Don't use identifier quoting + +Identifier quoting is a workaround for using reserved words that often causes problems in edge cases. Do not use identifier quoting and avoid using reserved words as table or column names. + +++ Initialize collections in the constructor + +It is recommended best practice to initialize any business collections in entities in the constructor. Example: + + [php] + namespace MyProject\Model; + use Doctrine\Common\Collections\ArrayCollection; + + class User { + private $addresses; + private $articles; + + public function __construct() { + $this->addresses = new ArrayCollection; + $this->articles = new ArrayCollection; + } + } + +++ Don't map foreign keys to fields in an entity + +Foreign keys have no meaning whatsoever in an object model. Foreign keys are how a relational database establishes relationships. Your object model establishes relationships through object references. Thus mapping foreign keys to object fields heavily leaks details of the relational model into the object model, something you really should not do. + +++ Use explicit transaction demarcation + +While Doctrine will automatically wrap all DML operations in a transaction on flush(), it is considered best practice to explicitly set the transaction boundaries yourself. +Otherwise every single query is wrapped in a small transaction (Yes, SELECT queries, too) since you can not talk to your database outside of a transaction. +While such short transactions for read-only (SELECT) queries generally dont have any noticable performance impact, it is still preferrable to use fewer, well-defined transactions +that are established through explicit transaction boundaries. + diff --git a/manual/en/caching.txt b/manual/en/caching.txt new file mode 100644 index 000000000..f3e5d202f --- /dev/null +++ b/manual/en/caching.txt @@ -0,0 +1,350 @@ +Doctrine provides cache drivers in the `Common` package for some of the most +popular caching implementations such as APC, Memcache and Xcache. We also provide +an `ArrayCache` driver which stores the data in a PHP array. Obviously, the cache +does not live between requests but this is useful for testing in a development +environment. + +++ Cache Drivers + +The cache drivers follow a simple interface that is defined in `Doctrine\Common\Cache\Cache`. +All the cache drivers extend a base class `Doctrine\Common\Cache\AbstractCache` +which implements the before mentioned interface. + +The interface defines the following methods for you to publicly use. + + * fetch($id) - Fetches an entry from the cache. + * contains($id) - Test if an entry exists in the cache. + * save($id, $data, $lifeTime = false) - Puts data into the cache. + * delete($id) - Deletes a cache entry. + +Each driver extends the `AbstractCache` class which defines a few abstract +protected methods that each of the drivers must implement. + + * _doFetch($id) + * _doContains($id) + * _doSave($id, $data, $lifeTime = false) + * _doDelete($id) + +The public methods `fetch()`, `contains()`, etc. utilize the above protected methods +that are implemented by the drivers. The code is organized this way so that the +protected methods in the drivers do the raw interaction with the cache implementation +and the `AbstractCache` can build custom functionality on top of these methods. + ++++ APC + +In order to use the APC cache driver you must have it compiled and enabled in +your php.ini. You can read about APC [here](http://us2.php.net/apc) on the PHP +website. It will give you a little background information about what it is and +how you can use it as well as how to install it. + +Below is a simple example of how you could use the APC cache driver by itself. + + [php] + $cacheDriver = new \Doctrine\Common\Cache\ApcCache(); + $cacheDriver->save('cache_id', 'my_data'); + ++++ Memcache + +In order to use the Memcache cache driver you must have it compiled and enabled in +your php.ini. You can read about Memcache [here](http://us2.php.net/memcache) on +the PHP website. It will give you a little background information about what it is +and how you can use it as well as how to install it. + +Below is a simple example of how you could use the Memcache cache driver by itself. + + [php] + $memcache = new Memcache(); + $memcache->connect('memcache_host', 11211); + + $cacheDriver = new \Doctrine\Common\Cache\MemcacheCache(); + $cacheDriver->setMemcache() + $cacheDriver->save('cache_id', 'my_data'); + ++++ Xcache + +In order to use the Xcache cache driver you must have it compiled and enabled in +your php.ini. You can read about Xcache [here](http://xcache.lighttpd.net/). It +will give you a little background information about what it is and how you can +use it as well as how to install it. + +Below is a simple example of how you could use the Xcache cache driver by itself. + + [php] + $cacheDriver = new \Doctrine\Common\Cache\XcacheCache(); + $cacheDriver->save('cache_id', 'my_data'); + +++ Using Cache Drivers + +In this section we'll describe how you can fully utilize the API of the cache +drivers to save cache, check if some cache exists, fetch the cached data and +delete the cached data. We'll use the `ArrayCache` implementation as our +example here. + + [php] + $cacheDriver = new \Doctrine\Common\Cache\ArrayCache(); + ++++ Saving + +To save some data to the cache driver it is as simple as using the `save()` method. + + [php] + $cacheDriver->save('cache_id', 'my_data'); + +The `save()` method accepts three arguments which are described below. + + * `$id` - The cache id + * `$data` - The cache entry/data. + * `$lifeTime` - The lifetime. If != false, sets a specific lifetime for this cache entry (null => infinite lifeTime). + +You can save any type of data whether it be a string, array, object, etc. + + [php] + $array = array( + 'key1' => 'value1', + 'key2' => 'value2' + ); + $cacheDriver->save('my_array', $array); + ++++ Checking + +Checking whether some cache exists is very simple, just use the `contains()` method. +It accepts a single argument which is the ID of the cache entry. + + [php] + if ($cacheDriver->contains('cache_id')) { + echo 'cache exists'; + } else { + echo 'cache does not exist'; + } + ++++ Fetching + +Now if you want to retrieve some cache entry you can use the `fetch()` method. It +also accepts a single argument just like `contains()` which is the ID of the cache entry. + + [php] + $array = $cacheDriver->fetch('my_array'); + ++++ Deleting + +As you might guess, deleting is just as easy as saving, checking and fetching. +We have a few ways to delete cache entries. You can delete by an individual ID, +regular expression, prefix, suffix or you can delete all entries. + +++++ By Cache ID + + [php] + $cacheDriver->delete('my_array'); + +You can also pass wild cards to the `delete()` method and it will return an array +of IDs that were matched and deleted. + + [php] + $deleted = $cacheDriver->delete('users_*'); + +++++ By Regular Expression + +If you need a little more control than wild cards you can use a PHP regular +expression to delete cache entries. + + [php] + $deleted = $cacheDriver->deleteByRegex('/users_.*/'); + +++++ By Prefix + +Because regular expressions are kind of slow, if simply deleting by a prefix or +suffix is sufficient, it is recommended that you do that instead of using a regular +expression because it will be much faster if you have many cache entries. + + [php] + $deleted = $cacheDriver->deleteByPrefix('users_'); + +++++ By Suffix + +Just like we did above with the prefix you can do the same with a suffix. + + [php] + $deleted = $cacheDriver->deleteBySuffix('_my_account'); + +++++ All + +If you simply want to delete all cache entries you can do so with the `deleteAll()` +method. + + [php] + $deleted = $cacheDriver->deleteAll(); + ++++ Counting + +If you want to count how many entries are stored in the cache driver instance +you can use the `count()` method. + + [php] + echo $cacheDriver->count(); + +> **NOTE** +> In order to use `deleteByRegex()`, `deleteByPrefix()`, `deleteBySuffix()`, +> `deleteAll()`, `count()` or `getIds()` you must enable an option for the cache +> driver to manage your cache IDs internally. This is necessary because APC, +> Memcache, etc. don't have any advanced functionality for fetching and deleting. +> We add some functionality on top of the cache drivers to maintain an index of +> all the IDs stored in the cache driver so that we can allow more granular deleting +> operations. +> +> [php] +> $cacheDriver->setManageCacheIds(true); + ++++ Namespaces + +If you heavily use caching in your application and utilize it in multiple parts +of your application, or use it in different applications on the same server you +may have issues with cache naming collisions. This can be worked around by using +namespaces. You can set the namespace a cache driver should use by using the +`setNamespace()` method. + + [php] + $cacheDriver->setNamespace('my_namespace_'); + +++ Integrating with the ORM + +The Doctrine ORM package is tightly integrated with the cache drivers to allow +you to improve performance of various aspects of Doctrine by just simply making +some additional configurations and method calls. + ++++ Query Cache + +It is highly recommended that in a production environment you cache the +transformation of a DQL query to its SQL counterpart. It doesn't make sense to +do this parsing multiple times as it doesn't change unless you alter the DQL +query. + +This can be done by configuring the query cache implementation to use on your ORM +configuration. + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + ++++ Result Cache + +The result cache can be used to cache the results of your queries so that we +don't have to query the database or hydrate the data again after the first time. +You just need to configure the result cache implementation. + + [php] + $config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + +Now when you're executing DQL queries you can configure them to use the result cache. + + [php] + $query = $em->createQuery('select u from \Entities\User u'); + $query->useResultCache(true); + +You can also configure an individual query to use a different result cache driver. + + [php] + $query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache()); + +> **NOTE** +> Setting the result cache driver on the query will automatically enable the +> result cache for the query. If you want to disable it pass false to +> `useResultCache()`. +> +> [php] +> $query->useResultCache(false); + +If you want to set the time the cache has to live you can use the `setResultCacheLifetime()` +method. + + [php] + $query->setResultCacheLifetime(3600); + +The ID used to store the result set cache is a hash which is automatically generated +for you if you don't set a custom ID yourself with the `setResultCacheId()` method. + + [php] + $query->setResultCacheId('my_custom_id'); + +You can also set the lifetime and cache ID by passing the values as the second +and third argument to `useResultCache()`. + + [php] + $query->useResultCache(true, 3600, 'my_custom_id'); + ++++ Metadata Cache + +Your class metadata can be parsed from a few different sources like YAML, XML, +Annotations, etc. Instead of parsing this information on each request we should +cache it using one of the cache drivers. + +Just like the query and result cache we need to configure it first. + + [php] + $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + +Now the metadata information will only be parsed once and stored in the cache +driver. + +++ Clearing the Cache + +We've already shown you previously how you can use the API of the cache drivers +to manually delete cache entries. For your convenience we offer a command line task +for you to help you with clearing the query, result and metadata cache. + +From the Doctrine command line you can run the following command. + + $ ./doctrine clear-cache + +Running this task with no arguments will clear all the cache for all the configured +drivers. If you want to be more specific about what you clear you can use the +following options. + +To clear the query cache use the `--query` option. + + $ ./doctrine clear-cache --query + +To clear the metadata cache use the `--metadata` option. + + $ ./doctrine clear-cache --metadata + +To clear the result cache use the `--result` option. + + $ ./doctrine clear-cache --result + +When you use the `--result` option you can use some other options to be more +specific about what queries result sets you want to clear. + +Just like the API of the cache drivers you can clear based on an ID, regular +expression, prefix or suffix. + + $ ./doctrine clear-cache --result --id=cache_id + +Or if you want to clear based on a regular expressions. + + $ ./doctrine clear-cache --result --regex=users_.* + +Or with a prefix. + + $ ./doctrine clear-cache --result --prefix=users_ + +And finally with a suffix. + + $ ./doctrine clear-cache --result --suffix=_my_account + +> **NOTE** +> Using the `--id`, `--regex`, etc. options with the `--query` and `--metadata` +> are not allowed as it is not necessary to be specific about what you clear. +> You only ever need to completely clear the cache to remove stale entries. + +++ Cache Slams + +Something to be careful of when utilizing the cache drivers is cache slams. If +you have a heavily trafficked website with some code that checks for the existence +of a cache record and if it does not exist it generates the information and saves +it to the cache. Now if 100 requests were issued all at the same time and each one +sees the cache does not exist and they all try and insert the same cache entry +it could lock up APC, Xcache, etc. and cause problems. Ways exist to work around +this, like pre-populating your cache and not letting your users requests populate +the cache. + +You can read more about cache slams [here](http://t3.dotgnu.info/blog/php/user-cache-timebomb). \ No newline at end of file diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt new file mode 100644 index 000000000..b9a144bb3 --- /dev/null +++ b/manual/en/configuration.txt @@ -0,0 +1,407 @@ +++ Bootstrapping + +Bootstrapping Doctrine is a relatively simple procedure that roughly exists of +just 2 steps: + +* Making sure Doctrine class files can be loaded on demand. +* Obtaining an EntityManager instance. + ++++ Class loading + +Lets start with the class loading setup. We need to set up some class loaders +(often called "autoloader") so that Doctrine class files are loaded on demand. +The Doctrine\Common namespace contains a very fast and minimalistic class loader +that can be used for Doctrine and any other libraries where the coding standards +ensure that a class's location in the directory tree is reflected by its name +and namespace and where there is a common root namespace. + +> **NOTE** +> You are not forced to use the Doctrine class loader to load Doctrine +> classes. Doctrine does not care how the classes are loaded, if you want to use a +> different class loader or your own to load Doctrine classes, just do that. +> Along the same lines, the class loader in the Doctrine\Common namespace is not +> meant to be only used for Doctrine classes, too. It is a generic class loader that can +> be used for any classes that follow some basic naming standards as described above. + +The following example shows the setup of a `ClassLoader` + +> **NOTE** +> This assumes you've created some kind of script to test the following code in. +> Something like a `test.php` file. + + [php] + // test.php + + require '/path/to/lib/Doctrine/Common/ClassLoader.php'; + $classLoader = new \Doctrine\Common\ClassLoader('Doctrine', '/path/to/Doctrine2/lib); + $classLoader->register(); // register on SPL autoload stack + +For best class loading performance it is recommended that you keep your include_path short, ideally it should only contain the path to the PEAR libraries, and any other class libraries should be registered with their full base path. + ++++ Obtaining an EntityManager + +Once you have prepared the class loading, you acquire an EntityManager instance +with the following minimalist configuration: + + [php] + use Doctrine\ORM\EntityManager, + Doctrine\ORM\Configuration; + + // ... + + $config = new Configuration; + $cache = new \Doctrine\Common\Cache\ApcCache; + $config->setMetadataCacheImpl($cache); + $config->setQueryCacheImpl($cache); + $config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); + $config->setProxyNamespace('MyProject\Proxies'); + $connectionOptions = array( + 'driver' => 'pdo_sqlite', + 'path' => 'database.sqlite' + ); + + $em = EntityManager::create($connectionOptions, $config); + +> **CAUTION** +> Do not use Doctrine without a metadata and query cache! Doctrine is highly +> optimized for working with caches. The main parts in Doctrine that are optimized +> for caching are the metadata mapping information with the metadata cache and the +> DQL to SQL conversions with the query cache. These 2 caches require only an absolute +> minimum of memory yet they heavily improve the runtime performance of Doctrine. +> The recommended cache driver to use with Doctrine is [APC](http://www.php.net/apc). +> APC provides you with an opcode-cache (which is highly recommended anyway) and +> a very fast in-memory cache storage that you can use for the metadata and query +> caches as seen in the previous code snippet. + +An EntityManager is your central access point to ORM functionality provided by Doctrine. + +++ Configuration Options + +The following sections describe all the configuration options available on a `Doctrine\ORM\Configuration` instance. + ++++ Proxy Directory (***REQUIRED***) + + [php] + $config->setProxyDir($dir); + $config->getProxyDir(); + +Gets or sets the directory where Doctrine generates any proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. + ++++ Proxy Namespace (***REQUIRED***) + + [php] + $config->setProxyNamespace($namespace); + $config->getProxyNamespace(); + +Gets or sets the namespace to use for generated proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. + ++++ Metadata Driver (***REQUIRED***) + + [php] + $config->setMetadataDriverImpl($driver); + $config->getMetadataDriverImpl(); + +Gets or sets the metadata driver implementation that is used by Doctrine to acquire the object-relational metadata for your classes. + +There are currently 4 available implementations: + + * `Doctrine\ORM\Mapping\Driver\AnnotationDriver` + * `Doctrine\ORM\Mapping\Driver\XmlDriver` + * `Doctrine\ORM\Mapping\Driver\YamlDriver` + * `Doctrine\ORM\Mapping\Driver\DriverChain` + +Throughout the most part of this manual the AnnotationDriver is used in the examples. For information on the usage of the XmlDriver or YamlDriver please refer to the dedicated chapters `XML Mapping` and `YAML Mapping`. + +The annotation driver is configured as the default metadata driver, using Annotations as Metadata source +does not require you to set this option explicitly. + ++++ Metadata Cache (***RECOMMENDED***) + + [php] + $config->setMetadataCacheImpl($cache); + $config->getMetadataCacheImpl(); + +Gets or sets the cache implementation to use for caching metadata information, that is, all the information you supply via annotations, xml or yaml, so that they do not need to be parsed and loaded from scratch on every single request which is a waste of resources. +The cache implementation must implement the `Doctrine\Common\Cache\Cache` interface. + +Usage of a metadata cache is highly recommended. + +The recommended implementations are: + + * `Doctrine\Common\Cache\ApcCache` + * `Doctrine\Common\Cache\MemcacheCache` + * `Doctrine\Common\Cache\XcacheCache` + ++++ Query Cache (***RECOMMENDED***) + + [php] + $config->setQueryCacheImpl($cache); + $config->getQueryCacheImpl(); + +Gets or sets the cache implementation to use for caching DQL queries, that is, the result of a DQL parsing process that includes the final SQL as well as meta information about how to process the SQL result set of a query. Note that the query cache does not affect query results. You do not get stale data. This is a pure optimization cache without any negative side-effects (except some minimal memory usage in your cache). + +Usage of a query cache is highly recommended. + +The recommended implementations are: + + * `Doctrine\Common\Cache\ApcCache` + * `Doctrine\Common\Cache\MemcacheCache` + * `Doctrine\Common\Cache\XcacheCache` + ++++ SQL Logger (***Optional***) + + [php] + $config->setSqlLogger($logger); + $config->getSqlLogger(); + +Gets or sets the logger to use for logging all SQL statements executed by Doctrine. The logger class must implement the `Doctrine\DBAL\Logging\SqlLogger` interface. A simple default implementation that logs to the standard output using `echo` and `var_dump` can be found at `Doctrine\DBAL\Logging\EchoSqlLogger`. + ++++ Auto-generating Proxy Classes (***OPTIONAL***) + + [php] + $config->setAutoGenerateProxyClasses($bool); + $config->getAutoGenerateProxyClasses(); + +Gets or sets whether proxy classes should be generated automatically at runtime by Doctrine. If set to `FALSE`, proxy classes must be generated manually through the doctrine command line task `generate-proxies`. The strongly recommended value for a production environment is `FALSE`. + +++ Connection Options + +The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array +or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the +DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The DBAL configuration is explained +in the [DBAL section](./../dbal). + +++ 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: + + /** + * @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 should 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; + } + + protected function _onPropertyChanged($propName, $oldValue, $newValue) + { + if ($this->_listeners) { + foreach ($this->_listeners as $listener) { + $listener->propertyChanged($this, $propName, $oldValue, $newValue); + } + } + } + } + +Then, in each property setter of this class or derived classes, you need to +invoke `_onPropertyChanged` as follows to notify listeners: + + [php] + data) { + $this->_onPropertyChanged('data', $this->data, $data); + $this->data = $data; + } + } + } + +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. + +++ Partial Objects + +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 `Query#HINT_FORCE_PARTIAL_LOAD` query hint as follows: + + [php] + $q = $em->createQuery("select u.id, u.name from MyApp\Domain\User u"); + $q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); + ++++ When should I force partial objects? + +Mainly for optimization purposes, especially since the stateless nature of PHP +applications means that any fields or objects that are loaded unnecessarily in +a request are useless (though often minimal) overhead. Be careful of premature optimization. Only force partial objects if it proves to provide an improvement to a performance problem. + +++ Proxy Objects + +A proxy object is an object that is put in place or used instead of the "real" object. A proxy object can add behavior to the object being proxied without that object being aware of it. In Doctrine 2, proxy objects are used to realize several features but mainly for transparent lazy-loading. + +Proxy objects with their lazy-loading facilities help to keep the subset of objects that are already in memory connected to the rest of the objects. This is an essential property as without it there would always be fragile partial objects at the outer edges of your object graph. + +Doctrine 2 implements a variant of the proxy pattern where it generates classes that extend your entity classes and adds lazy-loading capabilities to them. Doctrine can then give you an instance of such a proxy class whenever you request an object of the class being proxied. This happens in two situations: + +**Reference Proxies** + +The method `EntityManager#getReference($entityName, $identifier)` lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier. You could simply do this: + + [php] + // $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart + // $itemId comes from somewhere, probably a request parameter + $item = $em->getReference('MyProject\Model\Item', $itemId); + $cart->addItem($item); + +Here, we added an Item to a Cart without loading the Item from the database. If you invoke any method on the Item instance, it would fully initialize its state transparently from the database. Here $item is actually an instance of the proxy class that was generated for the Item class but your code does not need to care. In fact it **should not care**. Proxy objects should be transparent to your code. + +**Association proxies** + +The second most important situation where Doctrine uses proxy objects is when querying for objects. Whenever you query for an object that has a single-valued association to another object that is configured LAZY, without joining that association in the same query, Doctrine puts proxy objects in place where normally the associated object would be. +Just like other proxies it will transparently initialize itself on first access. + +> **NOTE** +> Joining an association in a DQL or native query essentially means eager loading of that +> association in that query. This will override the 'fetch' option specified in +> the mapping for that association, but only for that query. + ++++ Generating Proxy classes + +Proxy classes can either be generated manually through the Doctrine CLI or automatically by Doctrine. The configuration option that controls this behavior is: + + [php] + $config->setAutoGenerateProxyClasses($bool); + $config->getAutoGenerateProxyClasses(); + +The default value is `TRUE` for convenient development. However, this setting is not optimal for performance and therefore not recommended for a production environment. +To eliminate the overhead of proxy class generation during runtime, set this configuration option to `FALSE`. When you do this in a development environment, note that you may get class/file not found errors if certain proxy classes are not available or failing lazy-loads if new methods were added to the entity class that are not yet in the proxy class. In such a case, simply use the Doctrine CLI to (re)generate the proxy classes like so: + + doctrine orm:generate-proxies + +++ Multiple Metadata Sources + +When using different components using Doctrine 2 you may end up with them using two different metadata drivers, +for example XML and YAML. You can use the DriverChain Metadata implementations to aggregate these drivers +based on namespaces: + + [php] + $chain = new DriverChain(); + $chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company'); + $chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping'); + +Based on the namespace of the entity the loading of entities is delegated to the appropriate driver. The chain +semantics come from the fact that the driver loops through all namespaces and matches the entity class name +against the namespace using a `strpos() === 0` call. This means you need to order the drivers correctly if +sub-namespaces use different metadata driver implementations. \ No newline at end of file diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt new file mode 100755 index 000000000..87aa54a93 --- /dev/null +++ b/manual/en/dql-doctrine-query-language.txt @@ -0,0 +1,803 @@ +++ DQL Explained + +DQL stands for **D**octrine **Q**uery **L**anguage and is an Object Query Language derivate that is very similar to the **H**ibernate **Q**uery **L**anguage (HQL) or the **J**ava **P**ersistence **Q**uery **L**anguage (JPQL). + +In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects. + +> **CAUTION** +> A common mistake for beginners is to mistake DQL for being just some form of SQL +> and therefore trying to use table names and column names or join arbitrary tables +> together in a query. You need to think about DQL as a query language for your object +> model, not for your relational schema. + +DQL is case in-sensitive, except for namespace, class and field names, which are case sensitive. + + +++ Types of DQL queries + +DQL as a query language has SELECT, UPDATE and DELETE constructs that map to their corresponding +SQL statement types. INSERT statements are not allowed in DQL, because entities and their relations +have to be introduced into the persistence context through `EntityManager#persist()` to ensure +consistency of your object model. + +DQL SELECT statements are a very powerful way of retrieving parts of your domain model that are +not accessible via assocations. Additionally they allow to retrieve entities and their associations +in one single sql select statement which can make a huge difference in performance in contrast +to using several queries. + +DQL UPDATE and DELETE statements offer a way to execute bulk changes on the entities of your +domain model. This is often necessary when you cannot load all the affected entities of a bulk +update into memory. + +++ SELECT queries + ++++ DQL SELECT clause + +The select clause of a DQL query specifies what appears in the query result. The composition of all the expressions in the select clause also influences the nature of the query result. + +Here is an example that selects all users with an age > 20: + + [sql] + SELECT u FROM MyProject\Model\User u WHERE u.age > 20 + +Lets examine the query: + +* `u` is a so called identification variable or alias that refers to the `MyProject\Model\User` class. By placing this alias in the SELECT clause we specify that we want all instances of the User class that are matched by this query appear in the query result. +* The FROM keyword is always followed by a fully-qualified class name which in turn is followed by an identification variable or alias for that class name. This class designates a root of our query from which we can navigate further via joins (explained later) and path expressions. +* The expression `u.age` in the WHERE clause is a path expression. Path expressions in DQL are easily identified by the use of the '.' operator that is used for constructing paths. The path expression `u.age` refers to the `age` field on the User class. + +The result of this query would be a list of User objects where all users are older than 20. + +The SELECT clause allows to specify both class identification variables that signal the hydration +of a complete entity class or just fields of the entity using the syntax `u.name`. +Combinations of both are also allowed and it is possible to wrap both fields and +identification values into aggregation and DQL functions. Numerical fields can +be part of computations using mathematical operatos. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn) +on more information. + ++++ Joins + +A SELECT query can contain joins. There are 2 types of JOINs: "Regular" Joins and "Fetch" Joins. + +**Regular Joins**: Used to limit the results and/or compute aggregate values. + +**Fetch Joins**: In addition to the uses of regular joins: Used to fetch related entities and include them in the hydrated result of a query. + +There is no special DQL keyword that distinguishes a regular join from a fetch join. A join (be it an inner or outer join) becomes a "fetch join" as soon as fields of the joined entity appear in the SELECT part of the DQL query outside of an aggregate function. Otherwise its a "regular join". + +Example: + +Regular join of the address: + + [sql] + SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin' + +Fetch join of the address: + + [sql] + SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin' + +When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the +root level of the result array. In the previous example an array of User instances is returned +and the address of each user is fetched and hydrated into the `User#address` variable. If you access +the address Doctrine does not need to + +> **NOTE** +> Doctrine allows you to walk all the assocations between all the objects in your domain model. +> Objects that were not already loaded from the database are replaced with lazy load proxy instances. +> Non-loaded Collections are also replaced by lazy-load instances that fetch all the contained objects upon +> first access. However relying on the lazy-load mechanism leads to many small queries executed +> against the database, which can significantly affect the performance of your application. +> **Fetch Joins** are the solution to hydrate most or all of the entities that you +> need in a single SELECT query. + ++++ Named and Positional Parameters + +DQL supports both named and positional parameters, however in contrast to many SQL dialects +positional parameters are specified with numbers, for example "?1", "?2" and so on. +Named parameters are specified with ":name1", ":name2" and so on. + ++++ DQL SELECT Examples + +This section contains a large set of DQL queries and some explainations of what is happening. +The actual result also depends on the hydration mode. + + [sql] + -- Hydrate all Users + SELECT u FROM MyProject\Model\User u + + -- Retrieve the IDs of all CmsUsers + SELECT u.id FROM CmsUser u + + -- Retrieve the IDs of all users that have written an article + SELECT DISTINCT a.user.id FROM CmsArticle a + + -- Retrieve all articles and sort them by the name of the articles users instance + SELECT a FROM CmsArticle a ORDER BY a.user.name ASC + + -- Retrieve the Username and Name of a CmsUser + SELECT u.username, u.name FROM CmsUser u + + -- Retrieve a ForumUser and his single associated entity + SELECT u, a FROM ForumUser u JOIN u.avatar a + + -- Retrieve a CmsUser and fetch join all the phonenumbers he has + SELECT u, p FROM CmsUser u JOIN u.phonenumbers p + + -- Hydrate a result in Ascending or Descending Order + SELECT u FROM ForumUser u ORDER BY u.id ASC + SELECT u FROM ForumUser u ORDER BY u.id DESC + + -- Using Aggregate Functions + SELECT COUNT(u.id) FROM CmsUser u GROUP BY u.id + + -- With WHERE Clause and Positional Parameter + SELECT u FROM ForumUser u WHERE u.id = ?1 + + -- With WHERE Clause and Named Parameter + SELECT u FROM ForumUser u WHERE u.username = :name + + -- With Nested Conditions in WHERE Clause + SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id + + -- With COUNT DISTINCT + SELECT COUNT(DISTINCT u.name) FROM CmsUser + + -- With Arithmetic Expression in WHERE clause + SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000 + + -- Using multiple classes fetched using a FROM clause (all returned on the root level of the result) + SELECT u, a FROM CmsUser u, CmsArticle a WHERE u.id = a.user.id + + -- Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids + SELECT u.id, a.id FROM CmsUser u LEFT JOIN u.articles a + + -- Restricting a JOIN clause by additional conditions + SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%' + + -- Using several Fetch JOINs + SELECT u, a, p, c FROM CmsUser u + JOIN u.articles a + JOIN u.phonenumbers p + JOIN a.comments c + + -- BETWEEN in WHERE clause + SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2 + + -- DQL Functions in WHERE clause + SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone' + + -- IN() Expression + SELECT u.name FROM CmsUser u WHERE u.id IN(46) + SELECT u FROM CmsUser u WHERE u.id IN (1, 2) + SELECT u FROM CmsUser u WHERE u.id NOT IN (1) + + -- CONCAT() DQL Function + SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1 + SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1 + + -- EXISTS in WHERE clause with correlated Subquery + SELECT u.id FROM CmsUser u + WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.phonenumber = u.id) + + -- Get all users who are members of $group. + SELECT u.id FROM CmsUser u WHERE :param MEMBER OF u.groups + + -- Get all users that have more than 1 phonenumber + SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1 + + -- Get all users that have no phonenumber + SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY + ++++ Using INDEX BY + +The INDEX BY construct is nothing that directly translates into SQL but that affects +object and array hydration. After each FROM and JOIN clause you specify by which field +this class should be indexed in the result. By default a result is incremented +by numerical keys starting with 0. However with INDEX BY you can specifiy any +other column to be the key of your result, it really only makes sense with primary +or unique fields though: + + [sql] + SELECT u.id, u.status, upper(u.name) nameUpper FROM User u INDEX BY u.id + JOIN u.phonenumbers p INDEX BY p.phonenumber + +Returns an array of the following kind, indexed by both user-id then phonenumber-id: + + array + 0 => + array + 1 => + object(stdClass)[299] + public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) + public 'id' => int 1 + .. + 'nameUpper' => string 'ROMANB' (length=6) + 1 => + array + 2 => + object(stdClass)[298] + public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) + public 'id' => int 2 + ... + 'nameUpper' => string 'JWAGE' (length=5) + +++ UPDATE queries + +DQL not only allows to select your Entities using field names, you can also execute bulk updates on a set +of entities using an DQL-UPDATE query. The Syntax of an UPDATE query works as expected, as the following +example shows: + + UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) + +References to related entities are only possible in the WHERE clause and using subselects. + +> **CAUTION** +> DQL UPDATE statements are ported directly into a Database UPDATE statement and therefore bypass +> any locking scheme and do not increment the version column. Entities that are already +> loaded into the persistence context will *NOT* be synced with the updated database state. It +> is recommended to call `EntityManager#clear()` and retrieve new instances of any affected entity. + +++ DELETE queries + +DELETE queries can also be specified using DQL and their syntax is as simple as the UPDATE syntax: + + DELETE MyProject\Model\User u WHERE u.id = 4 + +The same restrictions apply for the reference of related entities. + +> **CAUTION** +> DQL DELETE statements are ported directly into a Database DELETE statement and therefore +> bypass any checks for the version column if they are not explicitly added to the WHERE +> clause of the query. Additionally Deletes of specifies entities are *NOT* cascaded +> to related entities even if specified in the metadata. + +++ Functions, Operators, Aggregates + + ++++ DQL Functions + +The following functions are supported in SELECT, WHERE and HAVING clauses: + +* ABS(arithmetic_expression) +* CONCAT(str1, str2) +* CURRENT_DATE() - Return the current date +* CURRENT_TIME() - Returns the current time +* CURRENT_TIMESTAMP() - Returns a timestamp of the current date and time. +* LENGTH(str) - Returns the length of the given string +* LOCATE(needle, haystack [, offset]) - Locate the first occurance of the substring in the string. +* LOWER(str) - returns the string lowercased. +* MOD(a, b) - Return a MOD b. +* SIZE(collection) - Return the number of elements in the specified collection +* SQRT(q) - Return the squareroot of q. +* SUBSTRING(str, start [, length]) - Return substring of given string. +* TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str) - Trim the string by the given trim char, defaults to whitespaces. +* UPPER(str) - Return the upper-case of the given string. + ++++ Arithmetic operators + +You can do math in DQL using numeric values, for example: + + SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 + ++++ Aggregate Functions + +The following aggregate functions are allowed in SELECT and GROUP BY clauses: AVG, COUNT, MIN, MAX, SUM + ++++ Other Expressions + +DQL offers a wide-range of additional expressions that are known from SQL, here is a list of +all the supported constructs: + +* `ALL/ANY/SOME` - Used in a WHERE clause followed by a subselect this works like the equivalent constructs in SQL. +* `BETWEEN a AND b` and `NOT BETWEEN a AND b` can be used to match ranges of arithmetic values. +* `IN (x1, x2, ...)` and `NOT IN (x1, x2, ..)` can be used to match a set of given values. +* `LIKE ..` and `NOT LIKE ..` match parts of a string or text using % as a wildcard. +* `IS NULL` and `IS NOT NULL` to check for null values +* `EXISTS` and `NOT EXISTS` in combination with a subselect + ++++ Adding your own functions to the DQL language + +By default DQL comes with functions that are part of a large basis of underlying databases. However you will most likely +choose a database platform at the beginning of your project and most likely never change it. For this cases you can +easily extend the DQL parser with own specialized platform functions. + +You can register custom DQL functions in your ORM Configuration: + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->addCustomStringFunction($name, $class); + $config->addCustomNumericFunction($name, $class); + $config->addCustomDatetimeFunction($name, $class); + + $em = EntityManager::create($dbParams, $config); + +The functions have to return either a string, numeric or datetime value depending on the registered function type. As an example +we will add a MySQL specific FLOOR() functionality. All the given classes have to implement the base class +\Doctrine\ORM\Query\AST\Functions\FunctionNode: + + [php] + namespace MyProject\Query\AST; + + use \Doctrine\ORM\Query\AST\Functions\FunctionsNode; + + class MysqlFloor extends FunctionNode + { + public $simpleArithmeticExpression; + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'FLOOR(' . $sqlWalker->walkSimpleArithmeticExpression( + $this->simpleArithmeticExpression + ) . ')'; + } + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $lexer = $parser->getLexer(); + + $parser->match(Lexer::T_ABS); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + } + +We will register the function by calling and can then use it: + + [php] + \Doctrine\ORM\Query\Parser::registerNumericFunction('FLOOR', 'MyProject\Query\MysqlFloor'); + $dql = "SELECT FLOOR(person.salary * 1.75) FROM CompanyPerson person"; + +++ The Query class + +An instance of the `Doctrine\ORM\Query` class represents a DQL query. You create a Query instance be calling `EntityManager#createQuery($dql)`, passing the DQL query string. Alternatively you can create an empty `Query` instance and invoke `Query#setDql($dql)` afterwards. Here are some examples: + + [php] + // $em instanceof EntityManager + + // example1: passing a DQL string + $q = $em->createQuery('select u from MyProject\Model\User u'); + + // example2: usin setDql + $q = $em->createQuery(); + $q->setDql('select u from MyProject\Model\User u'); + + ++++ Query Result Formats + +The format in which the result of a DQL SELECT query is returned can be influenced by a so-called `hydration mode`. A hydration mode specifies a particular way in which an SQL result set is transformed. Each hydration mode has its own dedicated method on the Query class. Here they are: + +* `Query#getResult()`: Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array where the objects are nested in the result rows (mixed). +* `Query#getSingleResult()`: Retrieves a single object. If the result contains more than one object, an exception is thrown. The pure/mixed distinction does not apply. +* `Query#getArrayResult()`: Retrieves an array graph (a nested array) that is largely interchangeable with the object graph generated by `Query#getResultList()` for read-only purposes. + +> **NOTE** +> An array graph can differ from the corresponding object graph in +> certain scenarios due to the difference of the identity semantics between arrays and +> objects. + +* `Query#getScalarResult()`: Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The pure/mixed distinction does not apply. +* `Query#getSingleScalarResult()`: Retrieves a single scalar value from the result returned by the dbms. If the result contains more than a single scalar value, an exception is thrown. The pure/mixed distinction does not apply. + +Instead of using these methods, you can alternatively use the general-purpose method `Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)`. Using this method you can directly supply the hydration mode as the second parameter via one of the Query constants. In fact, the methods mentioned earlier are just convenient shortcuts for the execute method. For example, the method `Query#getResultList()` internally invokes execute, passing in `Query::HYDRATE_OBJECT` as the hydration mode. + +The use of the methods mentioned earlier is generally preferred as it leads to more concise code. + ++++ Pure and Mixed Results + +The nature of a result returned by a DQL SELECT query retrieved through `Query#getResult()` or `Query#getArrayResult()` can be of 2 forms: **pure** and **mixed**. In the previous simple examples, you already saw a "pure" query result, with only objects. By default, the result type is **pure** but **as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. A mixed result has a different structure than a pure result in order to accomodate for the scalar values. + +A pure result usually looks like this: + + array + [0] => Object + [1] => Object + [2] => Object + ... + +A mixed result on the other hand has the following general structure: + + array + array + [0] => Object + [1] => "some scalar string" + ['count'] => 42 + // ... more scalar values, either indexed numerically or with a name + array + [0] => Object + [1] => "some scalar string" + ['count'] => 42 + // ... more scalar values, either indexed numerically or with a name + +To better understand mixed results, consider the following DQL query: + + [sql] + SELECT u, UPPER(u.name) nameUpper FROM MyProject\Model\User u + +This query makes use of the `UPPER` DQL function that returns a scalar value and because there is now a scalar value in the SELECT clause, we get a mixed result. + +Here is how the result could look like: + + array + array + [0] => User (Object) + ['nameUpper'] => "Roman" + array + [0] => User (Object) + ['nameUpper'] => "Jonathan" + ... + +And here is how you would access it in PHP code: + + [php] + foreach ($results as $row) { + echo "Name: " . $row[0]->getName(); + echo "Name UPPER: " . $row['nameUpper']; + } + +You may have observed that in a mixed result, the object always ends up on index 0 of a result row. + ++++ Hydration Mode Asumptions + +Each of the Hydration Modes makes assumptions about how the result is returned to userland. You should +know about all the details to make best use of the different result formats: + +++++ Object Hydration + +++++ Array Hydration + +++++ Scalar Hydration Details + +The following assumptions are made about selected fields using Scalar Hydration: + +1. Fields from classes are prefixed by the DQL alias in the result. A query of the kind 'SELECT u.name ..' returns a key 'u_name' in the result rows. + ++++ Iterating Large Resultsets + +There are situations when a query you want to execute returns a very large result-set that needs +to be processed. All the previously described hydration modes completly load a result-set into +memory which might not be feasible with large result sets. See the [Batch Processing](batch-processing) +section on details how to iterate large result sets. + ++++ Functions + +The following methods exist on the `AbstractQuery` which both `Query` and `NativeQuery` extend from. + +++++ Parameters + +Prepared Statements that use numerical or named wildcards require additional parameters to be executable +agains the database. To pass parameters to the query the following methods can be used: + +* `AbstractQuery::setParameter($param, $value)` - Set the numerical or named wildcard to the given value. +* `AbstractQuery::setParameters(array $params)` - Set an array of parameter key-value pairs. +* `AbstractQuery::getParameter($param)` +* `AbstractQuery::getParameters()` + +++++ Cache related API + +You can cache query results based either on all variables that define the result (SQL, Hydration Mode, Parameters and Hints) +or on user-defined cache keys. However by default query results are not cached at all. You have to enable +the result cache on a per query basis. The following example shows a complete workflow using the Result Cache +API: + + [php] + $query = $em->createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); + $query->setParameter(1, 12); + + $query->setResultCacheDriver(new ApcCache()); + + $query->useResultCache(true) + ->setResultCacheLifeTime($seconds = 3600); + + $result = $query->getResult(); // cache miss + + $query->expireResultCache(true); + $result = $query->getResult(); // forced expire, cache miss + + $query->setResultCacheId('my_query_result'); + $result = $query->getResult(); // saved in given result cache id. + + // or call useResultCache() with all parameters: + $query->useResultCache(true, $seconds = 3600, 'my_query_result'); + $result = $query->getResult(); // cache hit! + +> **TIP!** +> You can set the Result Cache Driver globally on the `Doctrine\ORM\Configuration` instance +> so that it is passed to every `Query` and `NativeQuery` instance. + +++++ Query Hints + +You can pass hints to the query parser and hydrators by using the `AbstractQuery::setHint($name, $value)` method. +Currently there exist mostly internal query hints that are not be consumed in userland. However the following few hints +are to be used in userland: + +* Query::HINT_FORCE_PARTIAL_LOAD - Allows to hydrate objects although not all their columns are fetched. This query +hint can be used to handle memory consumption problems with large result-sets that contain char or binary data. +Doctrine has no way of implicitly reloaded this data. Partially loaded objects have to be passed to +`EntityManager::refresh()` if they are to be reloaded fully from the database. +* Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland aswell. +If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the +fields of the existing entity will be refreshed. In normal operation a result-set that loads data of an already existing +entity is discarded in favour of the already existing entity. +* Query::HINT_CUSTOM_TREE_WALKERS - An array of additional `Doctrine\ORM\Query\TreeWalker` instances that are attached +to the DQL query parsing process. + +++++ Query Cache (DQL Query Only) + +Parsing a DQL query and converting it into an SQL query against the underlying database platform obviously has some overhead +in contrast to directly executing Native SQL queries. That is why there is a dedicated Query Cache for caching the +DQL parser results. In combination with the use of wildcards you can reduce the number of parsed queries in production +to zero. + +The Query Cache Driver is passed from the `Doctrine\ORM\Configuration` instance to each `Doctrine\ORM\Query` instance +by default and is also enabled by default. This also means you don't regularly need to fiddle with the parameters +of the Query Cache, however if you do there are several methods to interact with it: + +* `Query::setQueryCacheDriver($driver)` - Allows to set a Cache instance +* `Query::setQueryCacheLifeTime($seconds = 3600)` - Set lifetime of the query caching. +* `Query::expireQueryCache($bool)` - Enforce the expiring of the query cache if set to true. +* `Query::getExpireQueryCache()` +* `Query::getQueryCacheDriver()` +* `Query::getQueryCacheLifeTime()` + +++++ First and Max Result Items (DQL Query Only) + +You can limit the number of results returned from a DQL query aswell as specify the starting offset, Doctrine +then uses a strategy of manipulating the select query to return only the requested number of results: + +* `Query::setMaxResults($maxResults)` +* `Query::setFirstResult($offset)` + +> **NOTE** +> If your query contains a fetch-joined collection specifying the result limit methods are not working +> as you would expect. Set Max Results restricts the number of database result rows, however in the +> case of fetch-joined collections one root entity might appear in many rows, effectivly hydrating +> less than the specified number of results. + +++ EBNF + +The following context-free grammar, written in an EBNF variant, describes the Doctrine Query Language. You can consult this grammar whenever you are unsure about what is possible with DQL or what the correct syntax for a particular query should be. + ++++ Document syntax: + +* non-terminals begin with an upper case character +* terminals begin with a lower case character +* parentheses (...) are used for grouping +* square brackets [...] are used for defining an optional part, eg. zero or one time +* curly brackets {...} are used for repetion, eg. zero or more times +* double quotation marks "..." define a terminal string a vertical bar | represents an alternative + ++++ Terminals + +* identifier (name, email, ...) +* string ('foo', 'bar''s house', '%ninja%', ...) +* char ('/', '\\', ' ', ...) +* integer (-1, 0, 1, 34, ...) +* float (-0.23, 0.007, 1.245342E+8, ...) +* boolean (false, true) + ++++ Query Language + + QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement + ++++ Statements + + SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + UpdateStatement ::= UpdateClause [WhereClause] + DeleteStatement ::= DeleteClause [WhereClause] + ++++ Identifiers + + /* Alias Identification usage (the "u" of "u.name") */ + IdentificationVariable ::= identifier + + /* Alias Identification declaration (the "u" of "FROM User u") */ + AliasIdentificationVariable :: = identifier + + /* identifier that must be a class name (the "User" of "FROM User u") */ + AbstractSchemaName ::= identifier + + /* identifier that must be a field (the "name" of "u.name") */ + /* This is responsable to know if the field exists in Object, no matter if it's a relation or a simple field */ + FieldIdentificationVariable ::= identifier + + /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ + CollectionValuedAssociationField ::= FieldIdentificationVariable + + /* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */ + SingleValuedAssociationField ::= FieldIdentificationVariable + + /* identifier that must be an embedded class state field (for the future) */ + EmbeddedClassStateField ::= FieldIdentificationVariable + + /* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ + /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ + SimpleStateField ::= FieldIdentificationVariable + + /* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */ + AliasResultVariable = identifier + + /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ + ResultVariable = identifier + ++++ Path Expressions + + /* "u.Group" or "u.Phonenumbers" declarations */ + JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) + + /* "u.Group" or "u.Phonenumbers" usages */ + AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression + + /* "u.name" or "u.Group" */ + SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression + + /* "u.name" or "u.Group.name" */ + StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField + + /* "u.Group" */ + SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField + + /* "u.Group.Permissions" */ + CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField + + /* "name" */ + StateField ::= {EmbeddedClassStateField "."}* SimpleStateField + + /* "u.name" or "u.address.zip" (address = EmbeddedClassStateField) */ + SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField + + ++++ Clauses + + SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* + SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression + UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* + DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable + FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* + SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* + WhereClause ::= "WHERE" ConditionalExpression + HavingClause ::= "HAVING" ConditionalExpression + GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* + OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* + Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + + ++++ Items + + UpdateItem ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue + OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] + GroupByItem ::= IdentificationVariable | SingleValuedPathExpression + NewValue ::= ScalarExpression | SimpleEntityExpression | "NULL" + + ++++ From, Join and Index by + + IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* + SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) + JoinVariableDeclaration ::= Join [IndexBy] + RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable + Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression + ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] + IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression + + ++++ Select Expressions + + SelectExpression ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")" | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable] + SimpleSelectExpression ::= ScalarExpression | IdentificationVariable | + (AggregateExpression [["AS"] AliasResultVariable]) + PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet + PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" + + ++++ Conditional Expressions + + ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* + ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* + ConditionalFactor ::= ["NOT"] ConditionalPrimary + ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" + SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression | + InExpression | NullComparisonExpression | ExistsExpression | + EmptyCollectionComparisonExpression | CollectionMemberExpression + + ++++ Collection Expressions + + EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" + CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression + ++++ Literal Values + + Literal ::= string | char | integer | float | boolean + InParameter ::= Literal | InputParameter + ++++ Input Parameter + + InputParameter ::= PositionalParameter | NamedParameter + PositionalParameter ::= "?" integer + NamedParameter ::= ":" string + + ++++ Arithmetic Expressions + + ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" + SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* + ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* + ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary + ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" + | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings + | FunctionsReturningDatetime | IdentificationVariable | InputParameter + + ++++ Scalar and Type Expressions + + ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression + BooleanPrimary | CaseExpression | EntityTypeExpression + CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | + CoalesceExpression | NullifExpression + GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression + "END" + WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* + "ELSE" ScalarExpression "END" + CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" + NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" + StringExpression ::= StringPrimary | "(" Subselect ")" + StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression + BooleanExpression ::= BooleanPrimary | "(" Subselect ")" + BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter + EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression + SimpleEntityExpression ::= IdentificationVariable | InputParameter + DatetimeExpression ::= DatetimePrimary | "(" Subselect ")" + DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression + + ++++ Aggregate Expressions + + AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | + "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" + ++++ Other Expressions + +QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS + + QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" + BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression + ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) + InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" + LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] + NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" + ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" + ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" + ++++ Functions + + FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime + + FunctionsReturningNumerics ::= + "LENGTH" "(" StringPrimary ")" | + "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | + "ABS" "(" SimpleArithmeticExpression ")" | "SQRT" "(" SimpleArithmeticExpression ")" | + "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + "SIZE" "(" CollectionValuedPathExpression ")" + + FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" + + FunctionsReturningStrings ::= + "CONCAT" "(" StringPrimary "," StringPrimary ")" | + "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | + "LOWER" "(" StringPrimary ")" | + "UPPER" "(" StringPrimary ")" + + + + + + + diff --git a/manual/en/events.txt b/manual/en/events.txt new file mode 100644 index 000000000..f8f848563 --- /dev/null +++ b/manual/en/events.txt @@ -0,0 +1,304 @@ +Doctrine 2 features a lightweight event system that is part of the Common package. + +++ The Event System + +The event system is controlled by the `EventManager`. It is the central point +of Doctrine's event listener system. Listeners are registered on the manager +and events are dispatched through the manager. + + [php] + $evm = new EventManager(); + +Now we can add some event listeners to the `$evm`. Let's create a `EventTest` class +to play around with. + + [php] + class EventTest + { + const preFoo = 'preFoo'; + const postFoo = 'postFoo'; + + private $_evm; + + public $preFooInvoked = false; + public $postFooInvoked = false; + + public function __construct($evm) + { + $evm->addEventListener(array(self::preFoo, self::postFoo), $this); + } + + public function preFoo(EventArgs $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(EventArgs $e) + { + $this->postFooInvoked = true; + } + } + + // Create a new instance + $test = new EventTest($evm); + +Events can be dispatched by using the `dispatchEvent()` method. + + [php] + $evm->dispatchEvent(EventTest::preFoo); + $evm->dispatchEvent(EventTest::postFoo); + +You can easily remove a listener with the `removeEventListener()` method. + + [php] + $evm->removeEventListener(array(self::preFoo, self::postFoo), $this); + +The Doctrine 2 event system also has a simple concept of event subscribers. We +can define a simple `TestEventSubscriber` class which implements the +`\Doctrine\Common\EventSubscriber` interface and implements a `getSubscribedEvents()` +method which returns an array of events it should be subscribed to. + + [php] + class TestEventSubscriber implements \Doctrine\Common\EventSubscriber + { + const preFoo = 'preFoo'; + + public $preFooInvoked = false; + + public function preFoo() + { + $this->preFooInvoked = true; + } + + public function getSubscribedEvents() + { + return array(self::preFoo); + } + } + + $eventSubscriber = new TestEventSubscriber(); + $evm->addEventSubscriber($eventSubscriber); + +Now when you dispatch an event any event subscribers will be notified for that event. + + [php] + $evm->dispatchEvent(TestEventSubscriber::preFoo); + +Now the test the `$eventSubscriber` instance to see if the `preFoo()` method was invoked. + + [php] + if ($eventSubscriber->preFooInvoked) { + echo 'pre foo invoked!'; + } + +++ Lifecycle Events + +The EntityManager and UnitOfWork trigger a bunch of events during the life-time of their registered entities. + +* preRemove - The preRemove event occurs for a given entity before the respective EntityManager remove operation for that entity is executed. +* postRemove - The postRemove event occurs for an entity after the entity has been deleted. It will be invoked after the database delete operations. +* prePersist - The prePersist event occurs for a given entity before the respective EntityManager persist operation for that entity is executed. +* postPersist - The postPersist event occurs for an entity after the entity has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event. +* preUpdate - The preUpdate event occurs before the database update operations to entity data. +* postUpdate - The postUpdate event occurs after the database update operations to entity data. +* postLoad - The postLoad event occurs for an entity after the entity has been loaded into the current EntityManager from the database or after the refresh operation has been applied to it. +* loadClassMetadata - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (annotations/xml/yaml). +* onFlush - The onFlush event occours after the change-sets of all managed entities are computed. This event is not a lifecycle callback. + +> **NOTE** +> Note that the postLoad event occurs for an entity before any associations have been +> initialized. Therefore it is not safe to access associations in a postLoad callback +> or event handler. + +You can access the Event constants from the `Events` class in the ORM package. + + [php] + use Doctrine\ORM\Events; + echo Events::preUpdate; + +These can be hooked into by two different types of event listeners: + +* Lifecycle Callbacks are methods on the entity classes that are called when the event is triggered. They recieve absolutely no arguments and are specifically designed to allow changes inside the entity classes state. +* Lifecycle Event Listeners are classes with specific callback methods that recieves some kind of `EventArgs` instance which give access to the entity, EntityManager or other relevant data. + +> **NOTE** +> All Lifecycle events that happen during the `flush()` of an EntityManager have very specific constraints on the allowed +> operations that can be executed. Please read the *Implementing Event Listeners* section very carefully to understand +> which operations are allowed in which lifecycle event. + +++ Lifecycle Callbacks + +A lifecycle event is a regular event with the additional feature of providing +a mechanism to register direct callbacks inside the corresponding entity classes +that are executed when the lifecycle event occurs. + + [php] + + /** @Entity @HasLifecycleCallbacks */ + class User + { + // ... + + /** + * @Column(type="string", length=255) + */ + public $value; + + /** @Column(name="created_at", type="string", length=255) */ + private $createdAt; + + /** @PrePersist */ + public function doStuffOnPrePersist() + { + $this->createdAt = date('Y-m-d H:m:s'); + } + + /** @PrePersist */ + public function doOtherStuffOnPrePersist() + { + $this->value = 'changed from prePersist callback!'; + } + + /** @PostPersist */ + public function doStuffOnPostPersist() + { + $this->value = 'changed from postPersist callback!'; + } + + /** @PostLoad */ + public function doStuffOnPostLoad() + { + $this->value = 'changed from postLoad callback!'; + } + + /** @PreUpdate */ + public function doStuffOnPreUpdate() + { + $this->value = 'changed from preUpdate callback!'; + } + } + +Note that when using annotations you have to apply the @HasLifecycleCallbacks marker annotation on the entity class. + +If you want to register lifecycle callbacks from YAML or XML you can do it with +the following. + + [yml] + User: + type: entity + fields: + # ... + name: + type: string(50) + lifecycleCallbacks: + doStuffOnPrePersist: prePersist + doStuffOnPostPersist: postPersist + +XML would look something like this: + + [xml] + + + + + + + + + + + + + + + +You just need to make sure a public `doStuffOnPrePersist()` and `doStuffOnPostPersist()` method is defined on your `User` model. + + [php] + // ... + + class User + { + // ... + + public function doStuffOnPrePersist() + { + // ... + } + + public function doStuffOnPostPersist() + { + // ... + } + } + +The `key` of the lifecycleCallbacks is the name of the method and the value is +the event type. The allowed event types are the ones listed in the previous Lifecycle Events section. + +++ Listening to Lifecycle Events + +Lifecycle event listeners are much more powerful than the simple lifecycle callbacks that are defined on the entity +classes. They allow to implement re-usable behaviours between different entity classes, yet require much more detailed +knowledge about the inner workings of the EntityManager and UnitOfWork. Please read the *Implementing Event Listeners* +section carefully if you are trying to write your own listener. + +To register an event listener you have to hook it into the EventManager that is passed to the EntityManager factory: + + [php] + $eventManager = new EventManager(); + $eventManager->addEventListener(array(Events::preUpdate), MyEventListener()); + $eventManager->addEventSubscriber(new MyEventSubscriber()); + + $entityManager = EntityManager::create($dbOpts, $config, $eventManager); + +You can also retrieve the event manager instance after the EntityManager was created: + + [php] + $entityManager->getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener()); + $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); + +++ Implementing Event Listeners + +This section explains what is and what is not allowed during specific lifecycle events of the UnitOfWork. +Although you get passed the EntityManager in all of these events, you have to follow this restrictions very +carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and +lost updates/persists/removes. + ++++ prePersist + ++++ preRemove + ++++ onFlush + ++++ preUpdate + ++++ postUpdate, postRemove, postPersist + +++ Load ClassMetadata Event + +When the mapping information for an entity is read, it is populated in to a +`ClassMetadataInfo` instance. You can hook in to this process and manipulate +the instance. + + [php] + $test = new EventTest(); + $metadataFactory = $em->getMetadataFactory(); + $evm = $em->getEventManager(); + $evm->addEventListener(Events::loadClassMetadata, $test); + + class EventTest + { + public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) + { + $classMetadata = $eventArgs->getClassMetadata(); + $fieldMapping = array( + 'fieldName' => 'about', + 'type' => 'string', + 'length' => 255 + ); + $classMetadata->mapField($fieldMapping); + } + } diff --git a/manual/en/improving-performance.txt b/manual/en/improving-performance.txt new file mode 100644 index 000000000..644636fc9 --- /dev/null +++ b/manual/en/improving-performance.txt @@ -0,0 +1,25 @@ + +++ Bytecode Cache + +It is highly recommended to make use of a bytecode cache like APC. A bytecode cache removes the need for parsing PHP code on every request and can greatly improve performance. + +> **NOTE** +> "If you care about performance and don’t use a bytecode cache then you don’t really care +> about performance. Please get one and start using it." (Stas Malyshev, Core Contributor +> to PHP and Zend Employee). + + +++ Metadata and Query caches + +As already mentioned earlier in the chapter about configuring Doctrine, it is strongly discouraged to use Doctrine without a Metadata and Query cache (preferrably with APC or Memcache as the cache driver). Operating Doctrine without these caches means Doctrine will need to load your mapping information on every single request and has to parse each DQL query on every single request. This is a waste of resources. + +++ Alternative Query Result Formats + +Make effective use of the available alternative query result formats like nested array graphs or pure scalar results, especially in scenarios where data is loaded for read-only purposes. + +++ Apply Best Practices + +A lot of the points mentioned in the Best Practices chapter will also positively affect the performance of Doctrine. + + + diff --git a/manual/en/inheritance-mapping.txt b/manual/en/inheritance-mapping.txt new file mode 100644 index 000000000..1b82b1d2a --- /dev/null +++ b/manual/en/inheritance-mapping.txt @@ -0,0 +1,141 @@ +++ Mapped Superclasses + +An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information +for its subclasses, but which is not itself an entity. Typically, the purpose of such a mapped superclass is to define +state and mapping information that is common to multiple entity classes. + +Mapped superclasses, just as regular, non-mapped classes, can appear in the middle of an otherwise +mapped inheritance hierarchy (through Single Table Inheritance or Class Table Inheritance). + +> *NOTICE* +> +> A mapped superclass cannot be an entity, it is not queryable and persistent relationships defined by a mapped +> superclass must be unidirectional. For further support of inheritance, the single or joined table inheritance +> features have to be used. + +Example: + + [php] + /** @MappedSuperclass */ + class MappedSuperclassBase + { + /** @Column(type="integer") */ + private $mapped1; + /** @Column(type="string") */ + private $mapped2; + /** + * @OneToOne(targetEntity="MappedSuperclassRelated1") + * @JoinColumn(name="related1_id", referencedColumnName="id") + */ + private $mappedRelated1; + + // ... more fields and methods + } + + /** @Entity */ + class EntitySubClass extends MappedSuperclassBase + { + /** @Id @Column(type="integer") */ + private $id; + /** @Column(type="string") */ + private $name; + + // ... more fields and methods + } + +The DDL for the corresponding database schema would look something like this (this is for SQLite): + + [sql] + CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id)) + +As you can see from this DDL snippet, there is only a single table for the entity subclass. All the mappings from the mapped superclass were inherited to the subclass as if they had been defined on that class directly. + + +++ Single Table Inheritance + +[Single Table Inheritance](http://martinfowler.com/eaaCatalog/singleTableInheritance.html) is an inheritance mapping strategy where all classes of a hierarchy are mapped to a single database table. In order to distinguish which row represents which type in the hierarchy a so-called discriminator column is used. + +Example: + + [php] + namespace MyProject\Model; + + /** + * @Entity + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + + /** + * @Entity + */ + class Employee extends Person + { + // ... + } + +Things to note: + +* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified on the topmost class that is part of the mapped entity hierarchy. +* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of a certain type. In the case above a value of "person" identifies a row as being of type `Person` and "employee" identifies a row as being of type `Employee`. +* The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied. + ++++ Design-time considerations + +This mapping approach works well when the type hierarchy is fairly simple and stable. Adding a new type to the hierarchy and adding fields to existing supertypes simply involves adding new columns to the table, though in large deployments this may have an adverse impact on the index and column layout inside the database. + ++++ Performance impact + +This strategy is very efficient for querying across all types in the hierarchy or for specific types. No table joins are required, only a WHERE clause listing the type identifiers. In particular, relationships involving types that employ this mapping strategy are very performant. + + +++ Class Table Inheritance + +[Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html) is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. +Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hieararchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance. + +Example: + + [php] + namespace MyProject\Model; + + /** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + + /** @Entity */ + class Employee extends Person + { + // ... + } + +Things to note: + +* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified on the topmost class that is part of the mapped entity hierarchy. +* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of which type. In the case above a value of "person" identifies a row as being of type `Person` and "employee" identifies a row as being of type `Employee`. +* The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied. + +> **NOTE** +> When you do not use the SchemaTool to generate the required SQL you should know that deleting a class table inheritance +> makes use of the foreign key property `ON DELETE CASCADE` in all database implementations. A failure to implement this +> yourself will lead to dead rows in the database. + ++++ Design-time considerations + +Introducing a new type to the hierarchy, at any level, simply involves interjecting a new table into the schema. Subtypes of that type will automatically join with that new type at runtime. Similarly, modifying any entity type in the hierarchy by adding, modifying or removing fields affects only the immediate table mapped to that type. This mapping strategy provides the greatest flexibility at design time, since changes to any type are always limited to that type's dedicated table. + ++++ Performance impact + +This strategy inherently requires multiple JOIN operations to perform just about any query which can have a negative impact on performance, especially with large tables and/or large hierarchies. When partial objects are allowed, either globally or on the specific query, then querying for any type will not cause the tables of subtypes to be OUTER JOINed which can increase performance but the resulting partial objects will not fully load themselves on access of any subtype fields, so accessing fields of subtypes after such a query is not safe. \ No newline at end of file diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt new file mode 100644 index 000000000..96f627d6a --- /dev/null +++ b/manual/en/introduction.txt @@ -0,0 +1,222 @@ +++ Welcome + +Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides +transparent persistence for PHP objects. It sits on top of a powerful database +abstraction layer (DBAL). One of its key features is the option to write +database queries in a proprietary object oriented SQL dialect called Doctrine +Query Language (DQL), inspired by Hibernates HQL. This provides developers with +a powerful alternative to SQL that maintains flexibility without requiring +unnecessary code duplication. + +++ Disclaimer + +This is the Doctrine 2 reference documentation. Introductory guides and tutorials that you can follow along from start to finish, like the "Guide to Doctrine" book known from the Doctrine 1.x series, will be available at a later date. + +++ Requirements + +Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved performance it +is also recommended that you use APC with PHP. + +++ Doctrine 2 Packages + +Doctrine 2 is divided into three main packages. + +* Common +* DBAL (includes Common) +* ORM (includes DBAL+Common) + +This manual mainly covers the ORM package, sometimes touching parts of the +underlying DBAL and Common packages. The Doctrine code base is split in to these +packages for a few reasons and they are to... + +* ...make things more maintainable and decoupled +* ...allow you to use the code in Doctrine Common without the ORM or DBAL +* ...allow you to use the DBAL without the ORM + ++++ The Common Package + +The Common package contains highly reusable components that have no dependencies +beyond the package itself (and PHP, of course). The root namespace of the +Common package is `Doctrine\Common`. + ++++ The DBAL Package + +The DBAL package contains an enhanced database abstraction layer on top of +PDO but is not strongly bound to PDO. The purpose of this layer is to provide a +single API that bridges most of the differences between the different RDBMS vendors. +The root namespace of the DBAL package is `Doctrine\DBAL`. + ++++ The ORM Package + +The ORM package contains the object-relational mapping toolkit that provides +transparent relational persistence for plain PHP objects. The root namespace of +the ORM package is `Doctrine\ORM`. + +++ Installing + +Doctrine can be installed many different ways. We will describe all the different +ways and you can choose which one suits you best. + ++++ PEAR + +You can easily install any of the three Doctrine packages from the PEAR command +line installation utility. + +To install just the `Common` package you can run the following command: + + $ sudo pear install pear.phpdoctrine.org/DoctrineCommon-2.0.0 + +If you want to use the Doctrine Database Abstraction Layer you can install it +with the following command. + + $ sudo pear install pear.phpdoctrine.org/DoctrineDBAL-2.0.0 + +Or, if you want to get the works and go for the ORM you can install it with the +following command. + + $ sudo pear install pear.phpdoctrine.org/DoctrineORM-2.0.0 + +When you have a package installed via PEAR you can required and load the +`ClassLoader` with the following code. + + [php] + require 'Doctrine/Common/ClassLoader.php'; + $classLoader = new \Doctrine\Common\ClassLoader(); + +The packages are installed in to your shared PEAR PHP code folder in a folder +named `Doctrine`. You also get a nice command line utility installed and made +available on your system. Now when you run the `doctrine` command you will +see what you can do with it. + + $ doctrine + Doctrine Command Line Interface + Available Tasks: + core:help + dbal:run-sql (--file= | --sql=) --depth= + orm:clear-cache (--query | --metadata | --result [--id=] [--regex=] [--prefix=] [--suffix=]) + orm:convert-mapping (--from= | --from-database) --to= --dest= + orm:ensure-production-settings + orm:generate-proxies --class-dir= [--to-dir=] + orm:run-dql --dql= --depth= + orm:schema-tool (--create | --drop | --update | --complete-update | --re-create) [--dump-sql] [--class-dir=] + orm:version + ++++ Package Download + +You can also use Doctrine 2 by downloading the latest release package +from [the download page](http://www.doctrine-project.org/download). + ++++ Subversion + +Alternatively you can check out the latest version of Doctrine 2 via SVN. + + $ svn co http://svn.doctrine-project.org/trunk doctrine + +++ Sandbox Quickstart + +> **NOTE** +> The sandbox is only available via SVN or soon as a separate download on the downloads +> page. + +The sandbox is a pre-configured environment for evaluating and playing +with Doctrine 2. + ++++ Overview + +After navigating to the sandbox directory, you should see the following structure: + + sandbox/ + Entities/ + Address.php + User.php + xml/ + Entities.Address.dcm.xml + Entities.User.dcm.xml + yaml/ + Entities.Address.dcm.yml + Entities.User.dcm.yml + cli-config.php + doctrine + doctrine.php + index.php + +Here is a short overview of the purpose of these folders and files: + + * The `Entities` folder is where any model classes are created. Two example entities are already there. + * The `xml` folder is where any XML mapping files are created (if you want to use XML mapping). Two example mapping documents for the 2 example entities are already there. + * The `yaml` folder is where any YAML mapping files are created (if you want to use YAML mapping). Two example mapping documents for the 2 example entities are already there. + * The `cli-config.php` contains bootstrap code for a configuration that is used by the CLI tool `doctrine` whenever you execute a task. + * `doctrine`/`doctrine.php` is a command-line tool. + * `index.php` is a basic classical bootstrap file of a php application that uses Doctrine 2. + ++++ Mini-tutorial + +1) From within the tools/sandbox folder, run the following command and you should +see the same output. + + $ php doctrine orm:schema-tool --create + Creating database schema... + Database schema created successfully. + +2) Take another look into the tools/sandbox folder. A SQLite database should +have been created with the name `database.sqlite`. + +3) Open `index.php` and edit it so that it looks as follows: + + [php] + //... bootstrap stuff + + ## PUT YOUR TEST CODE BELOW + + $user = new \Entities\User; + $user->setName('Garfield'); + $em->persist($user); + $em->flush(); + + echo "User saved!"; + +Open index.php in your browser or execute it on the command line. You should see +the output "User saved!". + +5) Inspect the SQLite database. Again from within the tools/sandbox folder, +execute the following command: + + $ php doctrine dbal:run-sql --sql="select * from users" + +You should get the following output: + + array(1) { + [0]=> + array(2) { + ["id"]=> + string(1) "1" + ["name"]=> + string(8) "Garfield" + } + } + +You just saved your first entity with a generated ID in an SQLite database. + +6) Replace the contents of index.php with the following: + + [php] + //... bootstrap stuff + + ## PUT YOUR TEST CODE BELOW + + $q = $em->createQuery('select u from Entities\User u where u.name = ?1'); + $q->setParameter(1, 'Garfield'); + $garfield = $q->getSingleResult(); + + echo "Hello " . $garfield->getName() . "!"; + +You just created your first DQL query to retrieve the user with the name +'Garfield' from an SQLite database (Yes, there is an easier way to do it, +but we wanted to introduce you to DQL at this point. Can you **find** the easier way?). + +> **TIP** +> When you create new model classes or alter existing ones you can recreate the database +> schema with the command `doctrine orm:schema-tool --drop` followed by +> `doctrine orm:schema-tool --create`. + +7) Explore Doctrine 2! diff --git a/manual/en/native-sql.txt b/manual/en/native-sql.txt new file mode 100644 index 000000000..fb6359737 --- /dev/null +++ b/manual/en/native-sql.txt @@ -0,0 +1,196 @@ +A `NativeQuery` lets you execute native SQL, mapping the results according to your specifications. Such a specification that describes how an SQL result set is mapped to a Doctrine result is represented by a `ResultSetMapping`. + +++ The NativeQuery class + +To create a `NativeQuery` you use the method `EntityManager#createNativeQuery($sql, $resultSetMapping)`. As you can see in the signature of this method, it expects 2 ingredients: The SQL you want to execute and the `ResultSetMapping` that describes how the results will be mapped. + +Once you obtained an instance of a `NativeQuery`, you can bind parameters to it and finally execute it. + +++ The ResultSetMapping + +Understanding the `ResultSetMapping` is the key to using a `NativeQuery`. +A Doctrine result can contain the following components: + + * Entity results. These represent root result elements. + * Joined entity results. These represent joined entities in associations of root entity results. + * Field results. These represent a column in the result set that maps to a field of an entity. A field result always belongs to an entity result or joined entity result. + * Scalar results. These represent scalar values in the result set that will appear in each result row. Adding scalar results to a ResultSetMapping can also cause the overall result to becomed **mixed** (see DQL - Doctrine Query Language) if the same ResultSetMapping also contains entity results. + * Meta results. These represent columns that contain meta-information, such as foreign keys and discriminator columns. + When querying for objects (`getResult()`), all meta columns of root entities or joined entities must be present in the SQL query + and mapped accordingly using `ResultSetMapping#addMetaResult`. + +> **TIP** +> It might not surprise you that Doctrine uses `ResultSetMapping`s internally when you +> create DQL queries. As the query gets parsed and transformed to SQL, Doctrine fills +> a `ResultSetMapping` that describes how the results should be processed by the hydration +> routines. + +We will now look at each of the result types that can appear in a ResultSetMapping in detail. + ++++ Entity results + +An entity result describes an entity type that appears as a root element in the transformed result. You add an entity result through `ResultSetMapping#addEntityResult()`. +Let's take a look at the method signature in detail: + + [php] + /** + * Adds an entity result to this ResultSetMapping. + * + * @param string $class The class name of the entity. + * @param string $alias The alias for the class. The alias must be unique among all entity + * results or joined entity results within this ResultSetMapping. + */ + public function addEntityResult($class, $alias) + +The first parameter is the fully qualified name of the entity class. The second parameter is some arbitrary alias for this entity result that must be unique within a `ResultSetMapping`. You use this alias to attach field results to the entity result. It is very similar to an identification variable that you use in DQL to alias classes or relationships. + +An entity result alone is not enough to form a valid `ResultSetMapping`. An entity result or joined entity result always needs a set of field results, which we will look at soon. + ++++ Joined entity results + +A joined entity result describes an entity type that appears as a joined relationship element in the transformed result, attached to a (root) entity result. You add a joined entity result through `ResultSetMapping#addJoinedEntityResult()`. Let's take a look at the method signature in detail: + + [php] + /** + * Adds a joined entity result. + * + * @param string $class The class name of the joined entity. + * @param string $alias The unique alias to use for the joined entity. + * @param string $parentAlias The alias of the entity result that is the parent of this joined result. + * @param object $relation The association field that connects the parent entity result with the joined entity result. + */ + public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) + +The first parameter is the class name of the joined entity. The second parameter is an arbitrary alias for the joined entity that must be unique within the `ResultSetMapping`. +You use this alias to attach field results to the entity result. The third parameter is the alias of the entity result that is the parent type of the joined relationship. The fourth and last parameter is the name of the field on the parent entity result that should contain the joined entity result. + + ++++ Field results + +A field result describes the mapping of a single column in an SQL result set to a field in an entity. As such, field results are inherently bound to entity results. You add a field result through `ResultSetMapping#addFieldResult()`. Again, let's examine the method signature in detail: + + [php] + /** + * Adds a field result that is part of an entity result or joined entity result. + * + * @param string $alias The alias of the entity result or joined entity result. + * @param string $columnName The name of the column in the SQL result set. + * @param string $fieldName The name of the field on the (joined) entity. + */ + public function addFieldResult($alias, $columnName, $fieldName) + +The first parameter is the alias of the entity result to which the field result will belong. The second parameter is the name of the column in the SQL result set. Note that this name is case sensitive, i.e. if you use a native query against Oracle it must be all uppercase. The third parameter is the name of the field on the entity result identified by `$alias` into which the value of the column should be set. + ++++ Scalar results + +A scalar result describes the mapping of a single column in an SQL result set to a scalar value in the Doctrine result. Scalar results are typically used for aggregate values but any column in the SQL result set can be mapped as a scalar value. To add a scalar result use `ResultSetMapping#addScalarResult()`. The method signature in detail: + + [php] + /** + * Adds a scalar result mapping. + * + * @param string $columnName The name of the column in the SQL result set. + * @param string $alias The result alias with which the scalar result should be placed in the result structure. + */ + public function addScalarResult($columnName, $alias) + +The first parameter is the name of the column in the SQL result set and the second parameter is the result alias under which the value of the column will be placed in the transformed Doctrine result. + ++++ Meta results + +A meta result describes a single column in an SQL result set that is either a foreign key or a discriminator column. +These columns are essential for Doctrine to properly construct objects out of SQL result sets. +To add a column as a meta result use `ResultSetMapping#addMetaResult()`. The method signature in detail: + + [php] + /** + * Adds a meta column (foreign key or discriminator column) to the result set. + * + * @param string $alias + * @param string $columnAlias + * @param string $columnName + */ + public function addMetaResult($alias, $columnAlias, $columnName) + +The first parameter is the alias of the entity result to which the meta column belongs. +A meta result column (foreign key or discriminator column) always belongs to to an entity result. +The second parameter is the column alias/name of the column in the SQL result set and the third parameter is the +column name used in the mapping. + + ++++ Examples + +Understanding a ResultSetMapping is probably easiest through looking at some examples. + +First a basic example that describes the mapping of a single entity. + + [php] + // Equivalent DQL query: "select u from User u where u.name=?1" + // User owns no associations. + $rsm = new ResultSetMapping; + $rsm->addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + + $query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +The result would look like this: + + array( + [0] => User (Object) + ) + +Note that this would be a partial object if the entity has more fields than just id and name. In the example above the column and field names are identical but that is not necessary, of course. Also note that the query string passed to createNativeQuery is **real native SQL**. Doctrine does not touch this SQL in any way. + +In the previous basic example, a User had no relations and the table the class is mapped to owns no foreign keys. +The next an example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress, +where the User is the owning side and thus owns the foreign key. + + [php] + // Equivalent DQL query: "select u from User u where u.name=?1" + // User owns an association to an Address but the Address is not loaded in the query. + $rsm = new ResultSetMapping; + $rsm->addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addMetaResult('u', 'address_id', 'address_id'); + + $query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +Foreign keys are used by Doctrine for lazy-loading purposes when querying for objects. +In the previous example, each user object in the result will have a proxy (a "ghost") in place +of the address that contains the address_id. When the ghost proxy is accessed, it loads itself +based on this key. + +Consequently, associations that are *fetch-joined* do not require the foreign keys to be present +in the SQL result set, only associations that are lazy. + +If a fetched entity is part of a mapped hierarchy that requires a discriminator column, this +column must be present in the result set as a meta column so that Doctrine can create the +appropriate concrete type. This is shown in the following example where we assume that there +are one or more subclasses that extend User and either Class Table Inheritance or Single Table Inheritance +is used to map the hierarchy (both use a discriminator column). + + [php] + // Equivalent DQL query: "select u from User u where u.name=?1" + // User is a mapped base class for other classes. User owns no associations. + $rsm = new ResultSetMapping; + $rsm->addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column + + $query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +Note that in the case of Class Table Inheritance, an example as above would result in partial objects +if any objects in the result are actually a subtype of User. When using DQL, Doctrine automatically +includes the necessary joins for this mapping strategy but with native SQL it is your responsibility. \ No newline at end of file diff --git a/manual/en/query-builder.txt b/manual/en/query-builder.txt new file mode 100644 index 000000000..1080d4601 --- /dev/null +++ b/manual/en/query-builder.txt @@ -0,0 +1,356 @@ + +++ The QueryBuilder + +A `QueryBuilder` provides an API that is designed for conditionally constructing a DQL query in several steps. + +It provides a set of classes and methods that is able to programatically build you queries, and also provides a fluent API. +This means that you can change between one methodology to the other as you want, and also pick one if you prefer. + ++++ Constructing a new QueryBuilder object + +The same way you build a normal Query, you build a `QueryBuilder` object, just providing the correct method name. +Here is an example how to build a `QueryBuilder` object: + + [php] + // $em instanceof EntityManager + + // example1: creating a QueryBuilder instance + $qb = $em->createQueryBuilder(); + +Once you created an instance of QueryBuilder, it provides a set of useful informative functions that you can use. +One good example is to inspect what type of object the `QueryBuilder` is. + + [php] + // $qb instanceof QueryBuilder + + // example2: retrieving type of QueryBuilder + echo $qb->getType(); // Prints: 0 + +There're currently 3 possible return values for `getType()`: + +* `QueryBuilder::SELECT`, which returns value 0 +* `QueryBuilder::DELETE`, returning value 1 +* `QueryBuilder::UPDATE`, which returns value 2 + +It is possible to retrieve the associated `EntityManager` of the current `QueryBuilder`, its DQL and also a `Query` object when you finish building your DQL. + + [php] + // $qb instanceof QueryBuilder + + // example3: retrieve the associated EntityManager + $em = $qb->getEntityManager(); + + // example4: retrieve the DQL string of what was defined in QueryBuilder + $dql = $qb->getDql(); + + // example5: retrieve the associated Query object with the processed DQL + $q = $qb->getQuery(); + +Internally, `QueryBuilder` works with a DQL cache, which prevents multiple processment if called multiple times. Any changes that may affect the generated DQL actually modifies the state of `QueryBuilder` to a stage we call as STATE_DIRTY. +One `QueryBuilder`can be in two different state: + +* `QueryBuilder::STATE_CLEAN`, which means DQL haven't been altered since last retrieval or nothing were added since its instantiation +* `QueryBuilder::STATE_DIRTY`, means DQL query must (and will) be processed on next retrieval + ++++ Working with QueryBuilder + +All helper methods in `QueryBuilder` relies actually on a single one: `add()`. +This method is the responsable to build every piece of DQL. It takes 3 parameters: `$dqlPartName`, `$dqlPart` and `$append` (default=false) + +* `$dqlPartName`: Where the `$dqlPart` should be placed. Possible values: select, from, where, groupBy, having, orderBy +* `$dqlPart`: What should be placed in `$dqlPartName`. Accepts a string or any instance of `Doctrine\ORM\Query\Expr\*` +* `$append`: Optional flag (default=false) if the `$dqlPart` should override all previously defined items in `$dqlPartName` or not + +- + + [php] + // $qb instanceof QueryBuilder + + // example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support + $qb->add('select', 'u') + ->add('from', 'User u') + ->add('where', 'u.id = ?1') + ->add('orderBy', 'u.name ASC'); + +++++ Expr\* classes + +When you call `add()` with string, it internally evaluates to an instance of `Doctrine\ORM\Query\Expr\Expr\*` class. +Here is the same query of example 6 written using `Doctrine\ORM\Query\Expr\Expr\*` classes: + + [php] + // $qb instanceof QueryBuilder + + // example7: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder using Expr\* instances + $qb->add('select', new Expr\Select(array('u'))) + ->add('from', new Expr\From('User', 'u')) + ->add('where', new Expr\Comparison('u.id', '=', '?1')) + ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); + +Of course this is the hardest way to build a DQL query in Doctrine. To simplify some of these efforts, we introduce what we call as `Expr` helper class. + +++++ The Expr class + +To workaround most of the issues that `add()` method may cause, Doctrine created a class that can be considered as a helper for building queries. +This class is called `Expr`, which provides a set of useful static methods to help building queries: + + [php] + // $qb instanceof QueryBuilder + + // example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.surname DESC" using Expr class + $qb->add('select', $qb->expr()->select('u')) + ->add('from', $qb->expr()->from('User', 'u')) + ->add('where', $qb->expr()->orx( + $qb->expr()->eq('u.id', '?1'), + $qb->expr()->like('u.nickname', '?2') + )) + ->add('orderBy', $qb->expr()->orderBy('u.surname', 'ASC')); + +Although it still sounds complex, the ability to programatically create conditions are the main feature of `Expr`. +Here it is a complete list of supported helper methods available: + + [php] + class Expr + { + /** Base objects **/ + + // Example usage - $qb->expr()->select('u') + public function select($select = null); // Returns Expr\Select instance + + // Example - $qb->expr()->from('User', 'u') + public function from($from, $alias); // Returns Expr\From instance + + // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', Expr\Join::ON, 'p.user_id = u.id AND p.country_code = 55'); + // Example - $qb->expr()->leftJoin('u. Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); + public function leftJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance + + // Example - $qb->expr()->innerJoin('u.Group', 'g', Expr\Join::WITH, 'g.manager_level = 100'); + // Example - $qb->expr()->innerJoin('u.Group', 'g', 'WITH', $qb->expr()->eq('g.manager_level', '100')); + public function innerJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance + + // Example - $qb->expr()->orderBy('u.surname', 'ASC')->add('u.firstname', 'ASC')->... + public function orderBy($sort = null, $order = null); // Returns Expr\OrderBy instance + + // Example - $qb->expr()->groupBy()->add('u.id')->... + public function groupBy($groupBy = null); // Returns Expr\GroupBy instance + + + /** Conditional objects **/ + + // Example - $qb->expr()->andx($cond1 [, $condN])->add(...)->... + public function andx($x = null); // Returns Expr\Andx instance + + // Example - $qb->expr()->orx($cond1 [, $condN])->add(...)->... + public function orx($x = null); // Returns Expr\Orx instance + + + /** Comparison objects **/ + + // Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1 + public function eq($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1 + public function neq($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1 + public function lt($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1 + public function lte($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1 + public function gt($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1 + public function gte($x, $y); // Returns Expr\Comparison instance + + + /** Arithmetic objects **/ + + // Example - $qb->expr()->prod('u.id', '2') => u.id * 2 + public function prod($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->diff('u.id', '2') => u.id - 2 + public function diff($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->sum('u.id', '2') => u.id + 2 + public function sum($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->quot('u.id', '2') => u.id / 2 + public function quot($x, $y); // Returns Expr\Math instance + + + /** Pseudo-function objects **/ + + // Example - $qb->expr()->exists($qb2->getDql()) + public function exists($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->all($qb2->getDql()) + public function all($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->some($qb2->getDql()) + public function some($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->any($qb2->getDql()) + public function any($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1')) + public function not($restriction); // Returns Expr\Func instance + + // Example - $qb->expr()->in('u.id', array(1, 2, 3)) + public function in($x, $y); // Returns Expr\Func instance + + // Example - $qb->expr()->notIn('u.id', '2') + public function notIn($x, $y); // Returns Expr\Func instance + + // Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%')) + public function like($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->between('u.id', '1', '10') + public function between($val, $x, $y); // Returns Expr\Func + + + /** Function objects **/ + + // Example - $qb->expr()->trim('u.firstname') + public function trim($x); // Returns Expr\Func + + // Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat(' ', 'u.lastname')) + public function concat($x, $y); // Returns Expr\Func + + // Example - $qb->expr()->substr('u.firstname', 0, 1) + public function substr($x, $from, $len); // Returns Expr\Func + + // Example - $qb->expr()->lower('u.firstname') + public function lower($x); // Returns Expr\Func + + // Example - $qb->expr()->upper('u.firstname') + public function upper($x); // Returns Expr\Func + + // Example - $qb->expr()->length('u.firstname') + public function length($x); // Returns Expr\Func + + // Example - $qb->expr()->avg('u.age') + public function avg($x); // Returns Expr\Func + + // Example - $qb->expr()->max('u.age') + public function max($x); // Returns Expr\Func + + // Example - $qb->expr()->min('u.age') + public function min($x); // Returns Expr\Func + + // Example - $qb->expr()->abs('u.currentBalance') + public function abs($x); // Returns Expr\Func + + // Example - $qb->expr()->sqrt('u.currentBalance') + public function sqrt($x); // Returns Expr\Func + + // Example - $qb->expr()->count('u.firstname') + public function count($x); // Returns Expr\Func + + // Example - $qb->expr()->countDistinct('u.surname') + public function countDistinct($x); // Returns Expr\Func + } + +++++ Helper methods + +Until now it was described the hardcore level of creating queries. It may be useful to work that way for optimization purposes, but most of the time it is preferred to work higher level. +To simplify even more the way you build a query in Doctrine, we can take advantage of what we call as helper methods. For all base code, it has a set of useful methods to simplify programmer's life. +Illustrating how to work with it, here is the same example 6 written now using `QueryBuilder` helper methods: + + [php] + // $qb instanceof QueryBuilder + + // example9: how to define: "SELECT u FROM User u WHERE u.id = ?1 ORDER BY u.name ASC" using QueryBuilder helper methods + $qb->select('u') + ->from('User', 'u') + ->where('u.id = ?1') + ->orderBy('u.name ASC'); + +`QueryBuilder` helper methods are considered the standard way to build DQL queries. Although it is supported, it should be avoided to use string based queries and greatly encouraged to use `$qb->expr()->*` methods. +Here is a converted example 8 to suggested standard way to build queries: + + [php] + // $qb instanceof QueryBuilder + + // example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ?1 OR u.nickname LIKE ?2 ORDER BY u.surname DESC" using QueryBuilder helper methods + $qb->select(array('u')) // string 'u' is converted to array internally + ->from('User', 'u') + ->where($qb->expr()->orx( + $qb->expr()->eq('u.id', '?1'), + $qb->expr()->like('u.nickname', '?2') + )) + ->orderBy('u.surname', 'ASC')); + +Here is a complete list of helper methods in `QueryBuilder`: + + [php] + class QueryBuilder + { + // Example - $qb->select('u') + // Example - $qb->select(array('u', 'p')) + // Example - $qb->select($qb->expr()->select('u', 'p')) + public function select($select = null); + + // Example - $qb->delete('User', 'u') + public function delete($delete = null, $alias = null); + + // Example - $qb->update('Group', 'g') + public function update($update = null, $alias = null); + + // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold')) + // Example - $qb->set('u.numChilds', 'u.numChilds + ?1') + // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1')) + public function set($key, $value); + + // Example - $qb->from('Phonenumber', 'p') + public function from($from, $alias = null); + + // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::ON, $qb->expr()->and($qb->expr()->eq('u.group_id', 'g.id'), 'g.name = ?1')) + // Example - $qb->innerJoin('u.Group', 'g', 'ON', 'u.group_id = g.id AND g.name = ?1') + public function innerJoin($join, $alias = null, $conditionType = null, $condition = null); + + // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55)) + // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55') + public function leftJoin($join, $alias = null, $conditionType = null, $condition = null); + + // NOTE: ->where() overrides all previously set conditions + // + // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2')) + // Example - $qb->where($qb->expr()->andx($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2'))) + // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2') + public function where($where); + + // Example - $qb->andWhere($qb->expr()->orx($qb->expr()->lte('u.age', 40), 'u.numChild = 0')) + public function andWhere($where); + + // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10)); + public function orWhere($where); + + // NOTE: -> groupBy() overrides all previously set grouping items + // + // Example - $qb->groupBy('u.id') + public function groupBy($groupBy); + + // Example - $qb->addGroupBy('g.name') + public function addGroupBy($groupBy); + + // NOTE: -> having() overrides all previously set having conditions + // + // Example - $qb->having('u.salary >= ?1') + // Example - $qb->having($qb->expr()->gte('u.salary', '?1')) + public function having($having); + + // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0)) + public function andHaving($having); + + // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100')) + public function orHaving($having); + + // NOTE: -> orderBy() overrides all previously set ordering items + // + // Example - $qb->orderBy('u.surname', 'DESC') + public function orderBy($sort, $order = null); + + // Example - $qb->addOrderBy('u.firstName') + public function addOrderBy($sort, $order = null); // Default $order = 'ASC' + } \ No newline at end of file diff --git a/manual/en/tools.txt b/manual/en/tools.txt new file mode 100644 index 000000000..d97136c69 --- /dev/null +++ b/manual/en/tools.txt @@ -0,0 +1,192 @@ +++ The Doctrine CLI + +The Doctrine CLI (Command Line Interface) is a tool for simplifying many common tasks during the development of a project that uses Doctrine. + ++++ Installation + +If you installed Doctrine 2 through PEAR, the `doctrine` command line tool should already be available to you. + +If you use Doctrine through SVN or a release package you need to copy the `doctrine` and `doctrine.php` files from the `tools/sandbox` or `bin` folder, respectively, to a location of your choice, for example a `tools` folder of your project. +In addition you may need to edit `doctrine.php` and adjust some paths to the new environment. You may want to add require_once() statement at the top of doctrine.php to set up the include_path for Doctrine classes. + ++++ Getting Help + +Type `doctrine` on the command line and you should see an overview of the available tasks or use the --help flag to get information on the available tasks. If you want to know more about the use of the schema tool for example you can call: + + doctrine orm:schema-tool --help + ++++ Configuration + +Whenever the `doctrine` command line tool is invoked it requires an instance of `Doctrine\Common\Cli\CliConfiguration` to be able to correctly work. When using ORM package, it is required to define an attribute inside Configuration: `em`. `em` must be an `EntityManager` instance that is used by ORM command-line tasks. + +Many tasks of the Doctrine CLI require the `em` attribute to be an `EntityManager` in order to execute. The `EntityManager` instance implicitly defines a database connection. +If you invoke a task that requires an EntityManager (and/or a database connection) and the `em` attribute is not defined in your CLI Configuration instance, the task invoking will report an error for you. + +CLI COnfiguration instance can be in a separate file (ie. `cli-config.php`) that contains typical Doctrine bootstrap code and predefines the needed attributes mentioned above. A typical `cli-config.php` file looks as follows: + + [php] + require_once '/path/to/lib/Doctrine/Common/ClassLoader.php'; + + $classLoader = new \Doctrine\Common\ClassLoader('MyProject', '/path/to/myproject/lib'); + $classLoader->register(); + + $ormConfig = new \Doctrine\ORM\Configuration(); + $ormConfig->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); + $ormConfig->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); + $ormConfig->setProxyNamespace('MyProject\Proxies'); + + $connectionOptions = array( + 'driver' => 'pdo_sqlite', + 'path' => 'database.sqlite' + ); + + $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $ormConfig); + + $cliConfig = new \Doctrine\Common\Cli\Configuration(); + $cliConfig->setAttribute('em', $em); + $cliConfig->setAttribute('globalArguments', array( + 'class-dir' => '/path/to/myproject/lib/MyProject/Models/' + )); + +It is important to define an instance of Doctrine\Common\Cli\Configuration that the doctrine.php script will ultimately use. For the required name of this variable, check the doctrine.php included in your package; the most new ones will automatically find the variable no matter what its name is, as long as it is an instance of the right class. +The CLI Configuration should contain at least the 'em' attribute, set to the EntityManager. To use many tasks a 'globalOptions' array should be set. +The `globalArguments` content will be passed to every command line task. For instance, to use the orm:schema-tool task to generate database tables from annotations, the class-dir global option must be set as in the example. + ++++ Task Overview + +The following tasks are currently available: + + * `dbal:run-sql`: Used to run arbitrary SQL on the command-line. + * `orm:convert-mapping`: Used to convert between annotations/xml/yaml mapping informations as well as for class generating from xml/yaml mapping documents or for reverse engineering. + * `orm:generate-proxies`: Used to generate proxy classes used by Doctrine. + * `orm:run-dql`: Used to run arbitrary DQL on the command-line. + * `orm:schema-tool`: Used to forward-engineer the relational database schema from existing classes and mappings. + * `orm:version`: Used to show the current version of the CLI and Doctrine. + +++ Database Schema Generation + +To generate your database schema from your Doctrine mapping files you can use the +`SchemaTool` class or the `schema-tool` CLI task. + +When using the SchemaTool class directly, create your schema using the `createSchema()` method. First create an instance of the `SchemaTool` and pass it an instance of the `EntityManager` that you want to use to create the schema. This method receives an array of `ClassMetadataInfo` instances. + + [php] + $tool = new \Doctrine\ORM\Tools\SchemaTool($em); + $classes = array( + $em->getClassMetadata('Entities\User'), + $em->getClassMetadata('Entities\Profile') + ); + $tool->createSchema($classes); + +To drop the schema you can use the `dropSchema()` method. + + [php] + $tool->dropSchema($classes); + +This drops all the tables that are currently used by your metadata model. +When you are changing your metadata alot during development you might want +to drop the complete database instead of only the tables of the current model +to clean up with orphaned tables. + + [php] + $tool->dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE); + +You can also use database introspection to update your schema easily with the +`updateSchema()` method. It will compare your existing database schema to the +passed array of `ClassMetdataInfo` instances. + + [php] + $tool->updateSchema($classes); + +If you want to use this functionality from the command line you can use the +`schema-tool` task. + +To create the schema use the `--create` option: + + $ php doctrine orm:schema-tool --create + +To drop the scheme use the `--drop` option: + + $ php doctrine orm:schema-tool --drop + +If you want to drop and then recreate the schema then use both options: + + $ php doctrine orm:schema-tool --drop --create + +As you would think, if you want to update your schema use the `--update` option: + + $ php doctrine orm:schema-tool --update + +All of the above tasks also accept a `--dump-sql` option that will output the SQL +for the ran operation. + + $ php doctrine orm:schema-tool --create --dump-sql + +Before using the orm:schema-tool task, remember to configure your cli-config.php properly. + +++ Convert Mapping Information + +Doctrine comes with some special tools for working with the various supported +formats for specifying mapping information. + +You have the ability to convert from a few different sources. + +* An existing database +* A directory of YAML schema files +* A directory of XML schema files +* A directory of PHP scripts which populate `ClassMetadataInfo` instances +* A directory of PHP classes defining Doctrine entities with annotations + +To convert a mapping source you can do everything you need with the `ClassMetadataExporter`. + + [php] + $cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter(); + +Once you have an instance you can start adding mapping sources to convert. + + [php] + $cme->addMappingSource('/path/to/yml', 'yml'); + $cme->addMappingSource('/path/to/xml', 'xml'); + $cme->addMappingSource('/path/to/php', 'php'); + $cme->addMappingSource('/path/to/annotations', 'annotation'); + +Now to convert the added mapping sources you can do so by using the exporter drivers. + + [php] + $metadatas = $cme->getMetadatasForMappingSources(); + + $exporter = $cme->getExporter('yml', '/path/to/export/yml'); + $exporter->setMetadatas($metadatas); + $exporter->export(); + +This functionality functionality is also available from the command line to for +example convert some YAML mapping files to XML. + + $ php doctrine orm:convert-mapping --from=/path/to/yml --to=xml --dest=/path/to/xml + +++ Reverse Engineering + +You can use the same `ClassMetadataExporter` to reverse engineer a database and +generate YAML, XML, etc. from your existing databases. + + [php] + $sm = $em->getConnection()->getSchemaManager(); + + $cme->addMappingSource($sm, 'database'); + $metadatas = $cme->getMetadatasForMappingSources(); + + $exporter = $cme->getExporter('yml', '/path/to/export/yml'); + $exporter->setMetadatas($metadatas); + $exporter->export(); + +From the command line it is very simple to do something like reverse engineer +your existing database to set of YAML mapping files. + + $ php doctrine orm:convert-mapping --from=database --to=yml --dest=/path/to/yml + +> **CAUTION** +> Reverse Engineering is not always working perfectly depending on special cases. +> It will only detect Many-To-One relations (even if they are One-To-One) and +> will try to create entities from Many-To-Many tables. It also has problems +> with naming of foreign keys that have multiple column names. Any Reverse Engineered +> Database-Schema needs considerable manual work to become a useful domain model. diff --git a/manual/en/transactions-and-concurrency.txt b/manual/en/transactions-and-concurrency.txt new file mode 100644 index 000000000..302efd37e --- /dev/null +++ b/manual/en/transactions-and-concurrency.txt @@ -0,0 +1,124 @@ +++ Transaction Demarcation + +Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very important because if not done properly it can have a negative effect on the performance of your application. Many databases and database abstraction layers like PDO by default operate in auto-commit mode, which means that every single SQL statement is wrapped in a small transaction that is immediately committed. Without any explicit transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap and many small transactions degrade the performance of your application. + +For the most part, Doctrine 2 already takes care of proper transaction demarcation for you: All the write operations (INSERT/UPDATE/DELETE) are queued until `EntityManager#flush()` is invoked which wraps all of these changes in a single, small transaction. This is a strategy called "transactional write-behind" that is frequently used in ORM solutions to increase efficiency. + +However, Doctrine 2 also allows you to take over and control transaction demarcation yourself, thereby "widening" the transaction boundaries. This is possible due to transparent nesting of transactions that is described in the following section. + +++ Transaction Nesting + +Each `Doctrine\DBAL\Driver\Connection` instance is wrapped in a `Doctrine\DBAL\Connection` that adds support for transparent nesting of transactions. For that purpose, the Connection class keeps an internal counter that represents the nesting level and is increased/decreased as beginTransaction(), commit() and rollback() are invoked. beginTransaction() increases the nesting level whilst commit() and rollback() decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1, beginTransaction() is invoked on the underlying driver and whenever the nesting level transitions from 1 to 0, commit() or rollback() is invoked on the underlying driver, depending on whether the transition was caused by `Connection#commit()` or `Connection#rollback()`. + +Lets visualize what that means in practice. It means that the first call to `Doctrine\DBAL\Connection#beginTransaction()` will increase the nesting level from 0 to 1 and invoke beginTransaction() on the underlying driver, effectively starting a "real" transaction by suspending auto-commit mode. Any subsequent, nested calls to `Doctrine\DBAL\Connection#beginTransaction()` would only increase the nesting level. + +Here is an example to help visualize how this works: + + [php] + // $conn instanceof Doctrine\DBAL\Connection + try { + $conn->beginTransaction(); // 0 => 1, "real" transaction started + + ... + + try { + $conn->beginTransaction(); // 1 => 2 + + ... + + $conn->commit(); // 2 => 1 + } catch (Exception $e) { + $conn->rollback(); // 2 => 1 + throw $e; + } + + ... + + $conn->commit(); // 1 => 0, "real" transaction committed + } catch (Exception $e) { + $conn->rollback(); // 1 => 0, "real" transaction rollback + throw $e; + } + +What is the benefit of this? It allows reliable and transparent widening of transaction boundaries. Given the following code snippet, without any explicit transaction demarcation: + + [php] + // $em instanceof EntityManager + $user = new User; + $user->setName('George'); + $em->persist($user); + $em->flush(); + +Inside `EntityManager#flush()` something like this happens: + + [php] + try { + $conn->beginTransaction(); // suspend auto-commit + + ... commit all changes to the database ... + + $conn->commit(); + } catch (Exception $e) { + $conn->rollback(); + throw $e; + } + +Since we do not do any custom transaction demarcation in the first snippet, `EntityManager#flush()` will begin and commit/rollback a "real" transaction. Now, if we want to widen the transaction boundaries, say, because we want to include some manual work with a `Doctrine\DBAL\Connection` in the same transaction, we can simply do this: + + [php] + // $em instanceof EntityManager + $conn = $em->getConnection(); + try { + $conn->beginTransaction(); // suspend auto-commit + + // Direct use of the Connection + $conn->insert(...); + + $user = new User; + $user->setName('George'); + $em->persist($user); + $em->flush(); + + $conn->commit(); + } catch (Exception $e) { + $conn->rollback(); + // handle or rethrow + } + +Now, our own code controls the "real" transaction and the transaction demarcation that happens inside `EntityManager#flush()` will merely affect the nesting level. When flush() returns, either by throwing an exception or regularly, the nesting level is the same as before the invocation of flush(), in this case 1, and thus our own $conn->commit() / $conn->rollback() affect the "real" transaction as expected, since we were the ones who started the transaction. + +> **CAUTION** +> Directly invoking `PDO#beginTransaction()`, `PDO#commit()` or `PDO#rollback()` or the +> corresponding methods on the particular `Doctrine\DBAL\Driver\Connection` instance in +> use bybasses the transparent transaction nesting that is provided by +> `Doctrine\DBAL\Connection` and can therefore corrupt the nesting level, causing errors +> with broken transaction boundaries that may be hard to debug. + +++ Optimistic Locking + +Database transactions are fine for concurrency control during a single request. However, a database transaction should not span across requests, the so-called "user think time". Therefore a long-running "business transaction" that spans multiple requests needs to involve several database transactions. Thus, database transactions alone can no longer control concurrency during such a long-running business transaction. Concurrency control becomes the partial responsibility of the application itself. + +Doctrine has integrated support for automatic optimistic locking via a version field. In this approach any entity that should be protected against concurrent modifications during long-running business transactions gets a version field that is either a simple number (mapping type: integer) or a timestamp (mapping type: datetime). When changes to such an entity are persisted at the end of a long-running conversation the version of the entity is compared to the version in the database and if they dont match, an `OptimisticLockException` is thrown, indicating that the entity has been modified by someone else already. + +You designate a version field in an entity as follows. In this example we'll use an integer. + + [php] + class User + { + // ... + /** @Version @Column(type="integer") */ + private $version; + // ... + } + +You could also just as easily use a datetime column and instead of incrementing an integer, a timestamp will be kept up to date. + + + [php] + class User + { + // ... + /** @Version @Column(type="integer") */ + private $version; + // ... + } \ No newline at end of file diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt new file mode 100644 index 000000000..d6c76ad07 --- /dev/null +++ b/manual/en/working-with-objects.txt @@ -0,0 +1,445 @@ +++ Understanding + +In this chapter we will help you understand the `EntityManager` and the `UnitOfWork`. +A Unit of Work is similar to an object-level transaction. A new Unit of Work is +implicity started when an EntityManager is initially created or after +`EntityManager#flush()` has been invoked. A Unit of Work is committed +(and a new one started) by invoking `EntityManager#flush()`. + +A Unit of Work can be manually closed by calling EntityManager#close(). Any +changes to objects within this Unit of Work that have not yet been persisted +are lost. + ++++ The size of a Unit of Work + +The size of a Unit of Work mainly refers to the number of managed entities at +a particular point in time. + ++++ The cost of flush() + +How costly a flush operation is in terms of performance mainly depends on 2 factors: + +* The size of your current Unit of Work +* The configured change tracking policies + +You can get the size of your Unit of Work as follows: + + [php] + $uowSize = $em->getUnitOfWork()->size(); + +The size represents the number of managed entities in the Unit of Work. This +size affects the performance of flush() operations due to change tracking +(see "Change Tracking Policies") and, of course, memory consumption, so you +may want to check it from time to time during development. + +> **CAUTION** +> Do not invoke `flush` after every change to an entity or every single invocation of +> persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the +> performance of your application. Instead form units of work that operate on your objects +> and call `flush` when you are done. While serving a single HTTP request there should +> be no need for invoking `flush` more than 0-2 times. + ++++ Direct access to a Unit of Work + +You can get direct access to the Unit of Work by calling `EntityManager#getUnitOfWork()`. +This will return the UnitOfWork instance the EntityManager is currently using. + + [php] + $uow = $em->getUnitOfWork(); + +> **NOTE** +> Directly manipulating a UnitOfWork is not recommended. When working directly with the +> UnitOfWork API respect methods marked as INTERNAL by not using them and carefully read +> the API documentation. + +++ Persisting entities + +An entity can be made persistent by passing it to the `EntityManager#persist($entity)` +method. By applying the persist operation on some entity, that entity becomes MANAGED, +which means that its persistence is from now on managed by an EntityManager. As a +result the persistent state of such an entity will subsequently be properly +synchronized with the database when `EntityManager#flush()` is invoked. + +> **CAUTION** +> Invoking the `persist` method on an entity does NOT cause an immediate SQL INSERT to be +> issued on the database. Doctrine applies a strategy called "transactional write-behind", +> which means that it will delay most SQL commands until `EntityManager#flush()` is +> invoked which will then issue all necessary SQL statements to synchronize your objects +> with the database in the most efficient way and a single, short transaction, +> taking care of maintaining referential integrity. + +- + +> **CAUTION** +> Generated entity identifiers / primary keys are guaranteed to be available after the +> next invocation of `EntityManager#flush()` that involves the entity in question. +> YOU CAN NOT RELY ON A GENERATED IDENTIFIER TO BE AVAILABLE AFTER INVOKING `persist`! + +Example: + + [php] + $user = new User; + $user->setName('Mr.Right'); + $em->persist($user); + $em->flush(); + // If $user had a generated identifier, it would now be available. + +The semantics of the persist operation, applied on an entity X, are as follows: + +* If X is a new entity, it becomes managed. The entity X will be entered into the database at or before transaction commit or as a result of the flush operation. +* If X is a preexisting managed entity, it is ignored by the persist operation. However, the persist operation is cascaded to entities referenced by X, if the relationships from X to these other entities are mapped with cascade=PERSIST or cascade=ALL (see "Transitive Persistence"). +* If X is a removed entity, it becomes managed. +* If X is a detached entity, an InvalidArgumentException will be thrown. + + +++ Removing entities + +An entity can be removed from persistent storage by passing it to the `EntityManager#remove($entity)` method. By applying the `remove` operation on some entity, that entity becomes REMOVED, which means that its persistent state will be deleted once `EntityManager#flush()` is invoked. The in-memory state of an entity is unaffected by the `remove` operation. + +> **CAUTION** +> Just like `persist`, invoking `remove` on an entity does NOT cause an immediate SQL +> DELETE to be issued on the database. The entity will be deleted on the next invocation +> of `EntityManager#flush()` that involves that entity. + +Example: + + [php] + $em->remove($user); + $em->flush(); + +The semantics of the remove operation, applied to an entity X are as follows: + +* If X is a new entity, it is ignored by the remove operation. However, the remove operation is cascaded to entities referenced by X, if the relationship from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). +* If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). +* If X is a detached entity, an InvalidArgumentException will be thrown. +* If X is a removed entity, it is ignored by the remove operation. +* A removed entity X will be removed from the database at or before transaction commit or as a result of the flush operation. + + +++ Detaching entities + +An entity is detached from an EntityManager and thus no longer managed by +invoking the `EntityManager#detach($entity)` method on it or by cascading +the detach operation to it. Changes made to the detached entity, if any +(including removal of the entity), will not be synchronized to the database +after the entity has been detached. + +Doctrine will not hold on to any references to a detached entity. + +Example: + + [php] + $em->detach($entity); + +The semantics of the detach operation, applied to an entity X are as follows: + +* If X is a managed entity, the detach operation causes it to become detached. The detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X. +* If X is a new or detached entity, it is ignored by the detach operation. +* If X is a removed entity, the detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X. + +There are several situations in which an entity is detached automatically without invoking the `detach` method: + +* When `EntityManager#clear()` is invoked, all entities that are currently managed by the EntityManager instance become detached. +* When serializing an entity. The entity retrieved upon subsequent unserialization will be detached (This is the case for all entities that are serialized and stored in some cache, i.e. when using the Query Result Cache). + +The `detach` operation is usually not as frequently needed and used as `persist` and `remove`. + + +++ Merging entities + +Merging entities refers to the merging of (usually detached) entities into the +context of an EntityManager so that they become managed again. To merge the +state of an entity into an EntityManager use the `EntityManager#merge($entity)` +method. The state of the passed entity will be merged into a managed copy of +this entity and this copy will subsequently be returned. + +Example: + + [php] + $detachedEntity = unserialize($serializedEntity); // some detached entity + $entity = $em->merge($detachedEntity); + // $entity now refers to the fully managed copy returned by the merge operation. + // The EntityManager $em now manages the persistence of $entity as usual. + +> **WARNING** +> When you want to serialize/unserialize entities you have to make all entity properties +> protected, never private. The reason for this is, if you serialize a class that was a proxy +> instance before the private variables won't be serialized and a PHP Notice is thrown. + +The semantics of the merge operation, applied to an entity X, are as follows: + +* If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity or a new managed copy X' of X is created. +* If X is a new entity instance, an InvalidArgumentException will be thrown. +* If X is a removed entity instance, an InvalidArgumentException will be thrown. +* If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been mapped with the cascade element value MERGE or ALL (see "Transitive Persistence"). +* For all entities Y referenced by relationships from X having the cascade element value +MERGE or ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.) +* If X is an entity merged to X', with a reference to another entity Y, where cascade=MERGE or cascade=ALL is not specified, then navigation of the same association from X' yields a reference to a managed object Y' with the same persistent identity as Y. + +The `merge` operation will throw an `OptimisticLockException` if the entity +being merged uses optimistic locking through a version field and the versions +of the entity being merged and the managed copy dont match. This usually means +that the entity has been modified while being detached. + +The `merge` operation is usually not as frequently needed and used as `persist` +and `remove`. The most common scenario for the `merge` operation is to reattach +entities to an EntityManager that come from some cache (and are therefore detached) +and you want to modify and persist such an entity. + +> **NOTE** +> If you load some detached entities from a cache and you do not need to persist or +> delete them or otherwise make use of them without the need for persistence services +> there is no need to use `merge`. I.e. you can simply pass detached objects from a cache +> directly to the view. + + +++ Associations + +Associations between entities are represented just like in regular object-oriented PHP, with references to other objects or collections of objects. When it comes to persistence, it is important to understand three main things: + + * The concept of owning and inverse sides in bidirectional associations as described [here](http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#owning-side-and-inverse-side). + * A collection of entities always only represents the association to the containing entities. If an entity is removed from a collection, the association is removed, not the entity itself. + * Collection-valued persistent fields and properties must be defined in terms of the Doctrine\Common\Collections\Collection interface. [See here](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities:persistent-fields) for more details. + + +++ Establishing Associations + +Establishing an association between two entities is straight-forward. Here are some examples: + + [php] + // Article <- one-to-many -> Comment + $article->getComments()->add($comment); + $comment->setArticle($article); + + // User <- many-to-many -> Groups + $user->getGroups()->add($group); + $group->getUsers()->add($user); + + // User <- one-to-one -> Address + $user->setAddress($address); + $address->setUser($user); + + +Notice how always both sides of the bidirectional association are updated. Unidirectional associations are consequently simpler to handle. + +++ Removing Associations + +Removing an association between two entities is similarly straight-forward. There are two strategies +to do so, by key and by element. Here are some examples: + + [php] + // Remove by Elements + // Article <- one-to-many -> Comment + $article->getComments()->removeElement($comment); + $comment->setArticle(null); + + // User <- many-to-many -> Group + $user->getGroups()->removeElement($group); + $group->getUsers()->removeElement($user); + + // Remove by key + $article->getComments()->remove($ithComment); + $comment->setArticle(null); + + // User <- one-to-one -> Address + $user->setAddress(null); + $address->setUser(null); + + +Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does not allow null values and setAddress(null) will fail for removing the association. If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`. This can also provide better encapsulation as it hides the internal meaning of not having an address. + +Since Doctrine always only looks at the owning side of a bidirectional association, it is essentially not necessary that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. + + +++ Association Management Methods + +It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained. + +The following code shows a simple, idiomatic example for a bidirectional one-to-many association between an Article and its Comments. + + [php] + // Mappings not shown. + class Article { + // The comments of the article. + private $comments; + // ... constructor omitted ... + public function addComment(Comment $comment) { + $this->comments->add($comment); + $comment->setArticle($this); + } + public function getComments() { + return $this->comments; + } + } + class Comment { + // The article the comment refers to. + private $article; + // ... constructor omitted ... + public function setArticle($article) { + $this->article = $article; + } + public function getArticle() { + return $this->article; + } + } + +With the above implementation, it is always ensured that at least the owning side from Doctrine's point of view (Comment) is properly updated. You will notice that `setArticle` does not call `addComment`, thus the bidirectional association is strictly-speaking still incomplete, if a user of the class only invokes `setArticle`. If you naively call `addComment` in `setArticle`, however, you end up with an infinite loop, so more work is needed. As you can see, proper bidirectional association management in plain OOP is a non-trivial task and encapsulating all the details inside the classes can be challenging. + +There is no single, best way for association management. It greatly depends on the requirements of your concrete domain model as well as your preferences. + + +++ Transitive persistence + +Persisting, removing, detaching and merging individual entities can become pretty +cumbersome, especially when a larger object graph with collections is involved. +Therefore Doctrine 2 provides a mechanism for transitive persistence through +cascading of these operations. Each association to another entity or a collection +of entities can be configured to automatically cascade certain operations. By +default, no operations are cascaded. + +The following cascade options exist: + + * persist : Cascades persist operations to the associated entities. + * remove : Cascades remove operations to the associated entities. + * merge : Cascades merge operations to the associated entities. + * detach : Cascades detach operations to the associated entities. + * all : Cascades persist, remove, merge and detach operations to associated entities. + +The following example shows an association to a number of addresses. If persist() +or remove() is invoked on any User entity, it will be cascaded to all associated +Address entities in the $addresses collection. + + [php] + class User + { + //... + /** + * @OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "remove"}) + */ + private $addresses; + //... + } + +Even though automatic cascading is convenient it should be used with care. +Do not blindly apply cascade=all to all associations as it will unnecessarily +degrade the performance of your application. + + +++ Querying + +Doctrine 2 provides the following ways, in increasing level of power and flexibility, to query for persistent objects. You should always start with the simplest one that suits your needs. + ++++ By Primary Key + +The most basic way to query for a persistent object is by its identifier / primary key using the `EntityManager#find($entityName, $id)` method. Here is an example: + + [php] + // $em instanceof EntityManager + $user = $em->find('MyProject\Domain\User', $id); + +The return value is either the found entity instance or null if no instance could be found with the given identifier. + +Essentially, `EntityManager#find()` is just a shortcut for the following: + + [php] + // $em instanceof EntityManager + $user = $em->getRepository('MyProject\Domain\User')->find($id); + +`EntityManager#getRepository($entityName)` returns a repository object which provides many ways to retreive entities of the specified type. By default, the repository instance is of type `Doctrine\ORM\EntityRepository`. You can also use custom repository classes as shown later. + ++++ By Simple Conditions + +To query for one or more entities based on several conditions that form a logical conjunction, use the `findBy` and `findOneBy` methods on a repository as follows: + + [php] + // $em instanceof EntityManager + + // All users that are 20 years old + $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20)); + + // All users that are 20 years old and have a surname of 'Miller' + $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller')); + + // A single user by its nickname + $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); + +An EntityRepository also provides a mechanism for more concise calls through its use of `__call`. Thus, the following two examples are equivalent: + + [php] + // A single user by its nickname + $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); + + // A single user by its nickname (__call magic) + $user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb'); + + ++++ By Eager Loading + +Whenever you query for an entity that has persistent associations and these associations are mapped as EAGER, they will automatically be loaded together with the entity being queried and is thus immediately available to your application. + + ++++ By Lazy Loading + +Whenever you have a managed entity instance at hand, you can traverse and use any associations of that entity that are configured LAZY as if they were in-memory already. Doctrine will automatically load the associated objects on demand through the concept of lazy-loading. + + ++++ By DQL + +The most powerful and flexible method to query for persistent objects is the Doctrine Query Language, an object query language. DQL enables you to query for persistent objects in the language of objects. DQL understands classes, fields, inheritance and associations. +DQL is syntactically very similar to the familar SQL but *it is not SQL*. + +A DQL query is represented by an instance of the `Doctrine\ORM\Query` class. You create a query using `EntityManager#createQuery($dql)`. Here is a simple example: + + [php] + // $em instanceof EntityManager + + // All users with an age between 20 and 30 (inclusive). + $q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30"); + $users = $q->getResult(); + +Note that this query contains no knowledge about the relational schema, only about the object model. DQL supports positional as well as named parameters, many functions, (fetch) joins, aggregates, subqueries and much more. Detailed information about DQL and its syntax as well as the Doctrine\ORM\Query class can be found in [the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language). For programmatically building up queries based on conditions that are only known at runtime, Doctrine provides the special `Doctrine\ORM\QueryBuilder` class. More information on constructing queries with a QueryBuilder can be found [in the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/query-builder). + + ++++ By Native Queries + +As an alternative to DQL or as a fallback for special SQL statements native queries can be used. +Native queries are built by using a hand-crafted SQL query and a ResultSetMapping that describes +how the SQL result set should be transformed by Doctrine. More information about native queries +can be found in [the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/native-sql). + ++++ Custom Repositories + +By default the EntityManager returns a default implementation of `Doctrine\ORM\EntityRepository` when +you call `EntityManager#getRepository($entityClass)`. You can overwrite this behaviour by specifying +the class name of your own Entity Repository in the Annotation, XML or YAML metadata. +In large applications that require lots of specialized DQL queries using a custom repository is +one recommended way of grouping these queries in a central location. + + [php] + namespace MyDomain\Model; + use Doctrine\ORM\EntityRepository; + + /** + * @entity(repositoryClass="MyDomain\Model\UserRepository") + */ + class User + { + + } + + class UserRepository extends EntityRepository + { + public function getAllAdminUsers() + { + return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"') + ->getResult(); + } + } + +You can access your repository now by calling: + + [php] + // $em instanceof EntityManager + + $admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers(); + diff --git a/manual/en/xml-mapping.txt b/manual/en/xml-mapping.txt new file mode 100644 index 000000000..0db537be7 --- /dev/null +++ b/manual/en/xml-mapping.txt @@ -0,0 +1,83 @@ +The XML mapping driver enables you to provide the ORM metadata in form of XML documents. + +The XML driver is backed by an XML Schema document that describes the structure of a mapping document. The most recent version of the XML Schema document is available online at [http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd](http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd). In order to point to the latest version of the document of a particular stable release branch, just append the release number, i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with XML mapping files is to use an IDE/editor that can provide code-completion based on such an XML Schema document. The following is an outline of a XML mapping document with the proper xmlns/xsi setup for the latest code in trunk. + + [xml] + + + ... + + + +The XML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: + + * Each entity/mapped superclass must get its own dedicated XML mapping document. + * The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.). + * All mapping documents should get the extension ".dcm.xml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough. + +- + + [php] + $driver->setFileExtension('.xml'); + +It is recommended to put all XML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the XmlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: + + [php] + // $config instanceof Doctrine\ORM\Configuration + $driver = new XmlDriver(array('/path/to/files')); + $config->setMetadataDriverImpl($driver); + + +++ Example + +As a quick start, here is a small example document that makes use of several common elements: + + [xml] + // Doctrine.Tests.ORM.Mapping.User.dcm.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Be aware that class-names specified in the XML files should be fully qualified. \ No newline at end of file diff --git a/manual/en/yaml-mapping.txt b/manual/en/yaml-mapping.txt new file mode 100644 index 000000000..a5c6b29c7 --- /dev/null +++ b/manual/en/yaml-mapping.txt @@ -0,0 +1,66 @@ +The YAML mapping driver enables you to provide the ORM metadata in form of YAML documents. + +The YAML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: + + * Each entity/mapped superclass must get its own dedicated YAML mapping document. + * The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.). + * All mapping documents should get the extension ".dcm.yml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough. + +- + + [php] + $driver->setFileExtension('.yml'); + +It is recommended to put all YAML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the YamlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: + + [php] + // $config instanceof Doctrine\ORM\Configuration + $driver = new YamlDriver(array('/path/to/files')); + $config->setMetadataDriverImpl($driver); + + +++ Example + +As a quick start, here is a small example document that makes use of several common elements: + + [yml] + # Doctrine.Tests.ORM.Mapping.User.dcm.yml + Doctrine\Tests\ORM\Mapping\User: + type: entity + table: cms_users + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string + length: 50 + oneToOne: + address: + targetEntity: Address + joinColumn: + name: address_id + referencedColumnName: id + oneToMany: + phonenumbers: + targetEntity: Phonenumber + mappedBy: user + cascade: cascadePersist + manyToMany: + groups: + targetEntity: Group + joinTable: + name: cms_users_groups + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + group_id: + referencedColumnName: id + lifecycleCallbacks: + prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] + postPersist: [ doStuffOnPostPersist ] + + Be aware that class-names specified in the YAML files should be fully qualified. \ No newline at end of file diff --git a/migrations/manual/en.txt b/migrations/manual/en.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/rest/manual/en.txt b/rest/manual/en.txt deleted file mode 100644 index e69de29bb..000000000 From 2adf9378e4d29cc4ccadfd9ededdbaf3060728bd Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Tue, 6 Apr 2010 14:37:15 -0400 Subject: [PATCH 004/430] - --- orm/cookbook/en.txt | 8 - orm/cookbook/en/dql-custom-walkers.txt | 174 ---- .../en/dql-user-defined-functions.txt | 198 ----- .../en/getting-started-xml-edition.txt | 704 --------------- ...menting-arrayaccess-for-domain-objects.txt | 89 -- ...nting-the-notify-changetracking-policy.txt | 48 -- .../en/implementing-wakeup-or-clone.txt | 63 -- .../en/integrating-with-codeigniter.txt | 104 --- orm/cookbook/en/validation-of-entities.txt | 114 --- orm/manual/en.txt | 20 - orm/manual/en/annotations-reference.txt | 557 ------------ orm/manual/en/architecture.txt | 81 -- orm/manual/en/association-mapping.txt | 429 ---------- orm/manual/en/basic-mapping.txt | 279 ------ orm/manual/en/batch-processing.txt | 122 --- orm/manual/en/best-practices.txt | 77 -- orm/manual/en/caching.txt | 350 -------- orm/manual/en/configuration.txt | 407 --------- orm/manual/en/dql-doctrine-query-language.txt | 803 ------------------ orm/manual/en/events.txt | 304 ------- orm/manual/en/improving-performance.txt | 25 - orm/manual/en/inheritance-mapping.txt | 141 --- orm/manual/en/introduction.txt | 222 ----- orm/manual/en/native-sql.txt | 196 ----- orm/manual/en/query-builder.txt | 356 -------- orm/manual/en/tools.txt | 192 ----- .../en/transactions-and-concurrency.txt | 124 --- orm/manual/en/working-with-objects.txt | 445 ---------- orm/manual/en/xml-mapping.txt | 83 -- orm/manual/en/yaml-mapping.txt | 66 -- 30 files changed, 6781 deletions(-) delete mode 100644 orm/cookbook/en.txt delete mode 100644 orm/cookbook/en/dql-custom-walkers.txt delete mode 100644 orm/cookbook/en/dql-user-defined-functions.txt delete mode 100644 orm/cookbook/en/getting-started-xml-edition.txt delete mode 100644 orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt delete mode 100644 orm/cookbook/en/implementing-the-notify-changetracking-policy.txt delete mode 100644 orm/cookbook/en/implementing-wakeup-or-clone.txt delete mode 100644 orm/cookbook/en/integrating-with-codeigniter.txt delete mode 100644 orm/cookbook/en/validation-of-entities.txt delete mode 100644 orm/manual/en.txt delete mode 100644 orm/manual/en/annotations-reference.txt delete mode 100644 orm/manual/en/architecture.txt delete mode 100644 orm/manual/en/association-mapping.txt delete mode 100644 orm/manual/en/basic-mapping.txt delete mode 100644 orm/manual/en/batch-processing.txt delete mode 100644 orm/manual/en/best-practices.txt delete mode 100644 orm/manual/en/caching.txt delete mode 100644 orm/manual/en/configuration.txt delete mode 100755 orm/manual/en/dql-doctrine-query-language.txt delete mode 100644 orm/manual/en/events.txt delete mode 100644 orm/manual/en/improving-performance.txt delete mode 100644 orm/manual/en/inheritance-mapping.txt delete mode 100644 orm/manual/en/introduction.txt delete mode 100644 orm/manual/en/native-sql.txt delete mode 100644 orm/manual/en/query-builder.txt delete mode 100644 orm/manual/en/tools.txt delete mode 100644 orm/manual/en/transactions-and-concurrency.txt delete mode 100644 orm/manual/en/working-with-objects.txt delete mode 100644 orm/manual/en/xml-mapping.txt delete mode 100644 orm/manual/en/yaml-mapping.txt diff --git a/orm/cookbook/en.txt b/orm/cookbook/en.txt deleted file mode 100644 index db1b17496..000000000 --- a/orm/cookbook/en.txt +++ /dev/null @@ -1,8 +0,0 @@ -+ Getting Started XML-Edition -+ Implementing ArrayAccess for domain objects -+ Implementing the NOTIFY changetracking policy -+ Validation of Entities -+ Implementing wakeup or clone -+ Integrating with CodeIgniter -+ DQL Custom Walkers -+ DQL User Defined Functions \ No newline at end of file diff --git a/orm/cookbook/en/dql-custom-walkers.txt b/orm/cookbook/en/dql-custom-walkers.txt deleted file mode 100644 index 48d4840a4..000000000 --- a/orm/cookbook/en/dql-custom-walkers.txt +++ /dev/null @@ -1,174 +0,0 @@ -# Extending DQL in Doctrine 2: Custom AST Walkers - -The Doctrine Query Language (DQL) is a propriotary sql-dialect that substitutes -tables and columns for Entity names and their fields. Using DQL you write a query -against the database using your entities. With the help of the metadata you -can write very concise, compact and powerful queries that are then translated -into SQL by the Doctrine ORM. - -In Doctrine 1 the DQL language was not implemented using a real parser. This -made modifications of the DQL by the user impossible. Doctrine 2 in constrast -has a real parser for the DQL language, which transforms the DQL statement -into an [Abstract Syntax Tree](http://en.wikipedia.org/wiki/Abstract_syntax_tree) -and generates the appropriate SQL statement for it. Since this process is -deterministic Doctrine heavily caches the SQL that is generated from any given DQL query, -which reduces the performance overhead of the parsing process to zero. - -You can modify the Abstract syntax tree by hooking into DQL parsing process -by adding a Custom Tree Walker. A walker is an interface that walks each -node of the Abstract syntax tree, thereby generating the SQL statement. - -There are two types of custom tree walkers that you can hook into the DQL parser: - -- An output walker. This one actually generates the SQL, and there is only ever one of them. We implemented the default SqlWalker implementation for it. -- A tree walker. There can be many tree walkers, they cannot generate the sql, however they can modify the AST before its rendered to sql. - -Now this is all awfully technical, so let me come to some use-cases fast -to keep you motivated. Using walker implementation you can for example: - -* Modify the AST to generate a Count Query to be used with a paginator for any given DQL query. -* Modify the Output Walker to generate vendor-specific SQL (instead of ANSI). -* Modify the AST to add additional where clauses for specific entities (example ACL, country-specific content...) -* Modify the Output walker to pretty print the SQL for debugging purposes. - -In this cookbook-entry I will show examples on the first two points. There -are probably much more use-cases. - -## Generic count query for pagination - -Say you have a blog and posts all with one category and one author. A query -for the front-page or any archive page might look something like: - - [sql] - SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... - -Now in this query the blog post is the root entity, meaning its the one that -is hydrated directly from the query and returned as an array of blog posts. -In contrast the comment and author are loaded for deeper use in the object tree. - -A pagination for this query would want to approximate the number of posts that -match the WHERE clause of this query to be able to predict the number of pages -to show to the user. A draft of the DQL query for pagination would look like: - - [sql] - SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... - -Now you could go and write each of these queries by hand, or you can use a tree -walker to modify the AST for you. Lets see how the API would look for this use-case: - - [php] - $pageNum = 1; - $query = $em->createQuery($dql); - $query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20); - - $totalResults = Paginate::count($query); - $results = $query->getResult(); - -The `Paginate::count(Query $query)` looks like: - - [php] - class Paginate - { - static public function count(Query $query) - { - /* @var $countQuery Query */ - $countQuery = clone $query; - - $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker')); - $countQuery->setFirstResult(null)->setMaxResults(null); - - return $countQuery->getSingleScalarResult(); - } - } - -It clones the query, resets the limit clause first and max results and registers the `CountSqlWalker` -customer tree walker which will modify the AST to execute a count query. The walkers -implementation is: - - [php] - class CountSqlWalker extends TreeWalkerAdapter - { - /** - * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. - * - * @return string The SQL. - */ - public function walkSelectStatement(SelectStatement $AST) - { - $parent = null; - $parentName = null; - foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) { - if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { - $parent = $qComp; - $parentName = $dqlAlias; - break; - } - } - - $pathExpression = new PathExpression( - PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, array( - $parent['metadata']->getSingleIdentifierFieldName()) - ); - $pathExpression->type = PathExpression::TYPE_STATE_FIELD; - - $AST->selectClause->selectExpressions = array( - new SelectExpression( - new AggregateExpression('count', $pathExpression, true), null - ) - ); - } - } - -This will delete any given select expressions and replace them with a distinct count -query for the root entities primary key. This will only work if your entity has -only one identifier field (composite keys won't work). - -## Modify the Output Walker to generate Vendor specific SQL - -Most RMDBS have vendor-specific features for optimizing select query -execution plans. You can write your own output walker to introduce certain -keywords using the Query Hint API. A query hint can be set via `Query::setHint($name, $value)` -as shown in the previous example with the `HINT_CUSTOM_TREE_WALKERS` query hint. - -We will implement a custom Output Walker that allows to specifiy the SQL_NO_CACHE -query hint. - - [php] - $dql = "SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ..."; - $query = $m->createQuery($dql); - $query->setQueryHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker'); - $query->setQueryHint("mysqlWalker.sqlNoCache", true); - $results = $query->getResult(); - -Our `MysqlWalker` will extend the default `SqlWalker`. We will modify the generation -of the SELECT clause, adding the SQL_NO_CACHE on those queries that need it: - - [php] - class MysqlWalker extends SqlWalker - { - /** - * Walks down a SelectClause AST node, thereby generating the appropriate SQL. - * - * @param $selectClause - * @return string The SQL. - */ - public function walkSelectClause($selectClause) - { - $sql = parent::walkSelectClause($selectClause); - - if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) { - if ($selectClause->isDistinct) { - $sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql); - } else { - $sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql); - } - } - - return $sql; - } - } - -Writing extensions to the Output Walker requires a very deep understanding -of the DQL Parser and Walkers, but may offer your huge benefits with using vendor specific -features. This would still allow you write DQL queries instead of NativeQueries -to make use of vendor specific features. diff --git a/orm/cookbook/en/dql-user-defined-functions.txt b/orm/cookbook/en/dql-user-defined-functions.txt deleted file mode 100644 index d738617c4..000000000 --- a/orm/cookbook/en/dql-user-defined-functions.txt +++ /dev/null @@ -1,198 +0,0 @@ -By default DQL supports a limited subset of all the vendor-specific SQL functions -common between all the vendors. However in many cases once you have decided on a -specific database vendor, you will never change it during the life of your project. -This decision for a specific vendor potentially allows you to make use of powerful -SQL features that are unique to the vendor. - -> **Note** -> -> It is worth to mention that Doctrine 2 also allows you to handwrite your SQL instead of extending -> the DQL parser, which is sort of an advanced extension point. You can map arbitrary SQL to your -> objects and gain access to vendor specific functionalities using the `EntityManager#createNativeQuery()` API. - -The DQL Parser has hooks to register functions that can then be used in your DQL queries and transformed into SQL, -allowing to extend Doctrines Query capabilities to the vendors strength. This post explains the -Used-Defined Functions API (UDF) of the Dql Parser and shows some examples to give you -some hints how you would extend DQL. - -There are three types of functions in DQL, those that return a numerical value, -those that return a string and those that return a Date. Your custom method -has to be registered as either one of those. The return type information -is used by the DQL parser to check possible syntax errors during the parsing -process, for example using a string function return value in a math expression. - -## Registering your own DQL functions - -You can register your functions adding them to the ORM configuration: - - [php] - $config = new \Doctrine\ORM\Configuration(); - $config->addCustomStringFunction($name, $class); - $config->addCustomNumericFunction($name, $class); - $config->addCustomDatetimeFunction($name, $class); - - $em = EntityManager::create($dbParams, $config); - -The `$name` is the name the function will be referred to in the DQL query. `$class` is a -string of a class-name which has to extend `Doctrine\ORM\Query\Node\FunctionNode`. -This is a class that offers all the necessary API and methods to implement -a UDF. - -In this post we will implement some MySql specific Date calculation methods, -which are quite handy in my opinion: - -## Date Diff - -[Mysql's DateDiff function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff) -takes two dates as argument and calculates the difference in days with `date1-date2`. - -The DQL parser is a top-down recursive descent parser to generate the -Abstract-Syntax Tree (AST) and uses a TreeWalker approach to generate the appropriate -SQL from the AST. This makes reading the Parser/TreeWalker code managable -in a finite amount of time. - -The `FunctionNode` class I referred to earlier requires you to implement -two methods, one for the parsing process (obviously) called `parse` and -one for the TreeWalker process called `getSql()`. I show you the code for -the DateDiff method and discuss it step by step: - - [php] - /** - * DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" - */ - class DateDiff extends FunctionNode - { - // (1) - public $firstDateExpression = null; - public $secondDateExpression = null; - - public function parse(\Doctrine\ORM\Query\Parser $parser) - { - $parser->match(Lexer::T_IDENTIFIER); // (2) - $parser->match(Lexer::T_OPEN_PARENTHESIS); // (3) - $this->firstDateExpression = $parser->ArithmeticPrimary(); // (4) - $parser->match(Lexer::T_COMMA); // (5) - $this->secondDateExpression = $parser->ArithmeticPrimary(); // (6) - $parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3) - } - - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) - { - return 'DATEDIFF(' . - $this->firstDateExpression->dispatch($sqlWalker) . ', ' . - $this->secondDateExpression->dispatch($sqlWalker) . - ')'; // (7) - } - } - -The Parsing process of the DATEDIFF function is going to find two expressions -the date1 and the date2 values, whose AST Node representations will be saved -in the variables of the DateDiff FunctionNode instance at (1). - -The parse() method has to cut the function call "DATEDIFF" and its argument -into pieces. Since the parser detects the function using a lookahead the -T_IDENTIFIER of the function name has to be taken from the stack (2), followed -by a detection of the arguments in (4)-(6). The opening and closing parenthesis -have to be detected also. This happens during the Parsing process and leads -to the generation of a DateDiff FunctionNode somewhere in the AST of the -dql statement. - -The `ArithmeticPrimary` method call is the most common denominator of valid -EBNF tokens taken from the [DQL EBNF grammer](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf) -that matches our requirements for valid input into the DateDiff Dql function. -Picking the right tokens for your methods is a tricky business, but the EBNF -grammer is pretty helpful finding it, as is looking at the Parser source code. - -Now in the TreeWalker process we have to pick up this node and generate SQL -from it, which apprently is quite easy looking at the code in (7). Since -we don't know which type of AST Node the first and second Date expression -are we are just dispatching them back to the SQL Walker to generate SQL from -and then wrap our DATEDIFF function call around this output. - -Now registering this DateDiff FunctionNode with the ORM using: - - [php] - $config = new \Doctrine\ORM\Configuration(); - $config->addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff'); - -We can do fancy stuff like: - - [sql] - SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7 - -## Date Add - -Often useful it the ability to do some simple date calculations in your DQL query -using [MySql's DATE_ADD function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add). - -I'll skip the bla and show the code for this function: - - [php] - /** - * DateAddFunction ::= - * "DATE_ADD" "(" ArithmeticPrimary ", INTERVAL" ArithmeticPrimary Identifier ")" - */ - class DateAdd extends FunctionNode - { - public $firstDateExpression = null; - public $intervalExpression = null; - public $unit = null; - - public function parse(\Doctrine\ORM\Query\Parser $parser) - { - $parser->match(Lexer::T_IDENTIFIER); - $parser->match(Lexer::T_OPEN_PARENTHESIS); - - $this->firstDateExpression = $parser->ArithmeticPrimary(); - - $parser->match(Lexer::T_COMMA); - $parser->match(Lexer::T_IDENTIFIER); - - $this->intervalExpression = $parser->ArithmeticPrimary(); - - $parser->match(Lexer::T_IDENTIFIER); - - /* @var $lexer Lexer */ - $lexer = $parser->getLexer(); - $this->unit = $lexer->token['value']; - - $parser->match(Lexer::T_CLOSE_PARENTHESIS); - } - - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) - { - return 'DATE_ADD(' . - $this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' . - $this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit . - ')'; - } - } - -The only difference compared to the DATEDIFF here is, we additionally need the `Lexer` to access -the value of the `T_IDENTIFIER` token for the Date Interval unit, for example the MONTH in: - - [sql] - SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created - -The above method now only supports the specification using `INTERVAL`, to also -allow a real date in DATE_ADD we need to add some decision logic to the parsing -process (makes up for a nice excercise). - -Now as you see, the Parsing process doesn't catch all the possible SQL errors, -here we don't match for all the valid inputs for the interval unit. -However where necessary we rely on the database vendors SQL parser to show us further errors -in the parsing process, for example if the Unit would not be one of the supported values -by MySql. - -## Conclusion - -Now that you all know how you can implement vendor specific SQL functionalities in DQL, -we would be excited to see user extensions that add vendor specific function packages, -for example more math functions, XML + GIS Support, Hashing functions and so on. - -For 2.0 we will come with the current set of functions, however for a future -version we will re-evaluate if we can abstract even more vendor sql functions -and extend the DQL languages scope. - -Code for this Extension to DQL and other Doctrine Extensions can be found -[in my Github DoctrineExtensions repository](http://github.com/beberlei/DoctrineExtensions). \ No newline at end of file diff --git a/orm/cookbook/en/getting-started-xml-edition.txt b/orm/cookbook/en/getting-started-xml-edition.txt deleted file mode 100644 index b9a9632d1..000000000 --- a/orm/cookbook/en/getting-started-xml-edition.txt +++ /dev/null @@ -1,704 +0,0 @@ -Doctrine 2 is a project that aims to handle the persistence of the domain model in a non-interfering way. -The Data Mapper pattern is at the heart of this project, aiming for a complete separation of the domain/business logic -from the persistence in a relational database management system. The benefit of Doctrine for the programmer is the -possibility can focus soley on the business and worry about persistence only as a secondary task. This doesn't mean -persistence is not important to Doctrine 2, however it is our believe that there are considerable benefits for object-oriented -programming, if persistence and entities are kept perfectly seperated. - -## What are Entities? - -Entities are leightweight PHP Objects that don't need to extend any abstract base class or interface. -An entity class must not be final or contain final methods. Additionally it must not implement __clone -nor __wakeup or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). - -An entity contains persistable properties. A persistable property is an instance variable of the entity -that contains the data which is persisted and retrieved by Doctrine's data mapping capabilities. - -## An Example Model: Bug Tracker - -For this Getting Started Guide for Doctrine we will implement the Bug Tracker domain model from the [Zend_Db_Table](http://framework.zend.com/manual/en/zend.db.table.html) -documentation. Reading that documentat we can extract the requirements to be: - -* A Bugs has a description, creation date, status, reporter and engineer -* A bug can occour on different products (platforms) -* Products have a name. -* Bug Reporter and Engineers are both Users of the System. -* A user can create new bugs. -* The assigned engineer can close a bug. -* A user can see all his reported or assigned bugs. -* Bugs can be paginated through a list-view. - -> **WARNING** -> -> This tutorial is incrementally building up your Doctrine 2 knowledge and even lets you make some mistakes, to -> show some common pitfalls in mapping Entities to a database. Don't blindly copy-paste the examples here, it -> is not production ready without the additional comments and knowledge this tutorial teaches. - -## A first prototype - -A first simplified design for this domain model might look like the following set of classes: - - [php] - class Bug - { - public $id; - public $description; - public $created; - public $status; - public $products = array(); - public $reporter; - public $engineer; - } - class Product - { - public $id; - public $name; - } - class User - { - public $id; - public $name; - public $reportedBugs = array(); - public $assignedBugs = array(); - } - -> **WARNING** -> -> This is only a prototype, please don't use public properties with Doctrine 2 at all, -> the "Queries for Application Use-Cases" section shows you why. In combination with proxies -> public properties can make up for pretty nasty bugs. - -Because we will focus on the mapping aspect, no effort is being made to encapsulate the business logic in this example. -All peristable properties are public in visibility. We will soon see that this is not the best solution in combination -with Doctrine 2, one restriction that actually forces you to encapsulate your properties. For persistence Doctrine 2 -actually uses Reflection to access the values in all your entities properties. - -Many of the fields are single scalar values, for example the 3 ID fields of the entities, their names, description, -status and change dates. Doctrine 2 can easily handle these single values as can any other ORM. From a point of our -domain model they are ready to be used right now and we will see at a later stage how they are mapped to the database. - -There are also several references between objects in this domain model, whose semantics are discussed case by case at this point -to explain how Doctrine handles them. In general each OneToOne or ManyToOne Relation in the Database is replaced by an -instance of the related object in the domain model. Each OneToMany or ManyToMany Relation is replaced by a collection -of instances in the domain model. - -If you think this through carefully you realize Doctrine 2 will load up the complete database in memory if you access -one object. However by default Doctrine generates Lazy Load proxies of entities or collections of all the relations -that haven't been explicitly retrieved from the database yet. - -To be able to use lazyload with collections, simple PHP arrays have to be replaced by a generic collection -interface Doctrine\Common\Collections\Collection which tries to act as array as much as possible using ArrayAccess, -IteratorAggregate and Countable interfaces. The class \Doctrine\Common\Collections\ArrayCollection is the most simple -implementation of this interface. - -Now that we know this, we have to clear up our domain model to cope with the assumptions about related collections: - - [php] - use Doctrine\Common\Collections\ArrayCollection; - - class Bug - { - public $products = null; - - public function __construct() - { - $this->products = new ArrayCollection(); - } - } - - class User - { - public $reportedBugs = null; - public $assignedBugs = null; - - public function __construct() - { - $this->reportedBugs = new ArrayCollection(); - $this->assignedBugs = new ArrayCollection(); - } - } - -Whenever an entity is recreated from the database, an Collection implementation of the type -Doctrine\ORM\PersistantCollection is injected into your entity instead of an array. Compared -to the ArrayCollection this implementation helps the Doctrine ORM understand the changes that -have happend to the collection which are noteworthy for persistence. - -> **Warning** -> Lazy load proxies always contain an instance of Doctrine's EntityManager and all its dependencies. Therefore a var_dump() -> will possibly dump a very large recursive structure which is impossible to render and read. You have to use -> `Doctrine\Common\Util\Debug::dump()` to restrict the dumping to a human readable level. Additionally you should be aware -> that dumping the EntityManager to a Browser may take several minutes, and the Debug::dump() method just ignores any -> occurences of it in Proxy instances. - -Because we only work with collections for the references we must be careful to implement a bidirectional reference in -the domain model. The concept of owning or inverse side of a relation is central to this notion and should always -be kept in mind. The following assumptions are made about relations and have to be followed to be able to work with Doctrine 2. -These assumptions are not unique to Doctrine 2 but are best practices in handling database relations and Object-Relational Mapping. - -* Changes to Collections are saved or updated, when the entity on the *ownin*g side of the collection is saved or updated. -* Saving an Entity at the inverse side of a relation never triggers a persist operation to changes to the collection. -* In a one-to-one relation the entity holding the foreign key of the related entity on its own database table is *always* the owning side of the relation. -* In a many-to-many relation, both sides can be the owning side of the relation. However in a bi-directional many-tomany relation only one is allowed to be. -* In a many-to-one relation the Many-side is the owning side by default, because it holds the foreign key. -* The OneToMany side of a relation is inverse by default, since the foreign key is saved on the Many side. A OneToMany relation can only be the owning side, if its implemented using a ManyToMany relation with join table and restricting the one side to allow only UNIQUE values per database constraint. - -> **Important** -> -> Consistency of bi-directional references on the inverse side of a relation have to be managed in userland application code. -> Doctrine cannot magically update your collections to be consistent. - -In the case of Users and Bugs we have references back and forth to the assigned and reported bugs from a user, -making this relation bi-directional. We have to change the code to ensure consistency of the bi-directional reference: - - [php] - class Bug - { - protected $engineer; - protected $reporter; - - public function setEngineer($engineer) - { - $engineer->assignedToBug($this); - $this->engineer = $engineer; - } - - public function setReporter($reporter) - { - $reporter->addReportedBug($this); - $this->reporter = $reporter; - } - - public function getEngineer() - { - return $this->engineer; - } - - public function getReporter() - { - return $this->reporter; - } - } - class User - { - public function addReportedBug($bug) - { - $this->reportedBugs[] = $bug; - } - - public function assignedToBug($bug) - { - $this->assignedBugs[] = $bug; - } - } - -I chose to name the inverse methods in past-tense, which should indicate that the actual assigning has already taken -place and the methods are only used for ensuring consistency of the references. You can see from `User::addReportedBug()` -and `User::assignedToBug()` that using this method in userland alone would not add the Bug to the collection of the owning -side in Bug::$reporter or Bug::$engineer. Using these methods and calling Doctrine for persistence would not update -the collections representation in the database. - -Only using `Bug::setEngineer()` or `Bug::setReporter()` correctly saves the relation information. We also set both collection -instance variables to protected, however with PHP 5.3's new features Doctrine is still able to use Reflection to set and get values -from protected and private properties. - -The `Bug::$reporter` and `Bug::$engineer` properties are Many-To-One relations, which point to a User. In a normalized -relational model the foreign key is saved on the Bug's table, hence in our object-relation model the Bug is at the owning -side of the relation. You should always make sure that the use-cases of your domain model should drive which side -is an inverse or owning one in your Doctrine mapping. In our example, whenever a new bug is saved or an engineer is assigned -to the bug, we don't want to update the User to persist the reference, but the Bug. -This is the case with the Bug being at the owning side of the relation. - -Bugs reference Products by a uni-directional ManyToMany relation in the database that points from from Bugs to Products. - - [php] - class Bug - { - protected $products = null; // Set protected for encapsulation - - public function assignToProduct($product) - { - $this->products[] = $product; - } - - public function getProducts() - { - return $this->products; - } - } - -We are now finished with the domain model given the requirements. From the simple model with public properties only -we had to do quite some work to get to a model where we encapsulated the references between the objects to make sure -we don't break its consistent state when using Doctrine. - -However up to now the assumptions Doctrine imposed on our business objects have not restricting us much in our domain -modelling capabilities. Actually we would have encapsulated access to all the properties anyways by using -object-oriented best-practices. - -## Metadata Mappings for our Entities - -Up to now we have only implemented our Entites as Data-Structures without actually telling Doctrine how to persist -them in the database. If perfect in-memory databases would exist, we could now finish the application using these entities -by implementing code to fullfil all the requirements. However the world isn't perfect and we have to persist our -entities in some storage to make sure we don't loose their state. Doctrine currently serves Relational Database Management Systems. -In the future we are thinking to support NoSQL vendors like CouchDb or MongoDb, however this is still far in the future. - -The next step for persistance with Doctrine is to describe the structure of our domain model entities to Doctrine -using a metadata language. The metadata language describes how entities, their properties and references should be -persisted and what constraints should be applied to them. - -Metadata for entities are loaded using a `Doctrine\ORM\Mapping\Driver\Driver` implementation and Doctrine 2 already comes -with XML, YAML and Annotations Drivers. In this Getting Started Guide I will use the XML Mapping Driver. I think XML -beats YAML because of schema validation, and my favorite IDE netbeans offers me auto-completion for the XML mapping files -which is awesome to work with and you don't have to look up all the different metadata mapping commands all the time. - -Since we haven't namespaced our three entities, we have to implement three mapping files called Bug.dcm.xml, -Product.dcm.xml and User.dcm.xml and put them into a distinct folder for mapping configurations. - -The first discussed definition will be for the Product, since it is the most simple one: - - [xml] - - - - - - - - - - - - -The toplevel `entity` definition tag specifies information about the class and table-name. The -primitive type `Product::$name` is defined as `field` attributes. The Id property is defined with the `id` tag. -The id has a `generator` tag nested inside which defines that the primary key generation mechanism -automatically uses the database platforms native id generation strategy, for example AUTO INCREMENT -in the case of MySql or Sequences in the case of PostgreSql and Oracle. - -We then go on specifying the definition of a Bug: - - [xml] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Here again we have the entity, id and primitive type definitions. -The column names are used from the Zend_Db_Table examples and have different names than the properties -on the Bug class. Additionally for the "created" field it is specified that it is of the Type "DATETIME", -which translates the YYYY-mm-dd HH:mm:ss Database format into a PHP DateTime instance and back. - -After the field definitions the two qualified references to the user entity are defined. They are created by -the `many-to-one` tag. The class name of the related entity has to be specified with the `target-entity` -attribute, which is enough information for the database mapper to access the foreign-table. The -`join-column` tags are used to specifiy how the foreign and referend columns are named, an information -Doctrine needs to construct joins between those two entities correctly. - -The last missing property is the `Bug::$products` collection. It holds all products where the specific -bug is occouring in. Again you have to define the `target-entity` and `field` attributes on the `many-to-many` -tag. Furthermore you have to specifiy the details of the many-to-many join-table and its foreign key columns. -The definition is rather complex, however relying on the XML auto-completion I got it working easily, although -I forget the schema details all the time. - -The last missing definition is that of the User entity: - - [xml] - - - - - - - - - - - - - - - - -Here are some new things to mention about the `one-to-many` tags. Remember that we discussed about -the inverse and owning side. Now both reportedBugs and assignedBugs are inverse relations, -which means the join details have already been defined on the owning side. Therefore we only -have to specify the property on the Bug class that holds the owning sides. - -This example has a fair overview of the most basic features of the metadata definition language. - -## Obtaining the EntityManager - -Doctrine's public interface is the EntityManager, it provides the access point to the complete -lifecycle management of your entities and transforms entities from and back to persistence. You -have to configure and create it to use your entities with Doctrine 2. I will show the configuration -steps and then discuss them step by step: - - [php] - // Setup Autoloader (1) - require '/path/to/lib/Doctrine/Common/ClassLoader.php'; - $loader = new Doctrine\Common\ClassLoader("Doctrine", '/path/to/Doctrine/trunk/lib/'); - $loader->register(); - - $config = new Doctrine\ORM\Configuration(); // (2) - - // Proxy Configuration (3) - $config->setProxyDir(__DIR__.'/lib/MyProject/Proxies'); - $config->setProxyNamespace('MyProject\Proxies'); - $config->setAutoGenerateProxyClasses((APPLICATION_ENV == "development")); - - // Mapping Configuration (4) - $driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings"); - $config->setMetadataDriverImpl($driverImpl); - - // Caching Configuration (5) - if (APPLICATION_ENV == "develoment") { - $cache = new \Doctrine\Common\Cache\ArayCache(); - } else { - $cache = new \Doctrine\Common\Cache\ApcCache(); - } - $config->setMetadataCacheImpl($cache); - $config->setQueryCacheImpl($cache); - - // database configuration parameters (6) - $conn = array( - 'driver' => 'pdo_sqlite', - 'path' => __DIR__ . '/db.sqlite', - ); - - // obtaining the entity manager (7) - $evm = new Doctrine\Common\EventManager() - $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config, $evm); - -The first block sets up the autoloading capabilities of Doctrine. I am registering the Doctrine -namespace to the given path. To add your own namespace you can instantiate another `CloassLoader` -with different namespace and path arguments. There is no requirement to use the Doctrine `ClassLoader` -for your autoloading needs, you can use whatever suits you best. - -The second block contains of the instantiation of the ORM Configuration object. Besides the -configuration shown in the next blocks there are several others with are all explained -in the [Configuration section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). - -The Proxy Configuration is a required block for your application, you have to specifiy where -Doctrine writes the PHP code for Proxy Generation. Proxies are children of your entities generated -by Doctrine to allow for type-safe lazy loading. We will see in a later chapter how exactly this works. -Besides the path to the proxies we also specifiy which namespace they will reside under aswell as -a flag `autoGenerateProxyClasses` indicating that proxies should be re-generated on each request, -which is recommended for development. In production this should be prevented at all costs, -the proxy class generation can be quite costly. - -The fourth block contains the mapping driver details. We will use XML Mapping in this example, so -we configure the `XmlDriver` instance with a path to mappings configuration folder where we put -the Bug.dcm.xml, Product.dcm.xml and User.dcm.xml. - -In the 5th block the caching configuration is set. In production we use caching only on a per request-basis -using the ArrayCache. In production it is literally required to use Apc, Memcache or XCache to get the full -speed out of Doctrine. Internally Doctrine uses caching heavily for the Metadata and DQL Query Language -so make sure you use a caching mechanism. - -The 6th block shows the configuration options required to connect to a database, in my case a file-based -sqlite database. All the configuration options for all the shipped drivers are given in the [DBAL Configuration -section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/dbal). - -The last block shows how the `EntityManager` is obtained from a factory method, Here we also pass -in an `EventManager` instance which is optional. However using the EventManager you can hook in to the lifecycle -of entities, which is a common use-case, so you know how to configure it already. - -## Generating the Database Schema - -Now that we have defined the Metadata Mappings and bootstrapped the EntityManager -we want to generate the relational database schema from it. -Doctrine has a Command-Line-Interface that allows you to access the SchemaTool, a component that generates -the required tables to work with the metadata. - -For the commandline tool to work a cli-config.php file has to be present in the project root directry, -where you will execute the doctrine command. Its a fairly simple file: - - [php] - $cliConfig = new Doctrine\Common\Cli\Configuration(); - $cliConfig->setAttribute('em', $entityManager); - -You can then use your favorite console tool to call: - - [console] - doctrine@my-desktop> cd myproject/ - doctrine@my-desktop> doctrine orm:schema-tool --create - -During the development you probably need to re-create the database several times when changing the Entity -metadata. You can then either re-create the database, or use the update functionality: - - [console] - doctrine@my-desktop> doctrine orm:schema-tool --re-create - doctrine@my-desktop> doctrine orm:schema-tool --update - -The updating of databases uses a Diff Algorithm for a given Database Schema, a cornerstone of the `Doctrine\DBAL` -package, which can even be used without the Doctrine ORM package. However its not available in SQLite since -it does not support ALTER TABLE. - -## Writing Entities into the Database - -Having created the schema we can now start and save entities in the database. For starters we need a create user use-case: - - [php] - $newUsername = "beberlei"; - - $user = new User(); - $user->name = $newUsername; - - $entityManager->persist($user); - $entityManager->flush(); - -Having a user, he can create products: - - [php] - $newProductName = "My Product"; - - $product = new Product(); - $product->name = $newProductName; - - $entityManager->persist($product); - $entityManager->flush(); - -So what is happening in those two snippets? In both examples the class creation is pretty standard, the interesting -bits are the communication with the `EntityManager`. To notify the EntityManager that a new entity should be inserted -into the database you have to call `persist()`. However the EntityManager does not act on this, its merely notified. -You have to explicitly call `flush()` to have the EntityManager write those two entities to the database. - -You might wonder why does this distinction between persist notification and flush exist? Doctrine 2 uses the -UnitOfWork pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one single fast transaction, which -is executed when flushing. This way the write-performance is unbelievable fast. Also in more complex scenarios -than those two before you can request updates on many different entities and all flush them at once. - -Doctrine's UnitOfWork even detects entities that have been retrieved from the database and changed when calling -flush, so that you only have to keep track of those entities that are new or to be removed and pass them to -`EntityManager#persist()` and `EntityManager#remove()` respectively. - -We are now getting to the "Create a New Bug" requirement and the code for this scenario may look like this: - - [php] - $reporter = $entityManager->find("User", $theReporterId); - $engineer = $entityManager->find("User", $theDefaultEngineerId); - - $bug = new Bug(); - $bug->description = "Something does not work!"; - $bug->created = new DateTime("now"); - $bug->status = "NEW"; - - foreach ($productIds AS $productId) { - $product = $entityManager->find("Product", $productId); - $bug->assignToProduct($product); - } - - $bug->setReporter($reporter); - $bug->setEngineer($engineer); - - $entityManager->persist($bug); - $entityManager->flush(); - - echo "Your new Bug Id: ".$bug->id."\n"; - -This is the first contact with the read API of the EntityManager, showing that a call to -`EntityManager#find($name, $id)` returns a single instance of an entity queried by primary key. -Besides this we see the persist + flush pattern again to save the Bug into the database. - -See how simple relating Bug, Reporter, Engineer and Products is done by using the discussed methods -in the "A first prototype" section. The UnitOfWork will detect this relations when flush -is called and relate them in the database appropriately. - -## Queries for Application Use-Cases - -Using the previous examples we can fill up the database quite a bit, however we now need to discuss how to query the underlying -mapper for the required view representations. When opening the application, bugs can be paginated through a list-view, which is the first -read-only use-case: - - [php] - $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC"; - - $query = $entityManager->createQuery($dql); - $query->setMaxResults(30); - $bugs = $query->getResult(); - - foreach($bugs AS $bug) { - echo $bug->description." - ".$bug->created->format('d.m.Y')."\n"; - echo " Reported by: ".$bug->getReporter()->name."\n"; - echo " Assigned to: ".$bug->getEngineer()->name."\n"; - foreach($bug->getProducts() AS $product) { - echo " Platform: ".$product->name."\n"; - } - echo "\n"; - } - -The DQL Query in this example fetches the 30 most recent bugs with their respective engineer and reporter -in one single SQL statement. The console output of this script is then: - - Something does not work! - 02.04.2010 - Reported by: beberlei - Assigned to: beberlei - Platform: My Product - -> **NOTE** -> -> **Dql is not Sql** -> -> You may wonder why we start writing SQL at the beginning of this use-case. Don't we use an ORM to get rid -> of all the endless hand-writing of SQL? Doctrine introduces DQL which is best described -> as **object-query-language** and is a dialect of [OQL](http://en.wikipedia.org/wiki/Object_Query_Language) and -> similar to [HQL](http://www.hibernate.org) or [JPQL](http://en.wikipedia.org/wiki/Java_Persistence_Query_Language). -> It does not know the concept of columns and tables, but only those -> of Entity-Class and property. Using the Metadata we defined before it allows for very short distinctive -> and powerful queries. -> -> An important reason why DQL is favourable to the Query API of most ORMs is its similarity to SQL. The DQL language -> allows query constructs that most ORMs don't, GROUP BY even with HAVING, Subselects, Fetch-Joins of nested -> classes, mixed results with entities and scalar data such as COUNT() results and much more. Using -> DQL you should seldom come to the point where you want to throw your ORM into the dumpster, because it -> doesn't support some the more powerful SQL concepts. -> -> Besides handwriting DQL you can however also use the `QueryBuilder` retrieved by calling `$entityManager->createQueryBuilder()` -> which is a Query Object around the DQL language. -> -> As a last resort you can however also use Native SQL and a description of the result set to retrieve -> entities from the database. DQL boils down to a Native SQL statement and a `ResultSetMapping` instance itself. -> Using Native SQL you could even use stored procedures for data retrieval, or make use of advanced non-portable -> database queries like PostgreSql's recursive queries. - -The next Use-Case is displaying a Bug by primary key. This could be done using DQL as in the previous example with a where clause, -however there is a convenience method on the Entity Manager that handles loading by primary key, which we have already -seen in the write scenarios: - - [php] - $bug = $entityManager->find("Bug", (int)$theBugId); - -However we will soon see another problem with our entities using this approach. Try displaying the engineer's name: - - [php] - echo "Bug: ".$bug->description."\n"; - echo "Engineer: ".$bug->getEngineer()->name."\n"; - -It will be null! What is happening? It worked in the previous example, so it can't be a problem with the persistance -code of Doctrine. You walked in the public property trap. Since we only retrieved the bug by primary key both the -engineer and reporter are not immediately loaded from the database but are replaced by LazyLoading proxies. Sample -code of this proxy generated code can be found in the specified Proxy Directory, it looks like: - - [php] - namespace MyProject\Proxies; - - /** - * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. - */ - class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy - { - // .. lazy load code here - - public function addReportedBug($bug) - { - $this->_load(); - return parent::addReportedBug($bug); - } - - public function assignedToBug($bug) - { - $this->_load(); - return parent::assignedToBug($bug); - } - } - -See how upon each method call the proxy is lazily loaded from the database? Using public properties however -we never call a method and Doctrine has no way to hook into the PHP Engine to detect this access and trigger -the lazy load. We need to revise our entities, make all the properties private or protected and add getters -and setters to get a working example: - - [php] - echo "Bug: ".$bug->getDescription()."\n"; - echo "Engineer: ".$bug->getEngineer()->getName()."\n"; - - /** - Bug: Something does not work! - Engineer: beberlei - */ - -For the next use-case we want to retrieve the dashboard view, a list of all open bugs the user reported or -was assigned to. This will be achieved using DQL again, this time with some WHERE clauses and usage of bound parameters: - - [php] - $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ". - "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC"; - - $myBugs = $entityManager->createQuery($dql) - ->setParameter(1, $theUserId) - ->setMaxResults(15) - ->getResult(); - - foreach ($myBugs AS $bug) { - echo $bug->getDescription()."\n"; - } - -That is it for the read-scenarios of this example, we will continue with the last missing bit, engineers -being able to close a bug. - -## Updating Entities - -There is a single use-case missing from the requirements, Engineers should be able to close a bug. This looks like: - - [php] - $bug = $entityManager->find("Bug", (int)$theBugId); - $bug->close(); - - $entityManager->flush(); - -When retrieving the Bug from the database it is inserted into the IdentityMap inside the UnitOfWork of Doctrine. -This means your Bug with exactly this id can only exist once during the whole request no matter how often you -call `EntityManager#find()`. It even detects entities that are hydrated using DQL and are already present in -the Identity Map. - -When flush is called the EntityManager loops over all the entities in the identity map and performs a comparison -between the values originally retrieved from the database and those values the entity currently has. If at -least one of these properties is different the entity is scheduled for an UPDATE against the database. -Only the changed columns are updated, which offers a pretty good performance improvement compared to updating -all the properties. - -This tutorial is over here, I hope you had fun. Additional content will be added to this tutorial -incrementally, topics will include: - - * Entity Repositories - * More on Association Mappings - * Lifecycle Events triggered in the UnitOfWork - * Ordering of Collections - -Additional details on all the topics discussed here can be found in the respective manual chapters. diff --git a/orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt b/orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt deleted file mode 100644 index 4f295420d..000000000 --- a/orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt +++ /dev/null @@ -1,89 +0,0 @@ - -This recipe will show you how to implement ArrayAccess for your domain objects in order to allow more uniform access, for example in templates. In these examples we will implement ArrayAccess on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. - -++ Option 1 - -In this implementation we will make use of PHPs highly dynamic nature to dynamically access properties of a subtype in a supertype at runtime. Note that this implementation has 2 main caveats: - -* It will not work with private fields -* It will not go through any getters/setters - -- - - [php] - abstract class DomainObject implements ArrayAccess - { - public function offsetExists($offset) { - return isset($this->$offset); - } - - public function offsetSet($offset, $value) { - $this->$offset = $value; - } - - public function offsetGet($offset) { - return $this->$offset; - } - - public function offsetUnset($offset) { - $this->$offset = null; - } - } - - -++ Option 2 - -In this implementation we will dynamically invoke getters/setters. Again we use PHPs dynamic nature to invoke methods on a subtype from a supertype at runtime. This implementation has the following caveats: - -* It relies on a naming convention -* The semantics of offsetExists can differ -* offsetUnset will not work with typehinted setters - -- - - [php] - abstract class DomainObject implements ArrayAccess - { - public function offsetExists($offset) { - // In this example we say that exists means it is not null - $value = $this->{"get$offset"}(); - return $value !== null; - } - - public function offsetSet($offset, $value) { - $this->{"set$offset"}($value); - } - - public function offsetGet($offset) { - return $this->{"get$offset"}(); - } - - public function offsetUnset($offset) { - $this->{"set$offset"}(null); - } - } - -++ Read-only - -You can slightly tweak option 1 or option 2 in order to make array access read-only. This will also circumvent some of the caveats of each option. Simply make offsetSet and offsetUnset throw an exception (i.e. BadMethodCallException). - - [php] - abstract class DomainObject implements ArrayAccess - { - public function offsetExists($offset) { - // option 1 or option 2 - } - - public function offsetSet($offset, $value) { - throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!"); - } - - public function offsetGet($offset) { - // option 1 or option 2 - } - - public function offsetUnset($offset) { - throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!"); - } - } - diff --git a/orm/cookbook/en/implementing-the-notify-changetracking-policy.txt b/orm/cookbook/en/implementing-the-notify-changetracking-policy.txt deleted file mode 100644 index 025375487..000000000 --- a/orm/cookbook/en/implementing-the-notify-changetracking-policy.txt +++ /dev/null @@ -1,48 +0,0 @@ - -The NOTIFY changetracking policy is the most effective changetracking policy provided by Doctrine but it requires some boilerplate code. This recipe will show you how this boilerplate code should look like. We will implement it on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. - -++ Implementing NotifyPropertyChanged - -The NOTIFY 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. - - [php] - use Doctrine\Common\NotifyPropertyChanged, - Doctrine\Common\PropertyChangedListener; - - abstract class DomainObject implements NotifyPropertyChanged - { - private $_listeners = array(); - - public function addPropertyChangedListener(PropertyChangedListener $listener) { - $this->_listeners[] = $listener; - } - - /** Notifies listeners of a change. */ - protected function _onPropertyChanged($propName, $oldValue, $newValue) { - if ($this->_listeners) { - foreach ($this->_listeners as $listener) { - $listener->propertyChanged($this, $propName, $oldValue, $newValue); - } - } - } - } - -Then, in each property setter of concrete, derived domain classes, you need to invoke _onPropertyChanged as follows to notify listeners: - - [php] - // Mapping not shown, either in annotations, xml or yaml as usual - class MyEntity extends DomainObject - { - private $data; - // ... other fields as usual - - public function setData($data) { - if ($data != $this->data) { // check: is it actually modified? - $this->_onPropertyChanged('data', $this->data, $data); - $this->data = $data; - } - } - } - -The check whether the new value is different from the old one is not mandatory but recommended. That way you can avoid unnecessary updates and also have full control over when you consider a property changed. - diff --git a/orm/cookbook/en/implementing-wakeup-or-clone.txt b/orm/cookbook/en/implementing-wakeup-or-clone.txt deleted file mode 100644 index efdefa7c3..000000000 --- a/orm/cookbook/en/implementing-wakeup-or-clone.txt +++ /dev/null @@ -1,63 +0,0 @@ - -As explained in the [restrictions for entity classes in the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities), -it is usually not allowed for an entity to implement `__wakeup` or `__clone`, because Doctrine -makes special use of them. However, it is quite easy to make use of these methods in a safe way -by guarding the custom wakeup or clone code with an entity identity check, as demonstrated in the following sections. - -++ Safely implementing __wakeup - -To safely implement `__wakeup`, simply enclose your implementation code in an identity check -as follows: - - [php] - class MyEntity - { - private $id; // This is the identifier of the entity. - //... - - public function __wakeup() - { - // If the entity has an identity, proceed as normal. - if ($this->id) { - // ... Your code here as normal ... - } - // otherwise do nothing, do NOT throw an exception! - } - - //... - } - -++ Safely implementing __clone - -Safely implementing `__clone` is pretty much the same: - - [php] - class MyEntity - { - private $id; // This is the identifier of the entity. - //... - - public function __clone() - { - // If the entity has an identity, proceed as normal. - if ($this->id) { - // ... Your code here as normal ... - } - // otherwise do nothing, do NOT throw an exception! - } - - //... - } - -++ Summary - -As you have seen, it is quite easy to safely make use of `__wakeup` and `__clone` in your entities -without adding any really Doctrine-specific or Doctrine-dependant code. - -These implementations are possible and safe because when Doctrine invokes these methods, -the entities never have an identity (yet). Furthermore, it is possibly a good idea to check -for the identity in your code anyway, since it's rarely the case that you want to unserialize -or clone an entity with no identity. - - - diff --git a/orm/cookbook/en/integrating-with-codeigniter.txt b/orm/cookbook/en/integrating-with-codeigniter.txt deleted file mode 100644 index 23165176a..000000000 --- a/orm/cookbook/en/integrating-with-codeigniter.txt +++ /dev/null @@ -1,104 +0,0 @@ -This is recipe for using Doctrine 2 in your [CodeIgniter](http://www.codeigniter.com) framework. - -Here is how to set it up: - -Make a CodeIgniter library that is both a wrapper and a bootstrap for Doctrine 2. - -++ Setting up the file structure - -Here are the steps: - -* Add a php file to your system/application/libraries folder called Doctrine.php. This is going to be your wrapper/bootstrap for the D2 entity manager. -* Put the Doctrine folder (the one that contains Common, DBAL, and ORM) inside that same libraries folder. -* Your system/application/libraries folder now looks like this: - - system/applications/libraries - -Doctrine - -Doctrine.php - -index.html - -* If you want, open your config/autoload.php file and autoload your Doctrine library. - - [php] - $autoload['libraries'] = array('doctrine'); - -++ Creating your Doctrine CodeIgniter library - -Now, here is what your Doctrine.php file should look like. Customize it to your needs. - - [php] - use Doctrine\Common\ClassLoader, - Doctrine\ORM\Configuration, - Doctrine\ORM\EntityManager, - Doctrine\Common\Cache\ArrayCache, - Doctrine\DBAL\Logging\EchoSqlLogger; - - class Doctrine { - - public $em = null; - - public function __construct() - { - // load database configuration from CodeIgniter - require_once APPPATH.'config/database.php'; - - // Set up class loading. You could use different autoloaders, provided by your favorite framework, - // if you want to. - require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php'; - - $doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries'); - $doctrineClassLoader->register(); - $entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" )); - $entitiesClassLoader->register(); - $proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies'); - $proxiesClassLoader->register(); - - // Set up caches - $config = new Configuration; - $cache = new ArrayCache; - $config->setMetadataCacheImpl($cache); - $config->setQueryCacheImpl($cache); - - // Proxy configuration - $config->setProxyDir(APPPATH.'/models/proxies'); - $config->setProxyNamespace('Proxies'); - - // Set up logger - $logger = new EchoSqlLogger; - $config->setSqlLogger($logger); - - $config->setAutoGenerateProxyClasses( TRUE ); - - // Database connection information - $connectionOptions = array( - 'driver' => 'pdo_mysql', - 'user' => $db['default']['username'], - 'password' => $db['default']['password'], - 'host' => $db['default']['hostname'], - 'dbname' => $db['default']['database'] - ); - - // Create EntityManager - $this->em = EntityManager::create($connectionOptions, $config); - } - } - -Please note that this is a development configuration; for a production system you'll want to use a real caching system like APC, get rid of EchoSqlLogger, and turn off autoGenerateProxyClasses. - -For more details, consult the [Doctrine 2 Configuration documentation](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). - -++ Now to use it - -Whenever you need a reference to the entity manager inside one of your controllers, views, or models you can do this: - - [php] - $em = $this->doctrine->em; - -That's all there is to it. Once you get the reference to your EntityManager do your Doctrine 2.0 voodoo as normal. - -Note: If you do not choose to autoload the Doctrine library, you will need to put this line before you get a reference to it: - - [php] - $this->load->library('doctrine'); - -Good luck! diff --git a/orm/cookbook/en/validation-of-entities.txt b/orm/cookbook/en/validation-of-entities.txt deleted file mode 100644 index 739a608d2..000000000 --- a/orm/cookbook/en/validation-of-entities.txt +++ /dev/null @@ -1,114 +0,0 @@ -Doctrine 2 does not ship with any internal validators, the reason being that -we think all the frameworks out there already ship with quite decents ones that can be integrated -into your Domain easily. What we offer are hooks to execute any kind of validation. - -> **Note** -> You don't need to validate your entities in the lifecycle events. Its only -> one of many options. Of course you can also perform validations in value setters -> or any other method of your entities that are used in your code. - -Entities can register lifecycle evnt methods with Doctrine that are called on -different occasions. For validation we would need to hook into the -events called before persisting and updating. Even though we don't support -validation out of the box, the implementation is even simpler than in Doctrine 1 -and you will get the additional benefit of being able to re-use your validation -in any other part of your domain. - -Say we have an `Order` with several `OrderLine` instances. We never want to -allow any customer to order for a larger sum than he is allowed to: - - [php] - class Order - { - public function assertCustomerAllowedBuying() - { - $orderLimit = $this->customer->getOrderLimit(); - - $amount = 0; - foreach ($this->orderLines AS $line) { - $amount += $line->getAmount(); - } - - if ($amount > $orderLimit) { - throw new CustomerOrderLimitExceededException(); - } - } - } - -Now this is some pretty important piece of business logic in your code, enforcing -it at any time is important so that customers with a unknown reputation don't -owe your business too much money. - -We can enforce this constraint in any of the metadata drivers. First Annotations: - - [php] - /** - * @Entity - * @HasLifecycleCallbacks - */ - class Order - { - /** - * @PrePersist @PreUpdate - */ - public function assertCustomerAllowedBuying() {} - } - -In XML Mappings: - - [xml] - - - - - - - - - -YAML needs some little change yet, to allow multiple lifecycle events for one method, -this will happen before Beta 1 though. - -Now validation is performed whenever you call `EntityManager#persist($order)` -or when you call `EntityManager#flush()` and an order is about to be updated. -Any Exception that happens in the lifecycle callbacks will be catched by the -EntityManager and the current transaction is rolled back. - -Of course you can do any type of primitive checks, not null, email-validation, string size, -integer and date ranges in your validation callbacks. - - [php] - class Order - { - /** - * @PrePersist @PreUpdate - */ - public function validate() - { - if (!($this->plannedShipDate instanceof DateTime)) { - throw new ValidateException(); - } - - if ($this->plannedShipDate->format('U') < time()) { - throw new ValidateException(); - } - - if ($this->customer == null) { - throw new OrderRequiresCustomerException(); - } - } - } - -What is nice about lifecycle events is, you can also re-use the methods at other places -in your domain, for example in combination with your form library. -Additionally there is no limitation in the number of methods you register -on one particular event, i.e. you can register multiple methods for validation in "PrePersist" -or "PreUpdate" or mix and share them in any combinations between those two events. - -There is no limit to what you can and can't validate in "PrePersist" and "PreUpdate" aslong as -you don't create new entity instances. This was already discussed in the previous -blog post on the Versionable extension, which requires another type of event called "onFlush". - -Further readings: - -* [Doctrine 2 Manual: Events](http://www.doctrine-project.org/documentation/manual/2_0/en/events#lifecycle-events) \ No newline at end of file diff --git a/orm/manual/en.txt b/orm/manual/en.txt deleted file mode 100644 index b17808194..000000000 --- a/orm/manual/en.txt +++ /dev/null @@ -1,20 +0,0 @@ -+ Introduction -+ Architecture -+ Configuration -+ Basic Mapping -+ Association Mapping -+ Inheritance Mapping -+ Working with objects -+ Transactions and Concurrency -+ Events -+ Batch processing -+ DQL (Doctrine Query Language) -+ Query Builder -+ Native SQL -+ XML Mapping -+ YAML Mapping -+ Annotations Reference -+ Caching -+ Improving Performance -+ Tools -+ Best Practices \ No newline at end of file diff --git a/orm/manual/en/annotations-reference.txt b/orm/manual/en/annotations-reference.txt deleted file mode 100644 index cf5eece3b..000000000 --- a/orm/manual/en/annotations-reference.txt +++ /dev/null @@ -1,557 +0,0 @@ -In this chapter a reference of every Doctrine 2 Annotation is given with short explanations on their context and usage. - -++ Index - -* [@Column](#ann_column) -* [@ChangeTrackingPolicy](#ann_changetrackingpolicy) -* [@DiscriminatorColumn](#ann_discriminatorcolumn) -* [@DiscriminatorMap](#ann_discriminatormap) -* [@Entity](#ann_entity) -* [@GeneratedValue](#ann_generatedvalue) -* [@HasLifecycleCallbacks](#ann_haslifecyclecallbacks) -* [@Index](#ann_indexes) -* [@Id](#ann_id) -* [@InheritanceType](#ann_inheritancetype) -* [@JoinColumn](#ann_joincolumn) -* [@JoinTable](#ann_jointable) -* [@ManyToOne](#ann_manytoone) -* [@ManyToMany](#ann_manytomany) -* [@MappedSuperclass](#ann_mappedsuperclass) -* [@OneToOne](#ann_onetoone) -* [@OneToMany](#ann_onetomany) -* [@OrderBy](#ann_orderby) -* [@PostLoad](#ann_postload) -* [@PostPersist](#ann_postpersist) -* [@PostRemove](#ann_postremove) -* [@PostUpdate](#ann_postupdate) -* [@PrePersist](#ann_prepersist) -* [@PreRemove](#ann_preremove) -* [@PreUpdate](#ann_preupdate) -* [@SequenceGenerator](#ann_sequencegenerator) -* [@Table](#ann_table) -* [@UniqueConstraint](#ann_uniqueconstraint) -* [@Version](#ann_version) - -++ Reference - - -+++ @Column - -Marks an annotated instance variable as "persistent". It has to be inside the instance variables PHP DocBlock comment. -Any value hold inside this variable will be saved to and loaded from the database as part of the lifecycle of the instance variables entity-class. - -Required attributes: - -* type - Name of the Doctrine Type which is converted between PHP and Database representation. - -Optional attributes: - -* name - By default the property name is used for the database column name also, however the 'name' attribute allows you to determine the column name. -* length - Used by the "string" type to determine its maximum length in the database. Doctrine does not validate the length of a string values for you. -* precision - The precision for a decimal (exact numeric) column (Applies only for decimal column) -* scale - The scale for a decimal (exact numeric) column (Applies only for decimal column) -* unique - Boolean value to determine if the value of the column should be unique accross all rows of the underlying entities table. -* nullable - Determines if NULL values allowed for this column. -* columnDefinition - DDL SQL snippet that starts after the column name and specificies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. However you should make careful use of this feature and the consequences. Additionally you should remember that the "type" attribute still handles the conversion between PHP and Database values. If you use this attribute on a column that is used for joins between tables you should also take a look at [@JoinColumn](#ann_joincolumn). - -Examples: - - /** - * @Column(type="string", length=32, unique=true, nullable=false) - */ - protected $username; - - /** - * @Column(type="string", columnDefinition="CHAR(2) NOT NULL") - */ - protected $country; - - /** - * @Column(type="decimal", precision=2, scale=1) - */ - protected $height; - - -+++ @ChangeTrackingPolicy - -The Change Tracking Policy annotation allows to specify how the Doctrine 2 UnitOfWork should detect changes -in properties of entities during flush. By default each entity is checked according to a deferred implict -strategy, which means upon flush UnitOfWork compares all the properties of an entity to a previously stored -snapshot. This works out of the box, however you might want to tweak the flush performance where using -another change tracking policy is an interesting option. - -The [details on all the available change tracking policies](/../configuration#change-tracking-policies) -can be found in the configuration section. - -Example: - - /** - * @Entity - * @ChangeTrackingPolicy("DEFERRED_IMPLICIT") - * @ChangeTrackingPolicy("DEFERRED_EXPLICIT") - * @ChangeTrackingPolicy("NOTIFY") - */ - class User {} - - -+++ @DiscrimnatorColumn - -This annotation is a required annotation for the topmost/super class of an inheritance hierachy. It specifies -the details of the column which saves the name of the class, which the entity is actually instantiated as. - -Required attributes: - -* name - The column name of the discriminator. This name is also used during Array hydration as key to specify the class-name. - -Optional attributes: - -* type - By default this is string. -* length - By default this is 255. - - -+++ @DiscriminatorMap - -The discrimnator map is a required annotation on the top-most/super class in an inheritance hierachy. It takes -an array as only argument which defines which class should be saved under which name in the database. Keys -are the database value and values are the classes, either as fully- or as unqualified class names depending -if the classes are in the namespace or not. - - /** - * @Entity - * @InheritanceType("JOINED") - * @DiscriminatorColumn(name="discr", type="string") - * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) - */ - class Person - { - // ... - } - - - -+++ @Entity - -Required annotation to mark a PHP class as Entity. Doctrine manages the persistence of all classes marked as entity. - -Optional attributes: - -* repositoryClass - Specifies the FQCN of a subclass of the Doctrine\ORM\EntityRepository. Use of repositories for entites is encouraged to keep specialized DQL and SQL operations separated from the Model/Domain Layer. - -Example: - - /** - * @Entity(repositoryClass="MyProject\UserRepository") - */ - class User - { - //... - } - - -+++ @GeneratedValue - -Specifies which strategy is used for identifier generation for an instance variable which is annotated by [@Id](#ann_id). -This annotation is optional and only has meaning when used in conjunction with @Id. - -If this annotation is not specified with @Id the NONE strategy is used as default. - -Required attributes: - -* strategy - Set the name of the identifier generation strategy. Valid values are AUTO, SEQUENCE, TABLE, IDENTITY and NONE. - -Example: - - /** - * @Id - * @Column(type="integer") - * @generatedValue(strategy="IDENTITY") - */ - protected $id = null; - - -+++ @HasLifecycleCallbacks - -Annotation which has to be set on the entity-class PHP DocBlock to notify Doctrine that this entity has entity life-cycle -callback annotations set on at least one of its methods. Using @PostLoad, @PrePersist, @PostPersist, @PreRemove, @PostRemove, -@PreUpdate or @PostUpdate without this marker annotation will make Doctrine ignore the callbacks. - -Example: - - /** - * @Entity - * @HasLifecycleCallbacks - */ - class User - { - /** - * @PostPersist - */ - public function sendOptinMail() {} - } - - -+++ @Index - -Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the -SchemaTool to generate a database index on the specified table columns. It only has meaning in the SchemaTool -schema generation context. - -Required attributes: - -* name - Name of the Index -* columns - Array of columns. - -Example: - - /** - * @Entity - * @Table(name="ecommerce_products",indexes={@index(name="search_idx", columns={"name", "email"})}) - */ - class ECommerceProduct - { - } - - -+++ @Id - -The annotated instance variable will be marked as entity identifier, the primary key in the database. -This annotation is a marker only and has no required or optional attributes. For entites that have multiple -identifier columns each column has to be marked with @Id. - -Example: - - /** - * @Id - * @Column(type="integer") - */ - protected $id = null; - - -+++ @InheritanceType - -In an inheritance hierachy you have to use this annotation on the topmost/super class to define which -strategy should be used for inheritance. Currently Single Table and Class Table Inheritance are supported. - -This annotation has always been used in conjunction with the [@DiscriminatorMap](#ann_discriminatormap) and -[@DiscriminatorColumn](#ann_discriminatorcolumn) annotations. - -Examples: - - /** - * @Entity - * @InheritanceType("SINGLE_TABLE") - * @DiscriminatorColumn(name="discr", type="string") - * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) - */ - class Person - { - // ... - } - - /** - * @Entity - * @InheritanceType("JOINED") - * @DiscriminatorColumn(name="discr", type="string") - * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) - */ - class Person - { - // ... - } - - -+++ @JoinColumn - -This annotation is used in the context of relations in [@ManyToOne](#ann_manytoone), [@OneToOne](#ann_onetoone) fields -and in the Context of [@JoinTable](#ann_jointable) nested inside a @ManyToMany. This annotation is not required. -If its not specified the attributes *name* and *referencedColumnName* are infered from the table and primary key names. - -Required attributes: - -* name - Column name that holds the foreign key identifier for this relation. In the context of @JoinTable it specifies the column name in the join table. -* referencedColumnName - Name of the primary key identifier that is used for joining of this relation. - -Optional attributes: - -* unique - Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false. -* nullable - Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true. -* onDelete - Cascade Action (Database-level) -* onUpdate - Cascade Action (Database-level) -* columnDefinition - DDL SQL snippet that starts after the column name and specificies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on @JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a "columnDefinition" attribute on [@Column](#ann_column) also sets the related @JoinColumn's columnDefinition. This is necessary to make foreign keys work. - -Example: - - /** - * @OneToOne(targetEntity="Customer") - * @JoinColumn(name="customer_id", referencedColumnName="id") - */ - private $customer; - - -+++ @JoinColumns - -An array of @JoinColumn annotations for a [@ManyToOne](#ann_manytoone) or [@OneToOne](#ann_onetoone) relation -with an entity that has multiple identifiers. - - -+++ @JoinTable - -Using [@OneToMany](#ann_onetomany) or [@ManyToMany](#ann_manytomany) on the owning side of the relation requires to specifiy -the @JoinTable annotation which describes the details of the database join table. If you do not specify @JoinTable on -these relations reasonable mapping defaults apply using the affected table and the column names. - -Required attributes: - -* name - Database name of the join-table -* joinColumns - An array of @JoinColumn annotations describing the join-relation between the owning entites table and the join table. -* inverseJoinColumns - An array of @JoinColumn annotations describing the join-relation between the inverse entities table and the join table. - -Optional attributes: - -* schema - Database schema name of this table. - -Example: - - /** - * @ManyToMany(targetEntity="Phonenumber") - * @JoinTable(name="users_phonenumbers", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} - * ) - */ - public $phonenumbers; - - -+++ @ManyToOne - -Defines that the annotated instance variable holds a reference that describes a many-to-one relationship between two entities. - -Required attributes: - -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. - -Optional attributes: - -* cascade - Cascade Option -* fetch - One of LAZY or EAGER - -Example: - - /** - * @ManyToOne(targetEntity="Cart", cascade="ALL", fetch="EAGER") - */ - private $cart; - - -+++ @ManyToMany - -Defines an instance variable holds a many-to-many relationship between two entities. [@JoinTable](#ann_jointable) -is an additional, optional annotation that has reasonable default configuration values using the table -and names of the two related entities. - -Required attributes: - -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. - -Optional attributes: - -* mappedBy - This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship. -* cascade - Cascade Option -* fetch - One of LAZY or EAGER - -Example: - - /** - * Owning Side - * - * @ManyToMany(targetEntity="Group") - * @JoinTable(name="user_groups", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} - * ) - */ - private $groups; - - /** - * Inverse Side - * - * @ManyToMany(targetEntity="User", mappedBy="groups") - */ - private $features; - - -+++ @MappedSuperclass - -An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information -for its subclasses, but which is not itself an entity. This annotation is specified on the Class docblock -and has no additional attributes. - -The @MappedSuperclass annotation cannot be used in conjunction with @Entity. See the Inheritance Mapping -section for [more details on the restrictions of mapped superclasses](/../inheritance-mapping#mapped-superclasses). - - -+++ @OnetoOne - -The @OneToOne annotation works almost exactly as the [@ManyToOne](#ann_manytoone) with one additional option -that can be specified. The configuration defaults for [@JoinColumn](#ann_joincolumn) using the target entity table and primary key column names -apply here too. - -Required attributes: - -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. - -Optional attributes: - -* cascade - Cascade Option -* fetch - One of LAZY or EAGER -* orphanRemoval - Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any -owning instance, should be removed by Doctrine. Defaults to false. - - -+++ @OneToMany - - -+++ @OrderBy - -Optional annotation that can be specified with a [@ManyToMany](#ann_manytomany) or [@OneToMany](#ann_onetomany) -annotation to specify by which criteria the collection should be retrieved from the database by using an ORDER BY -clause. - -This annotation requires a single non-attributed value with an DQL snippet: - -Example: - - /** - * @ManyToMany(targetEntity="Group") - * @OrderBy({"name" = "ASC"}) - */ - private $groups; - -The DQL Snippet in OrderBy is only allowed to consist of unqualified, -unquoted field names and of an optional ASC/DESC positional statement. -Multiple Fields are separated by a comma (,). The referenced field -names have to exist on the `targetEntity` class of the `@ManyToMany` or -`@OneToMany` annotation. - - -+++ @PostLoad - -Marks a method on the entity to be called as a @PostLoad event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -+++ @PostPersist - -Marks a method on the entity to be called as a @PostPersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -+++ @PostRemove - -Marks a method on the entity to be called as a @PostRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -+++ @PostUpdate - -Marks a method on the entity to be called as a @PostUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -+++ @PrePersist - -Marks a method on the entity to be called as a @PrePersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -+++ @PreRemove - -Marks a method on the entity to be called as a @PreRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -+++ @PreUpdate - -Marks a method on the entity to be called as a @PreUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -+++ @SequenceGenerator - -For the use with @generatedValue(strategy="SEQUENCE") this annotation allows to specifiy details about the sequence, -such as the increment size and initial values of the sequence. - -Required attributes: - -* sequenceName - Name of the sequence - -Optional attributes: - -* allocationSize - Increment the sequence by the allocation size when its fetched. A value larger than 1 allows to optimize for scenarios where you create more than one new entity per request. Defaults to 10 -* initialValue - Where does the sequence start, defaults to 1. - -Example: - - /** - * @Id - * @GeneratedValue(strategy="SEQUENCE") - * @Column(type="integer") - * @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100) - */ - protected $id = null; - - -+++ @Table - -Annotation describes the table an entity is persisted in. It is placed on the entity-class PHP DocBlock and is optional. -If it is not specified the table name will default to the entities unqualified classname. - -Required attributes: - -* name - Name of the table - -Optional attributes: - -* schema - Database schema name of this table. -* indexes - Array of @Index annotations -* uniqueConstraints - Array of @UniqueConstraint annotations. - -Example: - - /** - * @Entity - * @Table(name="user", - * uniqueConstraints={@UniqueConstraint(name="user_unique",columns={"username"})}, - * indexes={@Index(name="user_idx", columns={"email"})} - * ) - */ - class User { } - - -+++ @UniqueConstraint - -Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the -SchemaTool to generate a database unique constraint on the specified table columns. It only has meaning in the SchemaTool -schema generation context. - -Required attributes: - -* name - Name of the Index -* columns - Array of columns. - -Example: - - /** - * @Entity - * @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "email"})}) - */ - class ECommerceProduct - { - } - - -+++ @Version - -Marker annotation that defines a specified column as version attribute used in an optimistic locking scenario. -It only works on [@Column](#ann_column) annotations that have the type integer or datetime. - -Example: - - /** - * @column(type="integer") - * @version - */ - protected $version; \ No newline at end of file diff --git a/orm/manual/en/architecture.txt b/orm/manual/en/architecture.txt deleted file mode 100644 index 45f10d160..000000000 --- a/orm/manual/en/architecture.txt +++ /dev/null @@ -1,81 +0,0 @@ -This chapter gives an overview of the overall architecture, terminology and constraints of -Doctrine 2. It is recommended to read this chapter carefully. - -++ Entities - -An entity is a lightweight persistent domain object. An entity can be any regular -php class that obeys to the following restrictions: - -* An entity class must not be final or contain final methods. -* An entity class must not implement `__clone` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). -* An entity class must not implement `__wakeup` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). - Also consider implementing [Serializable](http://de3.php.net/manual/en/class.serializable.php]) instead. -* Any two entity classes in a class hierarchy that inherit directly or indirectly from one another must not have a mapped property with the same name. - That is, if B inherits from A then B must not have a mapped field with the same name as an already mapped field that is inherited from A. - -Entities support inheritance, polymorphic associations, and polymorphic queries. -Both abstract and concrete classes can be entities. Entities may extend non-entity -classes as well as entity classes, and non-entity classes may extend entity classes. - -> **TIP** -> The constructor of an entity is only ever invoked when *you* construct a new instance -> with the *new* keyword. Doctrine never calls entity constructors, thus you are free to use -> them as you wish and even have it require arguments of any type. - -+++ Entity states - -An entity instance can be characterized as being NEW, MANAGED, DETACHED or REMOVED. - -* A NEW entity instance has no persistent identity, and is not yet associated with an EntityManager and a UnitOfWork (i.e. those just created with the "new" operator). -* A MANAGED entity instance is an instance with a persistent identity that is associated with an EntityManager and whose persistence is thus managed. -* A DETACHED entity instance is an instance with a persistent identity that is not (or no longer) associated with an EntityManager and a UnitOfWork. -* A REMOVED entity instance is an instance with a persistent identity, associated with an EntityManager, that will be removed from the database upon transaction commit. - -+++ Persistent fields - -The persistent state of an entity is represented by instance variables. An -instance variable must be directly accessed only from within the methods of the -entity by the entity instance itself. Instance variables must not be accessed by -clients of the entity. The state of the entity is available to clients only through -the entity’s methods, i.e. accessor methods (getter/setter methods) or other -business methods. - -Collection-valued persistent fields and properties must be defined in terms of -the `Doctrine\Common\Collections\Collection` interface. The collection -implementation type may be used by the application to initialize fields or -properties before the entity is made persistent. Once the entity becomes -managed (or detached), subsequent access must be through the interface type. - -+++ Serializing entities - -Serializing entities can be problematic and is not really recommended, at least not as long as an -entity instance still holds references to proxy objects or is still managed by an EntityManager. -If you intend to serialize (and unserialize) entity instances that still hold references to proxy objects -you may run into problems with private properties because of technical limitations. -Proxy objects implement `__sleep` and it is not possible for `__sleep` to return names of -private properties in parent classes. On ther other hand it is not a solution for proxy objects -to implement `Serializable` because Serializable does not work well with any potential cyclic -object references (at least we did not find a way yet, if you did, please contact us). - -++ The EntityManager - -The `EntityManager` class is a central access point to the ORM functionality -provided by Doctrine 2. The `EntityManager` API is used to manage the persistence -of your objects and to query for persistent objects. - -+++ Transactional write-behind - -An `EntityManager` and the underlying `UnitOfWork` employ a strategy called -"transactional write-behind" that delays the execution of SQL statements in -order to execute them in the most efficient way and to execute them at the end -of a transaction so that all write locks are quickly released. You should see -Doctrine as a tool to synchronize your in-memory objects with the database in -well defined units of work. Work with your objects and modify them as usual and -when you're done call `EntityManager#flush()` to make your changes persistent. - -+++ The Unit of Work - -Internally an `EntityManager` uses a `UnitOfWork`, which is a typical -implementation of the [Unit of Work pattern](http://martinfowler.com/eaaCatalog/unitOfWork.html), to keep track of all the things that need to be done -the next time `flush` is invoked. You usually do not directly interact with -a `UnitOfWork` but with the `EntityManager` instead. \ No newline at end of file diff --git a/orm/manual/en/association-mapping.txt b/orm/manual/en/association-mapping.txt deleted file mode 100644 index 5190f6a14..000000000 --- a/orm/manual/en/association-mapping.txt +++ /dev/null @@ -1,429 +0,0 @@ -This chapter explains how associations between entities are mapped with Doctrine. We start out with an explanation of the concept of owning and inverse sides which is important to understand when working with bidirectional associations. Please read these explanations carefully. - -++ Owning Side and Inverse Side - -When mapping bidirectional associations it is important to understand the concept of the owning and inverse sides. The following general rules apply: - -* Relationships may be bidirectional or unidirectional. -* A bidirectional relationship has both an owning side and an inverse side. -* A unidirectional relationship only has an owning side. -* The owning side of a relationship determines the updates to the relationship in the database. - - -The following rules apply to *bidirectional* associations: - -* The inverse side of a bidirectional relationship must refer to its owning side by use of the mappedBy attribute of the OneToOne, OneToMany, or ManyToMany mapping declaration. The mappedBy attribute designates the field in the entity that is the owner of the relationship. -* The owning side of a bidirectional relationship must refer to its inverse side by use of the inversedBy attribute of the OneToOne, ManyToOne, or ManyToMany mapping declaration. The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. -* The many side of OneToMany/ManyToOne bidirectional relationships *must* be the owning side, hence the mappedBy element can not be specified on the ManyToOne side. -* For OneToOne bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key (@JoinColumn(s)). -* For ManyToMany bidirectional relationships either side may be the owning side (the side that defines the @JoinTable and/or does not make use of the mappedBy attribute, thus using a default join table). - -Especially important is the following: - -**The owning side of a relationship determines the updates to the relationship in the database**. - -To fully understand this, remember how bidirectional associations are maintained -in the object world. There are 2 references on each side of the association -and these 2 references both represent the same association but can change -independently of one another. Of course, in a correct application the semantics -of the bidirectional association are properly maintained by the application -developer (that's his responsiblity). Doctrine needs to know which of -these two in-memory references is the one that should be persisted and which -not. This is what the owning/inverse concept is mainly used for. - -**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)** - -The owning side of a bidirectional association is the side Doctrine "looks at" when determining -the state of the association, and consequently whether there is anything to do to update the association -in the database. - -> **NOTE** -> "Owning side" and "inverse side" are technical concepts of the ORM technology, not concepts -> of your domain model. What you consider as the owning side in your domain model can be different -> from what the owning side is for Doctrine. These are unrelated. - -++ Collections - -In all the examples of many-valued associations in this manual we will make use of a `Collection` interface and a corresponding default implementation `ArrayCollection` that are defined in the `Doctrine\Common\Collections` namespace. Why do we need that? Doesn't that couple my domain model to Doctrine? Unfortunately, PHP arrays, while being great for many things, do not make up for good collections of business objects, especially not in the context of an ORM. The reason is that plain PHP arrays can not be transparently extended / instrumented in PHP code, which is necessary for a lot of advanced ORM features. The classes / interfaces that come closest to an OO collection are ArrayAccess and ArrayObject but until instances of these types can be used in all places where a plain array can be used (something that may happen in PHP6) their useability is fairly limited. You "can" type-hint on `ArrayAccess` instead of `Collection`, since the Collection interface extends `ArrayAccess`, but this will severely limit you in the way you can work with the collection, because the `ArrayAccess` API is (intentionally) very primitive and more importantly because you can not pass this collection to all the useful PHP array functions, which makes it very hard to work with. - -> **CAUTION** -> The Collection interface and ArrayCollection class, like everything else in the -> Doctrine\Common namespace, are neither part of the ORM, nor the DBAL, it is a plain PHP -> class that has no outside dependencies apart from dependencies on PHP itself (and the -> SPL). Therefore using this class in your domain classes and elsewhere does not introduce -> a coupling to the persistence layer. The Collection class, like everything else in the -> Common namespace, is not part of the persistence layer. You could even copy that class -> over to your project if you want to remove Doctrine from your project and all your -> domain classes will work the same as before. - -++ Mapping Defaults - -The @JoinColumn and @JoinTable definitions are usually optional and have sensible default values. The defaults for a join column in a one-to-one/many-to-one association is as follows: - - name: "_id" - referencedColumnName: "id" - -As an example, consider this mapping: - - [php] - /** @OneToOne(targetEntity="Shipping") */ - private $shipping; - -This is essentially the same as the following, more verbose, mapping: - - [php] - /** - * @OneToOne(targetEntity="Shipping") - * @JoinColumn(name="shipping_id", referencedColumnName="id") - */ - private $shipping; - - -The @JoinTable definition used for many-to-many mappings has similar defaults. As an example, consider this mapping: - - [php] - class User { - //... - /** @ManyToMany(targetEntity="Group") */ - private $groups; - //... - } - -This is essentially the same as the following, more verbose, mapping: - - [php] - class User { - //... - /** - * @ManyToMany(targetEntity="Group") - * @JoinTable(name="User_Group", - * joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")} - * ) - */ - private $groups; - //... - } - -In that case, the name of the join table defaults to a combination of the simple, unqualified class names of the participating classes, separated by an underscore character. The names of the join columns default to the simple, unqualified class name of the targeted class followed by "_id". The referencedColumnName always defaults to "id", just as in one-to-one or many-to-one mappings. - -If you accept these defaults, you can reduce the mapping code to a minimum. - -++ One-To-One, Unidirectional - -A unidirectional one-to-one association is very common. Here is an example of a `Product` that has one `Shipping` object associated to it. The `Shipping` side does not reference back to the `Product` so it is unidirectional. - - [php] - /** @Entity */ - class Product - { - // ... - - /** - * @OneToOne(targetEntity="Shipping") - * @JoinColumn(name="shipping_id", referencedColumnName="id") - */ - private $shipping; - - // ... - } - - /** @Entity */ - class Shipping - { - // ... - } - -Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. - -++ One-To-One, Bidirectional - -Here is a one-to-one relationship between a `Customer` and a `Cart`. The `Cart` -has a reference back to the `Customer` so it is bidirectional. - - [php] - /** @Entity */ - class Customer - { - // ... - - /** - * @OneToOne(targetEntity="Cart", mappedBy="customer") - */ - private $cart; - - // ... - } - - /** @Entity */ - class Cart - { - // ... - - /** - * @OneToOne(targetEntity="Customer", inversedBy="cart") - * @JoinColumn(name="customer_id", referencedColumnName="id") - */ - private $customer; - - // ... - } - -Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. - -++ One-To-One, Self-referencing - -You can easily have self referencing one-to-one relationships like below. - - [php] - /** @Entity */ - class Customer - { - // ... - - /** - * @OneToOne(targetEntity="Customer") - * @JoinColumn(name="mentor_id", referencedColumnName="id") - */ - private $mentor; - - // ... - } - -Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. - -++ One-To-Many, Unidirectional with Join Table - -A unidirectional one-to-many association can be mapped through a join table. From Doctrine's point of view, it is simply mapped as a unidirectional many-to-many whereby a unique constraint on one of the join columns enforces the one-to-many cardinality. -The following example sets up such a unidirectional one-to-many association: - - [php] - /** @Entity */ - class User - { - // ... - - /** - * @ManyToMany(targetEntity="Phonenumber") - * @JoinTable(name="users_phonenumbers", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} - * ) - */ - private $phonenumbers; - - // ... - } - - /** @Entity */ - class Phonenumber - { - // ... - } - -> **NOTE** -> One-To-Many uni-directional relations with join-table only work using the @ManyToMany annotation and a unique-constraint. - -++ One-To-Many, Bidirectional - -Bidirectional one-to-many associations are very common. The following code shows an example with a Product and a Feature class: - - [php] - /** @Entity */ - class Product - { - // ... - /** - * @OneToMany(targetEntity="Feature", mappedBy="product") - */ - private $features; - // ... - } - - /** @Entity */ - class Feature - { - // ... - /** - * @ManyToOne(targetEntity="Product", inversedBy="features") - * @JoinColumn(name="product_id", referencedColumnName="id") - */ - private $product; - // ... - } - -Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. - -++ One-To-Many, Self-referencing - -You can also setup a one-to-many association that is self-referencing. In this example we -setup a hierarchy of `Category` objects by creating a self referencing relationship. -This effectively models a hierarchy of categories and from the database perspective is known as an adjacency list approach. - - [php] - /** @Entity */ - class Category - { - // ... - /** - * @OneToMany(targetEntity="Category", mappedBy="parent") - */ - private $children; - - /** - * @ManyToOne(targetEntity="Category", inversedBy="children") - * @JoinColumn(name="parent_id", referencedColumnName="id") - */ - private $parent; - // ... - } - -Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. - -++ Many-To-Many, Unidirectional - -Real many-to-many associations are less common. The following example shows a unidirectional association between User and Group entities: - - [php] - /** @Entity */ - class User - { - // ... - - /** - * @ManyToMany(targetEntity="Group") - * @JoinTable(name="users_groups", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} - * ) - */ - private $groups; - - // ... - } - - /** @Entity */ - class Group - { - // ... - } - -> **NOTE** -> Why are many-to-many associations less common? Because frequently you want to associate -> additional attributes with an association, in which case you introduce an association -> class. Consequently, the direct many-to-many association disappears and is replaced -> by one-to-many/many-to-one associations between the 3 participating classes. - -++ Many-To-Many, Bidirectional - -Here is a similar many-to-many relationship as above except this one is bidirectional. - - [php] - /** @Entity */ - class User - { - // ... - - /** - * @ManyToMany(targetEntity="Group", inversedBy="users") - * @JoinTable(name="users_groups", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} - * ) - */ - private $groups; - - // ... - } - - /** @Entity */ - class Group - { - // ... - /** - * @ManyToMany(targetEntity="User", mappedBy="groups") - */ - private $users; - // ... - } - -++ Many-To-Many, Self-referencing - -You can even have a self-referencing many-to-many association. A common scenario is where a `User` has friends and the target entity of that relationship is a `User` so it is self referencing. In this example it is bidirectional so `User` has a field named `$friendsWithMe` and `$myFriends`. - - [php] - /** @Entity */ - class User - { - // ... - - /** - * @ManyToMany(targetEntity="User", mappedBy="myFriends") - */ - private $friendsWithMe; - - /** - * @ManyToMany(targetEntity="User", inversedBy="friendsWithMe") - * @JoinTable(name="friends", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")} - * ) - */ - private $myFriends; - - // ... - } - -++ Ordering To-Many Collections - -In many use-cases you will want to sort collections when they are retrieved from the database. -In userland you do this as long as you haven't initially saved an entity with its associations -into the database. To retrieve a sorted collection from the database you can use the -`@OrderBy` annotation with an collection that specifies an DQL snippet that is appended -to all queries with this collection. - -Additional to any `@OneToMany` or `@ManyToMany` annotation you can specify the `@OrderBy` -in the following way: - - [php] - /** @Entity */ - class User - { - // ... - - /** - * @ManyToMany(targetEntity="Group") - * @OrderBy({"name" = "ASC"}) - */ - private $groups; - } - -The DQL Snippet in OrderBy is only allowed to consist of unqualified, -unquoted field names and of an optional ASC/DESC positional statement. -Multiple Fields are separated by a comma (,). The referenced field -names have to exist on the `targetEntity` class of the `@ManyToMany` or -`@OneToMany` annotation. - -The semantics of this feature can be described as follows. - -* `@OrderBy` acts as an implicit ORDER BY clause for the given fields, that is appended -to all the explicitly given ORDER BY items. -* All collections of the ordered type are always retrieved in an ordered fashion. -* To keep the database impact low, these implicit ORDER BY items are only added -to an DQL Query if the collection is fetch joined in the DQL query. - -Given our previously defined Example: - - [sql] - -- Would not add ORDER BY, since g is not fetch joined - SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10 - - -- However - SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 - -- would internally be rewritten to - SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC - - -- You can't reverse the order, an explicit: - SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC - -- is internally be rewritten to - SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC diff --git a/orm/manual/en/basic-mapping.txt b/orm/manual/en/basic-mapping.txt deleted file mode 100644 index 17910de81..000000000 --- a/orm/manual/en/basic-mapping.txt +++ /dev/null @@ -1,279 +0,0 @@ -This chapter explains the basic mapping of objects and properties. Mapping of associations will be covered in the next chapter "Association Mapping". - -++ Mapping Drivers - -Doctrine provides several different ways for specifying object-relational mapping metadata: - -* Docblock Annotations -* XML -* YAML - -This manual usually uses docblock annotations in all the examples that are spread throughout all chapters. There are dedicated chapters for XML and YAML mapping, respectively. - -> **NOTE** -> If you're wondering which mapping driver gives the best performance, the answer is: -> None. Once the metadata of a class has been read from the source (annotations, xml or -> yaml) it is stored in an instance of the `Doctrine\ORM\Mapping\ClassMetadata` class -> and these instances are stored in the metadata cache. Therefore at the end of the day -> all drivers perform equally well. If you're not using a metadata cache (not -> recommended!) then the XML driver might have a slight edge in performance due to the -> powerful native XML support in PHP. - -++ Introduction to Docblock Annotations - -You've probably used docblock annotations in some form already, most likely to provide documentation metadata for a tool like `PHPDocumentor` (@author, @link, ...). Docblock annotations are a tool to embed metadata inside the documentation section which can then be processed by some tool. Doctrine 2 generalizes the concept of docblock annotations so that they can be used for any kind of metadata and so that it is easy to define new docblock annotations. In order to allow more involved annotation values and to reduce the chances of clashes with other docblock annotations, the Doctrine 2 docblock annotations feature an alternative syntax that is heavily inspired by the Annotation syntax introduced in Java 5. - -The implementation of these enhanced docblock annotations is located in the `Doctrine\Common\Annotations` namespace and therefore part of the Common package. Doctrine 2 docblock annotations support namespaces and nested annotations among other things. The Doctrine 2 ORM defines its own set of docblock annotations for supplying object-relational mapping metadata. - -> **NOTE** -> If you're not comfortable with the concept of docblock annotations, don't worry, as -> mentioned earlier Doctrine 2 provides XML and YAML alternatives and you could easily -> implement your own favourite mechanism for defining ORM metadata. - -++ Persistent classes - -In order to mark a class for object-relational persistence it needs to be designated as an entity. This can be done through the `@Entity` marker annotation. - - [php] - /** @Entity */ - class MyPersistentClass - { - //... - } - -By default, the entity will be persisted to a table with the same name as the class name. In order to change that, you can use the `@Table` annotation as follows: - - [php] - /** - * @Entity - * @Table(name="my_persistent_class") - */ - class MyPersistentClass - { - //... - } - -Now instances of MyPersistentClass will be persisted into a table named `my_persistent_class`. - -++ Doctrine Mapping Types - -A Doctrine Mapping Type defines the mapping between a PHP type and an SQL type. All Doctrine Mapping Types that ship with Doctrine are fully portable between different RDBMS. You can even write your own custom mapping types that might or might not be portable, which is explained later in this chapter. - -For example, the Doctrine Mapping Type `string` defines the mapping from a PHP string to an SQL VARCHAR (or VARCHAR2 etc. depending on the RDBMS brand). Here is a quick overview of the built-in mapping types: - -* `string`: Type that maps an SQL VARCHAR to a PHP string. -* `integer`: Type that maps an SQL INT to a PHP integer. -* `smallint`: Type that maps a database SMALLINT to a PHP integer. -* `bigint`: Type that maps a database BIGINT to a PHP string. -* `boolean`: Type that maps an SQL boolean to a PHP boolean. -* `decimal`: Type that maps an SQL DECIMAL to a PHP double. -* `date`: Type that maps an SQL DATETIME to a PHP DateTime object. -* `time`: Type that maps an SQL TIME to a PHP DateTime object. -* `datetime`: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object. -* `text`: Type that maps an SQL CLOB to a PHP string. - -> **NOTE** -> Doctrine Mapping Types are NOT SQL types and NOT PHP types! They are mapping types -> between 2 types. - -++ Property Mapping - -After a class has been marked as an entity it can specify mappings for its instance fields. Here we will only look at simple fields that hold scalar values like strings, numbers, etc. Associations to other objects are covered in the chapter "Association Mapping". - -To mark a property for relational persistence the `@Column` docblock annotation is used. This annotation usually requires at least 1 attribute to be set, the `type`. The `type` attribute specifies the Doctrine Mapping Type to use for the field. If the type is not specified, 'string' is used as the default mapping type since it is the most flexible. - -Example: - - [php] - /** @Entity */ - class MyPersistentClass - { - /** @Column(type="integer") */ - private $id; - /** @Column(length=50) */ - private $name; // type defaults to string - //... - } - -In that example we mapped the field `id` to the column `id` using the mapping type `integer` and the field `name` is mapped to the column `name` with the default mapping type `string`. As you can see, by default the column names are assumed to be the same as the field names. To specify a different name for the column, you can use the `name` attribute of the Column annotation as follows: - - [php] - /** @Column(name="db_name") */ - private $name; - -The Column annotation has some more attributes. Here is a complete list: - -* `type`: (optional, defaults to 'string') The mapping type to use for the column. -* `name`: (optional, defaults to field name) The name of the column in the database. -* `length`: (optional, default 255) The length of the column in the database. (Applies only if a string-valued column is used). -* `unique`: (optional, default FALSE) Whether the column is a unique key. -* `nullable`: (optional, default FALSE) Whether the database column is nullable. -* `precision`: (optional, default 0) The precision for a decimal (exact numeric) column. (Applies only if a decimal column is used.) -* `scale`: (optional, default 0) The scale for a decimal (exact numeric) column. (Applies only if a decimal column is used.) - -++ Custom Mapping Types - -Doctrine allows you to create new mapping types. This can come in handy when you're missing a specific mapping type -or when you want to replace the existing implementation of a mapping type. - -In order to create a new mapping type you need to subclass `Doctrine\DBAL\Types\Type` and implement/override -the methods as you wish. Here is an example skeleton of such a custom type class: - - [php] - namespace My\Project\Types; - - use Doctrine\DBAL\Types\Type; - use Doctrine\DBAL\Platforms\AbstractPlatform; - - /** - * My custom datatype. - */ - class MyType extends Type - { - public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) - { - // return the SQL used to create your column type. To create a portable column type, use the $platform. - } - - public function convertToPHPValue($value, AbstractPlatform $platform) - { - // This is executed when the value is read from the database. Make your conversions here, optionally using the $platform. - } - - public function convertToDatabaseValue($value, AbstractPlatform $platform) - { - // This is executed when the value is written to the database. Make your conversions here, optionally using the $platform. - } - } - -Restrictions to keep in mind: - -* If the value of the field is *NULL* the method `convertToDatabaseValue()` is not called. -* The `UnitOfWork` never passes values to the database convert method that did not change in the request. - -When you have implemented the type you still need to let Doctrine know about it. This can be achieved -through the `Doctrine\DBAL\Configuration#setCustomTypes(array $types)` method. - -> **NOTE** -> `Doctrine\ORM\Configuration` is a subclass of `Doctrine\DBAL\Configuration`, so the -> methods are available on your ORM Configuration instance as well. - -Here is an example: - - [php] - // in bootstrapping code - - // ... - - use Doctrine\DBAL\Types\Type; - - // ... - - // Register my type - Type::addType('mytype', 'My\Project\Types\MyType'); - -As can be seen above, when registering the custom types in the configuration you specify a unique name -for the mapping type and map that to the corresponding fully qualified class name. Now you can use your new type in your mapping like this: - - [php] - class MyPersistentClass - { - /** @Column(type="mytype") */ - private $field; - } - -++ Identifiers / Primary Keys - -Every entity class needs an identifier/primary key. You designate the field that serves as the identifier with the `@Id` marker annotation. Here is an example: - - [php] - class MyPersistentClass - { - /** @Id @Column(type="integer") */ - private $id; - //... - } - -Without doing anything else, the identifier is assumed to be manually assigned. That means your code would need to properly set the identifier property before passing a new entity to `EntityManager#persist($entity)`. - -A common alternative strategy is to use a generated value as the identifier. To do this, you use the `@GeneratedValue` annotation like this: - - [php] - class MyPersistentClass - { - /** - * @Id @Column(type="integer") - * @GeneratedValue - */ - private $id; - } - -This tells Doctrine to automatically generate a value for the identifier. How this value is generated is specified by the `strategy` attribute, which is optional and defaults to 'AUTO'. A value of `AUTO` tells Doctrine to use the generation strategy that is preferred by the currently used database platform. For MySql, for example, Doctrine would use the `IDENTITY` strategy which means a typical AUTO_INCREMENT column. For PostgreSql it would choose to use the `SEQUENCE` strategy which would result in using a database sequence. - -+++ Id Generation Strategies - -The previous example showed how to use the default Id generation strategy without knowing the underlying database with the AUTO-detection strategy. -It is also possible to specifiy the Id Generation strategy more explicitly, which allows to make use of some additional features. - -Here is the list of possible generation strategies: - -* `AUTO` (default): Tells Doctrine to pick the strategy that is preferred by the used database platform. This strategy provides full portability. -* `NONE`: Tells Doctrine that you generated the entities primary key value in userland before `EntityManager#persist()` is called. -* `SEQUENCE`: Tells Doctrine to use a database sequence for ID generation. This strategy does currently not provide full portability. Sequences are supported by Oracle and PostgreSql. -* `IDENTITY`: Tells Doctrine to use special identity columns in the database that usually generate a value on insertion of a row (i.e. MySql AUTO_INCREMENT). This strategy does currently not provide full portability. IDENTITY is supported by MySql, Sqlite and MsSql. -* `TABLE`: Tells Doctrine to use a separate table for ID generation. This strategy provides full portability. ***This strategy is not yet implemented!*** - -++++ Sequence Generator - -The Sequence Generator can be used in conjunction with Oracle or Postgres Platforms and allows some additional configuration options besides -specifiying the sequence's name: - - [php] - class User { - /** - * @Id - * @GeneratedValue(strategy="SEQUENCE") - * @SequenceGenerator(name="tablename_seq", initialValue=1, allocationSize=100) - */ - protected $id = null; - } - -The initial value specifies at which value the sequence should start. - -Allocation Size is a powerful feature to optimize INSERT performance of Doctrine. The allocation size specifies -by how much values the sequence is incremented whenever the next value is retrieved. If this is larger than one -Doctrine 2 can generate Id values for the allocationSizes amount of entities. In the above example with -`allocationSize=100` Doctrine 2 would only need to access the sequence once to generate the identifiers for -100 new entities. - -> **CAUTION** - -> Allocation Size is detected by SchemaTool and transformed into an "INCREMENT BY " clause -> in the CREATE SEQUENCE statement. For a database schema created by you (and not SchemaTool) you have to -> make sure that the allocationSize configuration option is never larger than the actual sequences INCREMENT BY value. - -+++ Composite Keys - -Doctrine 2 allows to use composite primary keys. There are however some restrictions oposed to using a single identifier. -The use of the `@GeneratedValue` annotation is only supported for simple (not composite) primary keys, which means -you can only use composite keys if you generate the primary key values yourself before calling `EntityManager#persist()` -on the entity. - -To designate a composite primary key / identifier, simply put the @Id marker annotation on all fields that make up the primary key. - -++ Quoting Reserved Words - -It may sometimes be necessary to quote a column or table name because it conflicts with a reserved word of the particular RDBMS in use. This is often referred to as "Identifier Quoting". To let Doctrine know that you would like a table or column name to be quoted in all SQL statements, enclose the table or column name in backticks. Here is an example: - - [php] - /** @Column(name="`number`", type="integer") */ - private $number; - -Doctrine will then quote this column name in all SQL statements according to the used database platform. - -> **CAUTION** -> Identifier Quoting is not supported for join column names or discriminator column names. - -> **CAUTION** -> Identifier Quoting is a feature that is mainly intended to support legacy database -> schemas. The use of reserved words and identifier quoting is generally discouraged. \ No newline at end of file diff --git a/orm/manual/en/batch-processing.txt b/orm/manual/en/batch-processing.txt deleted file mode 100644 index b9c3619d1..000000000 --- a/orm/manual/en/batch-processing.txt +++ /dev/null @@ -1,122 +0,0 @@ -This chapter shows you how to accomplish bulk inserts, updates and deletes with Doctrine in an efficient way. The main problem with bulk operations is usually not to run out of memory and this is especially what the strategies presented here provide help with. - -> **CAUTION** -> An ORM tool is not primarily well-suited for mass inserts, updates or deletions. -> Every RDBMS has its own, most effective way of dealing with such operations and if -> the options outlined below are not sufficient for your purposes we recommend you -> use the tools for your particular RDBMS for these bulk operations. - -++ Bulk Inserts - -Bulk inserts in Doctrine are best performed in batches, taking advantage of the transactional write-behind behavior of an `EntityManager`. The following code shows an example for inserting 10000 objects with a batch size of 20. You may need to experiment with the batch size to find the size that works best for you. Larger batch sizes mean more prepared statement reuse internally but also mean more work during `flush`. - - [php] - $batchSize = 20; - for ($i = 1; $i <= 10000; ++$i) { - $user = new CmsUser; - $user->setStatus('user'); - $user->setUsername('user' . $i); - $user->setName('Mr.Smith-' . $i); - $em->persist($user); - if (($i % $batchSize) == 0) { - $em->flush(); - $em->clear(); // Detaches all objects from Doctrine! - } - } - -++ Bulk Updates - -There are 2 possibilities for bulk updates with Doctrine. - -+++ DQL UPDATE - -The by far most efficient way for bulk updates is to use a DQL UPDATE query. Example: - - [php] - $q = $em->createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9'); - $numUpdated = $q->execute(); - -+++ Iterating results - -An alternative solution for bulk updates is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this, combining the iteration with the batching strategy that was already used for bulk inserts: - - [php] - $batchSize = 20; - $i = 0; - $q = $em->createQuery('select u from MyProject\Model\User u'); - $iterableResult = $q->iterate(); - foreach($iterableResult AS $row) { - $user = $row[0]; - $user->increaseCredit(); - $user->calculateNewBonuses(); - if (($i % $batchSize) == 0) { - $em->flush(); // Executes all updates. - $em->clear(); // Detaches all objects from Doctrine! - } - ++$i; - } - -> **NOTE** -> Iterating results is not possible with queries that fetch-join a collection-valued -> association. The nature of such SQL result sets is not suitable for incremental -> hydration. - -++ Bulk Deletes - -There are two possibilities for bulk deletes with Doctrine. You can either issue -a single DQL DELETE query or you can iterate over results removing them one at a time. - -+++ DQL DELETE - -The by far most efficient way for bulk deletes is to use a DQL DELETE query. - -Example: - - [php] - $q = $em->createQuery('delete from MyProject\Model\Manager m where m.salary > 100000'); - $numDeleted = $q->execute(); - -+++ Iterating results - -An alternative solution for bulk deletes is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this: - - [php] - $batchSize = 20; - $i = 0; - $q = $em->createQuery('select u from MyProject\Model\User u'); - $iterableResult = $q->iterate(); - while (($row = $iterableResult->next()) !== false) { - $em->remove($row[0]); - if (($i % $batchSize) == 0) { - $em->flush(); // Executes all deletions. - $em->clear(); // Detaches all objects from Doctrine! - } - ++$i; - } - -> **NOTE** -> Iterating results is not possible with queries that fetch-join a collection-valued -> association. The nature of such SQL result sets is not suitable for incremental -> hydration. - -++ Iterating Large Results for Data-Processing - -You can use the `iterate()` method just to iterate over a large result and no UPDATE or DELETE -intention. The `IterableResult` instance returned from `$query->iterate()` implements the -Iterator interface so you can process a large result without memory problems using the -following approach: - - [php] - $q = $this->_em->createQuery('select u from MyProject\Model\User u'); - $iterableResult = $q->iterate(); - foreach ($iterableResult AS $row) { - // do stuff with the data in the row, $row[0] is always the object - - // detach from Doctrine, so that it can be Garbage-Collected immediately - $this->_em->detach($row[0]); - } - -> **NOTE** -> Iterating results is not possible with queries that fetch-join a collection-valued -> association. The nature of such SQL result sets is not suitable for incremental -> hydration. \ No newline at end of file diff --git a/orm/manual/en/best-practices.txt b/orm/manual/en/best-practices.txt deleted file mode 100644 index 9c095a5ae..000000000 --- a/orm/manual/en/best-practices.txt +++ /dev/null @@ -1,77 +0,0 @@ - -> **NOTE** -> The best practices mentioned here that affect database design generally refer to best -> practices when working with Doctrine and do not necessarily reflect best practices for -> database design in general. - -++ Don't use public properties on entities - -It is very important that you don't map public properties on entities, but only protected or private ones. -The reason for this is simple, whenever you access a public property of a proxy object that hasn't been initialized -yet the return value will be null. Doctrine cannot hook into this process and magically make the entity lazy load. - -This can create situations where it is very hard to debug the current failure. We therefore urge you to map only -private and protected properties on entities and use getter methods or magic __get() to access them. - -++ Constrain relationships as much as possible - -It is important to constrain relationships as much as possible. This means: - -* Impose a traversal direction (avoid bidirectional associations if possible) -* Eliminate nonessential associations - -This has several benefits: - -* Reduced coupling in your domain model -* Simpler code in your domain model (no need to maintain bidirectionality properly) -* Less work for Doctrine - -++ Avoid composite keys - -Even though Doctrine fully supports composite keys it is best not to use them if possible. Composite keys require additional work by Doctrine and thus have a higher probability of errors. - -++ Use events judiciously - -The event system of Doctrine is great and fast. Even though making heavy use of events, especially lifecycle events, can have a negative impact on the performance of your application. Thus you should use events judiciously. - -++ Use cascades judiciously - -Automatic cascades of the persist/remove/merge/etc. operations are very handy but should be used wisely. Do NOT simply add all cascades to all associations. Think about which cascades actually do make sense for you for a particular association, given the scenarios it is most likely used in. - -++ Don't use special characters - -Avoid using any non-ASCII characters in class, field, table or column names. Doctrine itself is not unicode-safe in many places and will not be until PHP itself is fully unicode-aware (PHP6). - -++ Don't use identifier quoting - -Identifier quoting is a workaround for using reserved words that often causes problems in edge cases. Do not use identifier quoting and avoid using reserved words as table or column names. - -++ Initialize collections in the constructor - -It is recommended best practice to initialize any business collections in entities in the constructor. Example: - - [php] - namespace MyProject\Model; - use Doctrine\Common\Collections\ArrayCollection; - - class User { - private $addresses; - private $articles; - - public function __construct() { - $this->addresses = new ArrayCollection; - $this->articles = new ArrayCollection; - } - } - -++ Don't map foreign keys to fields in an entity - -Foreign keys have no meaning whatsoever in an object model. Foreign keys are how a relational database establishes relationships. Your object model establishes relationships through object references. Thus mapping foreign keys to object fields heavily leaks details of the relational model into the object model, something you really should not do. - -++ Use explicit transaction demarcation - -While Doctrine will automatically wrap all DML operations in a transaction on flush(), it is considered best practice to explicitly set the transaction boundaries yourself. -Otherwise every single query is wrapped in a small transaction (Yes, SELECT queries, too) since you can not talk to your database outside of a transaction. -While such short transactions for read-only (SELECT) queries generally dont have any noticable performance impact, it is still preferrable to use fewer, well-defined transactions -that are established through explicit transaction boundaries. - diff --git a/orm/manual/en/caching.txt b/orm/manual/en/caching.txt deleted file mode 100644 index f3e5d202f..000000000 --- a/orm/manual/en/caching.txt +++ /dev/null @@ -1,350 +0,0 @@ -Doctrine provides cache drivers in the `Common` package for some of the most -popular caching implementations such as APC, Memcache and Xcache. We also provide -an `ArrayCache` driver which stores the data in a PHP array. Obviously, the cache -does not live between requests but this is useful for testing in a development -environment. - -++ Cache Drivers - -The cache drivers follow a simple interface that is defined in `Doctrine\Common\Cache\Cache`. -All the cache drivers extend a base class `Doctrine\Common\Cache\AbstractCache` -which implements the before mentioned interface. - -The interface defines the following methods for you to publicly use. - - * fetch($id) - Fetches an entry from the cache. - * contains($id) - Test if an entry exists in the cache. - * save($id, $data, $lifeTime = false) - Puts data into the cache. - * delete($id) - Deletes a cache entry. - -Each driver extends the `AbstractCache` class which defines a few abstract -protected methods that each of the drivers must implement. - - * _doFetch($id) - * _doContains($id) - * _doSave($id, $data, $lifeTime = false) - * _doDelete($id) - -The public methods `fetch()`, `contains()`, etc. utilize the above protected methods -that are implemented by the drivers. The code is organized this way so that the -protected methods in the drivers do the raw interaction with the cache implementation -and the `AbstractCache` can build custom functionality on top of these methods. - -+++ APC - -In order to use the APC cache driver you must have it compiled and enabled in -your php.ini. You can read about APC [here](http://us2.php.net/apc) on the PHP -website. It will give you a little background information about what it is and -how you can use it as well as how to install it. - -Below is a simple example of how you could use the APC cache driver by itself. - - [php] - $cacheDriver = new \Doctrine\Common\Cache\ApcCache(); - $cacheDriver->save('cache_id', 'my_data'); - -+++ Memcache - -In order to use the Memcache cache driver you must have it compiled and enabled in -your php.ini. You can read about Memcache [here](http://us2.php.net/memcache) on -the PHP website. It will give you a little background information about what it is -and how you can use it as well as how to install it. - -Below is a simple example of how you could use the Memcache cache driver by itself. - - [php] - $memcache = new Memcache(); - $memcache->connect('memcache_host', 11211); - - $cacheDriver = new \Doctrine\Common\Cache\MemcacheCache(); - $cacheDriver->setMemcache() - $cacheDriver->save('cache_id', 'my_data'); - -+++ Xcache - -In order to use the Xcache cache driver you must have it compiled and enabled in -your php.ini. You can read about Xcache [here](http://xcache.lighttpd.net/). It -will give you a little background information about what it is and how you can -use it as well as how to install it. - -Below is a simple example of how you could use the Xcache cache driver by itself. - - [php] - $cacheDriver = new \Doctrine\Common\Cache\XcacheCache(); - $cacheDriver->save('cache_id', 'my_data'); - -++ Using Cache Drivers - -In this section we'll describe how you can fully utilize the API of the cache -drivers to save cache, check if some cache exists, fetch the cached data and -delete the cached data. We'll use the `ArrayCache` implementation as our -example here. - - [php] - $cacheDriver = new \Doctrine\Common\Cache\ArrayCache(); - -+++ Saving - -To save some data to the cache driver it is as simple as using the `save()` method. - - [php] - $cacheDriver->save('cache_id', 'my_data'); - -The `save()` method accepts three arguments which are described below. - - * `$id` - The cache id - * `$data` - The cache entry/data. - * `$lifeTime` - The lifetime. If != false, sets a specific lifetime for this cache entry (null => infinite lifeTime). - -You can save any type of data whether it be a string, array, object, etc. - - [php] - $array = array( - 'key1' => 'value1', - 'key2' => 'value2' - ); - $cacheDriver->save('my_array', $array); - -+++ Checking - -Checking whether some cache exists is very simple, just use the `contains()` method. -It accepts a single argument which is the ID of the cache entry. - - [php] - if ($cacheDriver->contains('cache_id')) { - echo 'cache exists'; - } else { - echo 'cache does not exist'; - } - -+++ Fetching - -Now if you want to retrieve some cache entry you can use the `fetch()` method. It -also accepts a single argument just like `contains()` which is the ID of the cache entry. - - [php] - $array = $cacheDriver->fetch('my_array'); - -+++ Deleting - -As you might guess, deleting is just as easy as saving, checking and fetching. -We have a few ways to delete cache entries. You can delete by an individual ID, -regular expression, prefix, suffix or you can delete all entries. - -++++ By Cache ID - - [php] - $cacheDriver->delete('my_array'); - -You can also pass wild cards to the `delete()` method and it will return an array -of IDs that were matched and deleted. - - [php] - $deleted = $cacheDriver->delete('users_*'); - -++++ By Regular Expression - -If you need a little more control than wild cards you can use a PHP regular -expression to delete cache entries. - - [php] - $deleted = $cacheDriver->deleteByRegex('/users_.*/'); - -++++ By Prefix - -Because regular expressions are kind of slow, if simply deleting by a prefix or -suffix is sufficient, it is recommended that you do that instead of using a regular -expression because it will be much faster if you have many cache entries. - - [php] - $deleted = $cacheDriver->deleteByPrefix('users_'); - -++++ By Suffix - -Just like we did above with the prefix you can do the same with a suffix. - - [php] - $deleted = $cacheDriver->deleteBySuffix('_my_account'); - -++++ All - -If you simply want to delete all cache entries you can do so with the `deleteAll()` -method. - - [php] - $deleted = $cacheDriver->deleteAll(); - -+++ Counting - -If you want to count how many entries are stored in the cache driver instance -you can use the `count()` method. - - [php] - echo $cacheDriver->count(); - -> **NOTE** -> In order to use `deleteByRegex()`, `deleteByPrefix()`, `deleteBySuffix()`, -> `deleteAll()`, `count()` or `getIds()` you must enable an option for the cache -> driver to manage your cache IDs internally. This is necessary because APC, -> Memcache, etc. don't have any advanced functionality for fetching and deleting. -> We add some functionality on top of the cache drivers to maintain an index of -> all the IDs stored in the cache driver so that we can allow more granular deleting -> operations. -> -> [php] -> $cacheDriver->setManageCacheIds(true); - -+++ Namespaces - -If you heavily use caching in your application and utilize it in multiple parts -of your application, or use it in different applications on the same server you -may have issues with cache naming collisions. This can be worked around by using -namespaces. You can set the namespace a cache driver should use by using the -`setNamespace()` method. - - [php] - $cacheDriver->setNamespace('my_namespace_'); - -++ Integrating with the ORM - -The Doctrine ORM package is tightly integrated with the cache drivers to allow -you to improve performance of various aspects of Doctrine by just simply making -some additional configurations and method calls. - -+++ Query Cache - -It is highly recommended that in a production environment you cache the -transformation of a DQL query to its SQL counterpart. It doesn't make sense to -do this parsing multiple times as it doesn't change unless you alter the DQL -query. - -This can be done by configuring the query cache implementation to use on your ORM -configuration. - - [php] - $config = new \Doctrine\ORM\Configuration(); - $config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache()); - -+++ Result Cache - -The result cache can be used to cache the results of your queries so that we -don't have to query the database or hydrate the data again after the first time. -You just need to configure the result cache implementation. - - [php] - $config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache()); - -Now when you're executing DQL queries you can configure them to use the result cache. - - [php] - $query = $em->createQuery('select u from \Entities\User u'); - $query->useResultCache(true); - -You can also configure an individual query to use a different result cache driver. - - [php] - $query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache()); - -> **NOTE** -> Setting the result cache driver on the query will automatically enable the -> result cache for the query. If you want to disable it pass false to -> `useResultCache()`. -> -> [php] -> $query->useResultCache(false); - -If you want to set the time the cache has to live you can use the `setResultCacheLifetime()` -method. - - [php] - $query->setResultCacheLifetime(3600); - -The ID used to store the result set cache is a hash which is automatically generated -for you if you don't set a custom ID yourself with the `setResultCacheId()` method. - - [php] - $query->setResultCacheId('my_custom_id'); - -You can also set the lifetime and cache ID by passing the values as the second -and third argument to `useResultCache()`. - - [php] - $query->useResultCache(true, 3600, 'my_custom_id'); - -+++ Metadata Cache - -Your class metadata can be parsed from a few different sources like YAML, XML, -Annotations, etc. Instead of parsing this information on each request we should -cache it using one of the cache drivers. - -Just like the query and result cache we need to configure it first. - - [php] - $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); - -Now the metadata information will only be parsed once and stored in the cache -driver. - -++ Clearing the Cache - -We've already shown you previously how you can use the API of the cache drivers -to manually delete cache entries. For your convenience we offer a command line task -for you to help you with clearing the query, result and metadata cache. - -From the Doctrine command line you can run the following command. - - $ ./doctrine clear-cache - -Running this task with no arguments will clear all the cache for all the configured -drivers. If you want to be more specific about what you clear you can use the -following options. - -To clear the query cache use the `--query` option. - - $ ./doctrine clear-cache --query - -To clear the metadata cache use the `--metadata` option. - - $ ./doctrine clear-cache --metadata - -To clear the result cache use the `--result` option. - - $ ./doctrine clear-cache --result - -When you use the `--result` option you can use some other options to be more -specific about what queries result sets you want to clear. - -Just like the API of the cache drivers you can clear based on an ID, regular -expression, prefix or suffix. - - $ ./doctrine clear-cache --result --id=cache_id - -Or if you want to clear based on a regular expressions. - - $ ./doctrine clear-cache --result --regex=users_.* - -Or with a prefix. - - $ ./doctrine clear-cache --result --prefix=users_ - -And finally with a suffix. - - $ ./doctrine clear-cache --result --suffix=_my_account - -> **NOTE** -> Using the `--id`, `--regex`, etc. options with the `--query` and `--metadata` -> are not allowed as it is not necessary to be specific about what you clear. -> You only ever need to completely clear the cache to remove stale entries. - -++ Cache Slams - -Something to be careful of when utilizing the cache drivers is cache slams. If -you have a heavily trafficked website with some code that checks for the existence -of a cache record and if it does not exist it generates the information and saves -it to the cache. Now if 100 requests were issued all at the same time and each one -sees the cache does not exist and they all try and insert the same cache entry -it could lock up APC, Xcache, etc. and cause problems. Ways exist to work around -this, like pre-populating your cache and not letting your users requests populate -the cache. - -You can read more about cache slams [here](http://t3.dotgnu.info/blog/php/user-cache-timebomb). \ No newline at end of file diff --git a/orm/manual/en/configuration.txt b/orm/manual/en/configuration.txt deleted file mode 100644 index b9a144bb3..000000000 --- a/orm/manual/en/configuration.txt +++ /dev/null @@ -1,407 +0,0 @@ -++ Bootstrapping - -Bootstrapping Doctrine is a relatively simple procedure that roughly exists of -just 2 steps: - -* Making sure Doctrine class files can be loaded on demand. -* Obtaining an EntityManager instance. - -+++ Class loading - -Lets start with the class loading setup. We need to set up some class loaders -(often called "autoloader") so that Doctrine class files are loaded on demand. -The Doctrine\Common namespace contains a very fast and minimalistic class loader -that can be used for Doctrine and any other libraries where the coding standards -ensure that a class's location in the directory tree is reflected by its name -and namespace and where there is a common root namespace. - -> **NOTE** -> You are not forced to use the Doctrine class loader to load Doctrine -> classes. Doctrine does not care how the classes are loaded, if you want to use a -> different class loader or your own to load Doctrine classes, just do that. -> Along the same lines, the class loader in the Doctrine\Common namespace is not -> meant to be only used for Doctrine classes, too. It is a generic class loader that can -> be used for any classes that follow some basic naming standards as described above. - -The following example shows the setup of a `ClassLoader` - -> **NOTE** -> This assumes you've created some kind of script to test the following code in. -> Something like a `test.php` file. - - [php] - // test.php - - require '/path/to/lib/Doctrine/Common/ClassLoader.php'; - $classLoader = new \Doctrine\Common\ClassLoader('Doctrine', '/path/to/Doctrine2/lib); - $classLoader->register(); // register on SPL autoload stack - -For best class loading performance it is recommended that you keep your include_path short, ideally it should only contain the path to the PEAR libraries, and any other class libraries should be registered with their full base path. - -+++ Obtaining an EntityManager - -Once you have prepared the class loading, you acquire an EntityManager instance -with the following minimalist configuration: - - [php] - use Doctrine\ORM\EntityManager, - Doctrine\ORM\Configuration; - - // ... - - $config = new Configuration; - $cache = new \Doctrine\Common\Cache\ApcCache; - $config->setMetadataCacheImpl($cache); - $config->setQueryCacheImpl($cache); - $config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); - $config->setProxyNamespace('MyProject\Proxies'); - $connectionOptions = array( - 'driver' => 'pdo_sqlite', - 'path' => 'database.sqlite' - ); - - $em = EntityManager::create($connectionOptions, $config); - -> **CAUTION** -> Do not use Doctrine without a metadata and query cache! Doctrine is highly -> optimized for working with caches. The main parts in Doctrine that are optimized -> for caching are the metadata mapping information with the metadata cache and the -> DQL to SQL conversions with the query cache. These 2 caches require only an absolute -> minimum of memory yet they heavily improve the runtime performance of Doctrine. -> The recommended cache driver to use with Doctrine is [APC](http://www.php.net/apc). -> APC provides you with an opcode-cache (which is highly recommended anyway) and -> a very fast in-memory cache storage that you can use for the metadata and query -> caches as seen in the previous code snippet. - -An EntityManager is your central access point to ORM functionality provided by Doctrine. - -++ Configuration Options - -The following sections describe all the configuration options available on a `Doctrine\ORM\Configuration` instance. - -+++ Proxy Directory (***REQUIRED***) - - [php] - $config->setProxyDir($dir); - $config->getProxyDir(); - -Gets or sets the directory where Doctrine generates any proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. - -+++ Proxy Namespace (***REQUIRED***) - - [php] - $config->setProxyNamespace($namespace); - $config->getProxyNamespace(); - -Gets or sets the namespace to use for generated proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. - -+++ Metadata Driver (***REQUIRED***) - - [php] - $config->setMetadataDriverImpl($driver); - $config->getMetadataDriverImpl(); - -Gets or sets the metadata driver implementation that is used by Doctrine to acquire the object-relational metadata for your classes. - -There are currently 4 available implementations: - - * `Doctrine\ORM\Mapping\Driver\AnnotationDriver` - * `Doctrine\ORM\Mapping\Driver\XmlDriver` - * `Doctrine\ORM\Mapping\Driver\YamlDriver` - * `Doctrine\ORM\Mapping\Driver\DriverChain` - -Throughout the most part of this manual the AnnotationDriver is used in the examples. For information on the usage of the XmlDriver or YamlDriver please refer to the dedicated chapters `XML Mapping` and `YAML Mapping`. - -The annotation driver is configured as the default metadata driver, using Annotations as Metadata source -does not require you to set this option explicitly. - -+++ Metadata Cache (***RECOMMENDED***) - - [php] - $config->setMetadataCacheImpl($cache); - $config->getMetadataCacheImpl(); - -Gets or sets the cache implementation to use for caching metadata information, that is, all the information you supply via annotations, xml or yaml, so that they do not need to be parsed and loaded from scratch on every single request which is a waste of resources. -The cache implementation must implement the `Doctrine\Common\Cache\Cache` interface. - -Usage of a metadata cache is highly recommended. - -The recommended implementations are: - - * `Doctrine\Common\Cache\ApcCache` - * `Doctrine\Common\Cache\MemcacheCache` - * `Doctrine\Common\Cache\XcacheCache` - -+++ Query Cache (***RECOMMENDED***) - - [php] - $config->setQueryCacheImpl($cache); - $config->getQueryCacheImpl(); - -Gets or sets the cache implementation to use for caching DQL queries, that is, the result of a DQL parsing process that includes the final SQL as well as meta information about how to process the SQL result set of a query. Note that the query cache does not affect query results. You do not get stale data. This is a pure optimization cache without any negative side-effects (except some minimal memory usage in your cache). - -Usage of a query cache is highly recommended. - -The recommended implementations are: - - * `Doctrine\Common\Cache\ApcCache` - * `Doctrine\Common\Cache\MemcacheCache` - * `Doctrine\Common\Cache\XcacheCache` - -+++ SQL Logger (***Optional***) - - [php] - $config->setSqlLogger($logger); - $config->getSqlLogger(); - -Gets or sets the logger to use for logging all SQL statements executed by Doctrine. The logger class must implement the `Doctrine\DBAL\Logging\SqlLogger` interface. A simple default implementation that logs to the standard output using `echo` and `var_dump` can be found at `Doctrine\DBAL\Logging\EchoSqlLogger`. - -+++ Auto-generating Proxy Classes (***OPTIONAL***) - - [php] - $config->setAutoGenerateProxyClasses($bool); - $config->getAutoGenerateProxyClasses(); - -Gets or sets whether proxy classes should be generated automatically at runtime by Doctrine. If set to `FALSE`, proxy classes must be generated manually through the doctrine command line task `generate-proxies`. The strongly recommended value for a production environment is `FALSE`. - -++ Connection Options - -The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array -or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the -DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The DBAL configuration is explained -in the [DBAL section](./../dbal). - -++ 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: - - /** - * @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 should 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; - } - - protected function _onPropertyChanged($propName, $oldValue, $newValue) - { - if ($this->_listeners) { - foreach ($this->_listeners as $listener) { - $listener->propertyChanged($this, $propName, $oldValue, $newValue); - } - } - } - } - -Then, in each property setter of this class or derived classes, you need to -invoke `_onPropertyChanged` as follows to notify listeners: - - [php] - data) { - $this->_onPropertyChanged('data', $this->data, $data); - $this->data = $data; - } - } - } - -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. - -++ Partial Objects - -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 `Query#HINT_FORCE_PARTIAL_LOAD` query hint as follows: - - [php] - $q = $em->createQuery("select u.id, u.name from MyApp\Domain\User u"); - $q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); - -+++ When should I force partial objects? - -Mainly for optimization purposes, especially since the stateless nature of PHP -applications means that any fields or objects that are loaded unnecessarily in -a request are useless (though often minimal) overhead. Be careful of premature optimization. Only force partial objects if it proves to provide an improvement to a performance problem. - -++ Proxy Objects - -A proxy object is an object that is put in place or used instead of the "real" object. A proxy object can add behavior to the object being proxied without that object being aware of it. In Doctrine 2, proxy objects are used to realize several features but mainly for transparent lazy-loading. - -Proxy objects with their lazy-loading facilities help to keep the subset of objects that are already in memory connected to the rest of the objects. This is an essential property as without it there would always be fragile partial objects at the outer edges of your object graph. - -Doctrine 2 implements a variant of the proxy pattern where it generates classes that extend your entity classes and adds lazy-loading capabilities to them. Doctrine can then give you an instance of such a proxy class whenever you request an object of the class being proxied. This happens in two situations: - -**Reference Proxies** - -The method `EntityManager#getReference($entityName, $identifier)` lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier. You could simply do this: - - [php] - // $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart - // $itemId comes from somewhere, probably a request parameter - $item = $em->getReference('MyProject\Model\Item', $itemId); - $cart->addItem($item); - -Here, we added an Item to a Cart without loading the Item from the database. If you invoke any method on the Item instance, it would fully initialize its state transparently from the database. Here $item is actually an instance of the proxy class that was generated for the Item class but your code does not need to care. In fact it **should not care**. Proxy objects should be transparent to your code. - -**Association proxies** - -The second most important situation where Doctrine uses proxy objects is when querying for objects. Whenever you query for an object that has a single-valued association to another object that is configured LAZY, without joining that association in the same query, Doctrine puts proxy objects in place where normally the associated object would be. -Just like other proxies it will transparently initialize itself on first access. - -> **NOTE** -> Joining an association in a DQL or native query essentially means eager loading of that -> association in that query. This will override the 'fetch' option specified in -> the mapping for that association, but only for that query. - -+++ Generating Proxy classes - -Proxy classes can either be generated manually through the Doctrine CLI or automatically by Doctrine. The configuration option that controls this behavior is: - - [php] - $config->setAutoGenerateProxyClasses($bool); - $config->getAutoGenerateProxyClasses(); - -The default value is `TRUE` for convenient development. However, this setting is not optimal for performance and therefore not recommended for a production environment. -To eliminate the overhead of proxy class generation during runtime, set this configuration option to `FALSE`. When you do this in a development environment, note that you may get class/file not found errors if certain proxy classes are not available or failing lazy-loads if new methods were added to the entity class that are not yet in the proxy class. In such a case, simply use the Doctrine CLI to (re)generate the proxy classes like so: - - doctrine orm:generate-proxies - -++ Multiple Metadata Sources - -When using different components using Doctrine 2 you may end up with them using two different metadata drivers, -for example XML and YAML. You can use the DriverChain Metadata implementations to aggregate these drivers -based on namespaces: - - [php] - $chain = new DriverChain(); - $chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company'); - $chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping'); - -Based on the namespace of the entity the loading of entities is delegated to the appropriate driver. The chain -semantics come from the fact that the driver loops through all namespaces and matches the entity class name -against the namespace using a `strpos() === 0` call. This means you need to order the drivers correctly if -sub-namespaces use different metadata driver implementations. \ No newline at end of file diff --git a/orm/manual/en/dql-doctrine-query-language.txt b/orm/manual/en/dql-doctrine-query-language.txt deleted file mode 100755 index 87aa54a93..000000000 --- a/orm/manual/en/dql-doctrine-query-language.txt +++ /dev/null @@ -1,803 +0,0 @@ -++ DQL Explained - -DQL stands for **D**octrine **Q**uery **L**anguage and is an Object Query Language derivate that is very similar to the **H**ibernate **Q**uery **L**anguage (HQL) or the **J**ava **P**ersistence **Q**uery **L**anguage (JPQL). - -In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects. - -> **CAUTION** -> A common mistake for beginners is to mistake DQL for being just some form of SQL -> and therefore trying to use table names and column names or join arbitrary tables -> together in a query. You need to think about DQL as a query language for your object -> model, not for your relational schema. - -DQL is case in-sensitive, except for namespace, class and field names, which are case sensitive. - - -++ Types of DQL queries - -DQL as a query language has SELECT, UPDATE and DELETE constructs that map to their corresponding -SQL statement types. INSERT statements are not allowed in DQL, because entities and their relations -have to be introduced into the persistence context through `EntityManager#persist()` to ensure -consistency of your object model. - -DQL SELECT statements are a very powerful way of retrieving parts of your domain model that are -not accessible via assocations. Additionally they allow to retrieve entities and their associations -in one single sql select statement which can make a huge difference in performance in contrast -to using several queries. - -DQL UPDATE and DELETE statements offer a way to execute bulk changes on the entities of your -domain model. This is often necessary when you cannot load all the affected entities of a bulk -update into memory. - -++ SELECT queries - -+++ DQL SELECT clause - -The select clause of a DQL query specifies what appears in the query result. The composition of all the expressions in the select clause also influences the nature of the query result. - -Here is an example that selects all users with an age > 20: - - [sql] - SELECT u FROM MyProject\Model\User u WHERE u.age > 20 - -Lets examine the query: - -* `u` is a so called identification variable or alias that refers to the `MyProject\Model\User` class. By placing this alias in the SELECT clause we specify that we want all instances of the User class that are matched by this query appear in the query result. -* The FROM keyword is always followed by a fully-qualified class name which in turn is followed by an identification variable or alias for that class name. This class designates a root of our query from which we can navigate further via joins (explained later) and path expressions. -* The expression `u.age` in the WHERE clause is a path expression. Path expressions in DQL are easily identified by the use of the '.' operator that is used for constructing paths. The path expression `u.age` refers to the `age` field on the User class. - -The result of this query would be a list of User objects where all users are older than 20. - -The SELECT clause allows to specify both class identification variables that signal the hydration -of a complete entity class or just fields of the entity using the syntax `u.name`. -Combinations of both are also allowed and it is possible to wrap both fields and -identification values into aggregation and DQL functions. Numerical fields can -be part of computations using mathematical operatos. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn) -on more information. - -+++ Joins - -A SELECT query can contain joins. There are 2 types of JOINs: "Regular" Joins and "Fetch" Joins. - -**Regular Joins**: Used to limit the results and/or compute aggregate values. - -**Fetch Joins**: In addition to the uses of regular joins: Used to fetch related entities and include them in the hydrated result of a query. - -There is no special DQL keyword that distinguishes a regular join from a fetch join. A join (be it an inner or outer join) becomes a "fetch join" as soon as fields of the joined entity appear in the SELECT part of the DQL query outside of an aggregate function. Otherwise its a "regular join". - -Example: - -Regular join of the address: - - [sql] - SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin' - -Fetch join of the address: - - [sql] - SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin' - -When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the -root level of the result array. In the previous example an array of User instances is returned -and the address of each user is fetched and hydrated into the `User#address` variable. If you access -the address Doctrine does not need to - -> **NOTE** -> Doctrine allows you to walk all the assocations between all the objects in your domain model. -> Objects that were not already loaded from the database are replaced with lazy load proxy instances. -> Non-loaded Collections are also replaced by lazy-load instances that fetch all the contained objects upon -> first access. However relying on the lazy-load mechanism leads to many small queries executed -> against the database, which can significantly affect the performance of your application. -> **Fetch Joins** are the solution to hydrate most or all of the entities that you -> need in a single SELECT query. - -+++ Named and Positional Parameters - -DQL supports both named and positional parameters, however in contrast to many SQL dialects -positional parameters are specified with numbers, for example "?1", "?2" and so on. -Named parameters are specified with ":name1", ":name2" and so on. - -+++ DQL SELECT Examples - -This section contains a large set of DQL queries and some explainations of what is happening. -The actual result also depends on the hydration mode. - - [sql] - -- Hydrate all Users - SELECT u FROM MyProject\Model\User u - - -- Retrieve the IDs of all CmsUsers - SELECT u.id FROM CmsUser u - - -- Retrieve the IDs of all users that have written an article - SELECT DISTINCT a.user.id FROM CmsArticle a - - -- Retrieve all articles and sort them by the name of the articles users instance - SELECT a FROM CmsArticle a ORDER BY a.user.name ASC - - -- Retrieve the Username and Name of a CmsUser - SELECT u.username, u.name FROM CmsUser u - - -- Retrieve a ForumUser and his single associated entity - SELECT u, a FROM ForumUser u JOIN u.avatar a - - -- Retrieve a CmsUser and fetch join all the phonenumbers he has - SELECT u, p FROM CmsUser u JOIN u.phonenumbers p - - -- Hydrate a result in Ascending or Descending Order - SELECT u FROM ForumUser u ORDER BY u.id ASC - SELECT u FROM ForumUser u ORDER BY u.id DESC - - -- Using Aggregate Functions - SELECT COUNT(u.id) FROM CmsUser u GROUP BY u.id - - -- With WHERE Clause and Positional Parameter - SELECT u FROM ForumUser u WHERE u.id = ?1 - - -- With WHERE Clause and Named Parameter - SELECT u FROM ForumUser u WHERE u.username = :name - - -- With Nested Conditions in WHERE Clause - SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id - - -- With COUNT DISTINCT - SELECT COUNT(DISTINCT u.name) FROM CmsUser - - -- With Arithmetic Expression in WHERE clause - SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000 - - -- Using multiple classes fetched using a FROM clause (all returned on the root level of the result) - SELECT u, a FROM CmsUser u, CmsArticle a WHERE u.id = a.user.id - - -- Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids - SELECT u.id, a.id FROM CmsUser u LEFT JOIN u.articles a - - -- Restricting a JOIN clause by additional conditions - SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%' - - -- Using several Fetch JOINs - SELECT u, a, p, c FROM CmsUser u - JOIN u.articles a - JOIN u.phonenumbers p - JOIN a.comments c - - -- BETWEEN in WHERE clause - SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2 - - -- DQL Functions in WHERE clause - SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone' - - -- IN() Expression - SELECT u.name FROM CmsUser u WHERE u.id IN(46) - SELECT u FROM CmsUser u WHERE u.id IN (1, 2) - SELECT u FROM CmsUser u WHERE u.id NOT IN (1) - - -- CONCAT() DQL Function - SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1 - SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1 - - -- EXISTS in WHERE clause with correlated Subquery - SELECT u.id FROM CmsUser u - WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.phonenumber = u.id) - - -- Get all users who are members of $group. - SELECT u.id FROM CmsUser u WHERE :param MEMBER OF u.groups - - -- Get all users that have more than 1 phonenumber - SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1 - - -- Get all users that have no phonenumber - SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY - -+++ Using INDEX BY - -The INDEX BY construct is nothing that directly translates into SQL but that affects -object and array hydration. After each FROM and JOIN clause you specify by which field -this class should be indexed in the result. By default a result is incremented -by numerical keys starting with 0. However with INDEX BY you can specifiy any -other column to be the key of your result, it really only makes sense with primary -or unique fields though: - - [sql] - SELECT u.id, u.status, upper(u.name) nameUpper FROM User u INDEX BY u.id - JOIN u.phonenumbers p INDEX BY p.phonenumber - -Returns an array of the following kind, indexed by both user-id then phonenumber-id: - - array - 0 => - array - 1 => - object(stdClass)[299] - public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) - public 'id' => int 1 - .. - 'nameUpper' => string 'ROMANB' (length=6) - 1 => - array - 2 => - object(stdClass)[298] - public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) - public 'id' => int 2 - ... - 'nameUpper' => string 'JWAGE' (length=5) - -++ UPDATE queries - -DQL not only allows to select your Entities using field names, you can also execute bulk updates on a set -of entities using an DQL-UPDATE query. The Syntax of an UPDATE query works as expected, as the following -example shows: - - UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) - -References to related entities are only possible in the WHERE clause and using subselects. - -> **CAUTION** -> DQL UPDATE statements are ported directly into a Database UPDATE statement and therefore bypass -> any locking scheme and do not increment the version column. Entities that are already -> loaded into the persistence context will *NOT* be synced with the updated database state. It -> is recommended to call `EntityManager#clear()` and retrieve new instances of any affected entity. - -++ DELETE queries - -DELETE queries can also be specified using DQL and their syntax is as simple as the UPDATE syntax: - - DELETE MyProject\Model\User u WHERE u.id = 4 - -The same restrictions apply for the reference of related entities. - -> **CAUTION** -> DQL DELETE statements are ported directly into a Database DELETE statement and therefore -> bypass any checks for the version column if they are not explicitly added to the WHERE -> clause of the query. Additionally Deletes of specifies entities are *NOT* cascaded -> to related entities even if specified in the metadata. - -++ Functions, Operators, Aggregates - - -+++ DQL Functions - -The following functions are supported in SELECT, WHERE and HAVING clauses: - -* ABS(arithmetic_expression) -* CONCAT(str1, str2) -* CURRENT_DATE() - Return the current date -* CURRENT_TIME() - Returns the current time -* CURRENT_TIMESTAMP() - Returns a timestamp of the current date and time. -* LENGTH(str) - Returns the length of the given string -* LOCATE(needle, haystack [, offset]) - Locate the first occurance of the substring in the string. -* LOWER(str) - returns the string lowercased. -* MOD(a, b) - Return a MOD b. -* SIZE(collection) - Return the number of elements in the specified collection -* SQRT(q) - Return the squareroot of q. -* SUBSTRING(str, start [, length]) - Return substring of given string. -* TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str) - Trim the string by the given trim char, defaults to whitespaces. -* UPPER(str) - Return the upper-case of the given string. - -+++ Arithmetic operators - -You can do math in DQL using numeric values, for example: - - SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 - -+++ Aggregate Functions - -The following aggregate functions are allowed in SELECT and GROUP BY clauses: AVG, COUNT, MIN, MAX, SUM - -+++ Other Expressions - -DQL offers a wide-range of additional expressions that are known from SQL, here is a list of -all the supported constructs: - -* `ALL/ANY/SOME` - Used in a WHERE clause followed by a subselect this works like the equivalent constructs in SQL. -* `BETWEEN a AND b` and `NOT BETWEEN a AND b` can be used to match ranges of arithmetic values. -* `IN (x1, x2, ...)` and `NOT IN (x1, x2, ..)` can be used to match a set of given values. -* `LIKE ..` and `NOT LIKE ..` match parts of a string or text using % as a wildcard. -* `IS NULL` and `IS NOT NULL` to check for null values -* `EXISTS` and `NOT EXISTS` in combination with a subselect - -+++ Adding your own functions to the DQL language - -By default DQL comes with functions that are part of a large basis of underlying databases. However you will most likely -choose a database platform at the beginning of your project and most likely never change it. For this cases you can -easily extend the DQL parser with own specialized platform functions. - -You can register custom DQL functions in your ORM Configuration: - - [php] - $config = new \Doctrine\ORM\Configuration(); - $config->addCustomStringFunction($name, $class); - $config->addCustomNumericFunction($name, $class); - $config->addCustomDatetimeFunction($name, $class); - - $em = EntityManager::create($dbParams, $config); - -The functions have to return either a string, numeric or datetime value depending on the registered function type. As an example -we will add a MySQL specific FLOOR() functionality. All the given classes have to implement the base class -\Doctrine\ORM\Query\AST\Functions\FunctionNode: - - [php] - namespace MyProject\Query\AST; - - use \Doctrine\ORM\Query\AST\Functions\FunctionsNode; - - class MysqlFloor extends FunctionNode - { - public $simpleArithmeticExpression; - - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) - { - return 'FLOOR(' . $sqlWalker->walkSimpleArithmeticExpression( - $this->simpleArithmeticExpression - ) . ')'; - } - - public function parse(\Doctrine\ORM\Query\Parser $parser) - { - $lexer = $parser->getLexer(); - - $parser->match(Lexer::T_ABS); - $parser->match(Lexer::T_OPEN_PARENTHESIS); - - $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); - - $parser->match(Lexer::T_CLOSE_PARENTHESIS); - } - } - -We will register the function by calling and can then use it: - - [php] - \Doctrine\ORM\Query\Parser::registerNumericFunction('FLOOR', 'MyProject\Query\MysqlFloor'); - $dql = "SELECT FLOOR(person.salary * 1.75) FROM CompanyPerson person"; - -++ The Query class - -An instance of the `Doctrine\ORM\Query` class represents a DQL query. You create a Query instance be calling `EntityManager#createQuery($dql)`, passing the DQL query string. Alternatively you can create an empty `Query` instance and invoke `Query#setDql($dql)` afterwards. Here are some examples: - - [php] - // $em instanceof EntityManager - - // example1: passing a DQL string - $q = $em->createQuery('select u from MyProject\Model\User u'); - - // example2: usin setDql - $q = $em->createQuery(); - $q->setDql('select u from MyProject\Model\User u'); - - -+++ Query Result Formats - -The format in which the result of a DQL SELECT query is returned can be influenced by a so-called `hydration mode`. A hydration mode specifies a particular way in which an SQL result set is transformed. Each hydration mode has its own dedicated method on the Query class. Here they are: - -* `Query#getResult()`: Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array where the objects are nested in the result rows (mixed). -* `Query#getSingleResult()`: Retrieves a single object. If the result contains more than one object, an exception is thrown. The pure/mixed distinction does not apply. -* `Query#getArrayResult()`: Retrieves an array graph (a nested array) that is largely interchangeable with the object graph generated by `Query#getResultList()` for read-only purposes. - -> **NOTE** -> An array graph can differ from the corresponding object graph in -> certain scenarios due to the difference of the identity semantics between arrays and -> objects. - -* `Query#getScalarResult()`: Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The pure/mixed distinction does not apply. -* `Query#getSingleScalarResult()`: Retrieves a single scalar value from the result returned by the dbms. If the result contains more than a single scalar value, an exception is thrown. The pure/mixed distinction does not apply. - -Instead of using these methods, you can alternatively use the general-purpose method `Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)`. Using this method you can directly supply the hydration mode as the second parameter via one of the Query constants. In fact, the methods mentioned earlier are just convenient shortcuts for the execute method. For example, the method `Query#getResultList()` internally invokes execute, passing in `Query::HYDRATE_OBJECT` as the hydration mode. - -The use of the methods mentioned earlier is generally preferred as it leads to more concise code. - -+++ Pure and Mixed Results - -The nature of a result returned by a DQL SELECT query retrieved through `Query#getResult()` or `Query#getArrayResult()` can be of 2 forms: **pure** and **mixed**. In the previous simple examples, you already saw a "pure" query result, with only objects. By default, the result type is **pure** but **as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. A mixed result has a different structure than a pure result in order to accomodate for the scalar values. - -A pure result usually looks like this: - - array - [0] => Object - [1] => Object - [2] => Object - ... - -A mixed result on the other hand has the following general structure: - - array - array - [0] => Object - [1] => "some scalar string" - ['count'] => 42 - // ... more scalar values, either indexed numerically or with a name - array - [0] => Object - [1] => "some scalar string" - ['count'] => 42 - // ... more scalar values, either indexed numerically or with a name - -To better understand mixed results, consider the following DQL query: - - [sql] - SELECT u, UPPER(u.name) nameUpper FROM MyProject\Model\User u - -This query makes use of the `UPPER` DQL function that returns a scalar value and because there is now a scalar value in the SELECT clause, we get a mixed result. - -Here is how the result could look like: - - array - array - [0] => User (Object) - ['nameUpper'] => "Roman" - array - [0] => User (Object) - ['nameUpper'] => "Jonathan" - ... - -And here is how you would access it in PHP code: - - [php] - foreach ($results as $row) { - echo "Name: " . $row[0]->getName(); - echo "Name UPPER: " . $row['nameUpper']; - } - -You may have observed that in a mixed result, the object always ends up on index 0 of a result row. - -+++ Hydration Mode Asumptions - -Each of the Hydration Modes makes assumptions about how the result is returned to userland. You should -know about all the details to make best use of the different result formats: - -++++ Object Hydration - -++++ Array Hydration - -++++ Scalar Hydration Details - -The following assumptions are made about selected fields using Scalar Hydration: - -1. Fields from classes are prefixed by the DQL alias in the result. A query of the kind 'SELECT u.name ..' returns a key 'u_name' in the result rows. - -+++ Iterating Large Resultsets - -There are situations when a query you want to execute returns a very large result-set that needs -to be processed. All the previously described hydration modes completly load a result-set into -memory which might not be feasible with large result sets. See the [Batch Processing](batch-processing) -section on details how to iterate large result sets. - -+++ Functions - -The following methods exist on the `AbstractQuery` which both `Query` and `NativeQuery` extend from. - -++++ Parameters - -Prepared Statements that use numerical or named wildcards require additional parameters to be executable -agains the database. To pass parameters to the query the following methods can be used: - -* `AbstractQuery::setParameter($param, $value)` - Set the numerical or named wildcard to the given value. -* `AbstractQuery::setParameters(array $params)` - Set an array of parameter key-value pairs. -* `AbstractQuery::getParameter($param)` -* `AbstractQuery::getParameters()` - -++++ Cache related API - -You can cache query results based either on all variables that define the result (SQL, Hydration Mode, Parameters and Hints) -or on user-defined cache keys. However by default query results are not cached at all. You have to enable -the result cache on a per query basis. The following example shows a complete workflow using the Result Cache -API: - - [php] - $query = $em->createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); - $query->setParameter(1, 12); - - $query->setResultCacheDriver(new ApcCache()); - - $query->useResultCache(true) - ->setResultCacheLifeTime($seconds = 3600); - - $result = $query->getResult(); // cache miss - - $query->expireResultCache(true); - $result = $query->getResult(); // forced expire, cache miss - - $query->setResultCacheId('my_query_result'); - $result = $query->getResult(); // saved in given result cache id. - - // or call useResultCache() with all parameters: - $query->useResultCache(true, $seconds = 3600, 'my_query_result'); - $result = $query->getResult(); // cache hit! - -> **TIP!** -> You can set the Result Cache Driver globally on the `Doctrine\ORM\Configuration` instance -> so that it is passed to every `Query` and `NativeQuery` instance. - -++++ Query Hints - -You can pass hints to the query parser and hydrators by using the `AbstractQuery::setHint($name, $value)` method. -Currently there exist mostly internal query hints that are not be consumed in userland. However the following few hints -are to be used in userland: - -* Query::HINT_FORCE_PARTIAL_LOAD - Allows to hydrate objects although not all their columns are fetched. This query -hint can be used to handle memory consumption problems with large result-sets that contain char or binary data. -Doctrine has no way of implicitly reloaded this data. Partially loaded objects have to be passed to -`EntityManager::refresh()` if they are to be reloaded fully from the database. -* Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland aswell. -If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the -fields of the existing entity will be refreshed. In normal operation a result-set that loads data of an already existing -entity is discarded in favour of the already existing entity. -* Query::HINT_CUSTOM_TREE_WALKERS - An array of additional `Doctrine\ORM\Query\TreeWalker` instances that are attached -to the DQL query parsing process. - -++++ Query Cache (DQL Query Only) - -Parsing a DQL query and converting it into an SQL query against the underlying database platform obviously has some overhead -in contrast to directly executing Native SQL queries. That is why there is a dedicated Query Cache for caching the -DQL parser results. In combination with the use of wildcards you can reduce the number of parsed queries in production -to zero. - -The Query Cache Driver is passed from the `Doctrine\ORM\Configuration` instance to each `Doctrine\ORM\Query` instance -by default and is also enabled by default. This also means you don't regularly need to fiddle with the parameters -of the Query Cache, however if you do there are several methods to interact with it: - -* `Query::setQueryCacheDriver($driver)` - Allows to set a Cache instance -* `Query::setQueryCacheLifeTime($seconds = 3600)` - Set lifetime of the query caching. -* `Query::expireQueryCache($bool)` - Enforce the expiring of the query cache if set to true. -* `Query::getExpireQueryCache()` -* `Query::getQueryCacheDriver()` -* `Query::getQueryCacheLifeTime()` - -++++ First and Max Result Items (DQL Query Only) - -You can limit the number of results returned from a DQL query aswell as specify the starting offset, Doctrine -then uses a strategy of manipulating the select query to return only the requested number of results: - -* `Query::setMaxResults($maxResults)` -* `Query::setFirstResult($offset)` - -> **NOTE** -> If your query contains a fetch-joined collection specifying the result limit methods are not working -> as you would expect. Set Max Results restricts the number of database result rows, however in the -> case of fetch-joined collections one root entity might appear in many rows, effectivly hydrating -> less than the specified number of results. - -++ EBNF - -The following context-free grammar, written in an EBNF variant, describes the Doctrine Query Language. You can consult this grammar whenever you are unsure about what is possible with DQL or what the correct syntax for a particular query should be. - -+++ Document syntax: - -* non-terminals begin with an upper case character -* terminals begin with a lower case character -* parentheses (...) are used for grouping -* square brackets [...] are used for defining an optional part, eg. zero or one time -* curly brackets {...} are used for repetion, eg. zero or more times -* double quotation marks "..." define a terminal string a vertical bar | represents an alternative - -+++ Terminals - -* identifier (name, email, ...) -* string ('foo', 'bar''s house', '%ninja%', ...) -* char ('/', '\\', ' ', ...) -* integer (-1, 0, 1, 34, ...) -* float (-0.23, 0.007, 1.245342E+8, ...) -* boolean (false, true) - -+++ Query Language - - QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement - -+++ Statements - - SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] - UpdateStatement ::= UpdateClause [WhereClause] - DeleteStatement ::= DeleteClause [WhereClause] - -+++ Identifiers - - /* Alias Identification usage (the "u" of "u.name") */ - IdentificationVariable ::= identifier - - /* Alias Identification declaration (the "u" of "FROM User u") */ - AliasIdentificationVariable :: = identifier - - /* identifier that must be a class name (the "User" of "FROM User u") */ - AbstractSchemaName ::= identifier - - /* identifier that must be a field (the "name" of "u.name") */ - /* This is responsable to know if the field exists in Object, no matter if it's a relation or a simple field */ - FieldIdentificationVariable ::= identifier - - /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ - CollectionValuedAssociationField ::= FieldIdentificationVariable - - /* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */ - SingleValuedAssociationField ::= FieldIdentificationVariable - - /* identifier that must be an embedded class state field (for the future) */ - EmbeddedClassStateField ::= FieldIdentificationVariable - - /* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ - /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ - SimpleStateField ::= FieldIdentificationVariable - - /* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */ - AliasResultVariable = identifier - - /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ - ResultVariable = identifier - -+++ Path Expressions - - /* "u.Group" or "u.Phonenumbers" declarations */ - JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) - - /* "u.Group" or "u.Phonenumbers" usages */ - AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression - - /* "u.name" or "u.Group" */ - SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression - - /* "u.name" or "u.Group.name" */ - StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField - - /* "u.Group" */ - SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField - - /* "u.Group.Permissions" */ - CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField - - /* "name" */ - StateField ::= {EmbeddedClassStateField "."}* SimpleStateField - - /* "u.name" or "u.address.zip" (address = EmbeddedClassStateField) */ - SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField - - -+++ Clauses - - SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* - SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression - UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* - DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable - FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* - SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* - WhereClause ::= "WHERE" ConditionalExpression - HavingClause ::= "HAVING" ConditionalExpression - GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* - OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* - Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] - - -+++ Items - - UpdateItem ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue - OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] - GroupByItem ::= IdentificationVariable | SingleValuedPathExpression - NewValue ::= ScalarExpression | SimpleEntityExpression | "NULL" - - -+++ From, Join and Index by - - IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* - SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) - JoinVariableDeclaration ::= Join [IndexBy] - RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable - Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression - ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] - IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression - - -+++ Select Expressions - - SelectExpression ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")" | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable] - SimpleSelectExpression ::= ScalarExpression | IdentificationVariable | - (AggregateExpression [["AS"] AliasResultVariable]) - PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet - PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" - - -+++ Conditional Expressions - - ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* - ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* - ConditionalFactor ::= ["NOT"] ConditionalPrimary - ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" - SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression | - InExpression | NullComparisonExpression | ExistsExpression | - EmptyCollectionComparisonExpression | CollectionMemberExpression - - -+++ Collection Expressions - - EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" - CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression - -+++ Literal Values - - Literal ::= string | char | integer | float | boolean - InParameter ::= Literal | InputParameter - -+++ Input Parameter - - InputParameter ::= PositionalParameter | NamedParameter - PositionalParameter ::= "?" integer - NamedParameter ::= ":" string - - -+++ Arithmetic Expressions - - ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" - SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* - ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* - ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary - ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" - | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings - | FunctionsReturningDatetime | IdentificationVariable | InputParameter - - -+++ Scalar and Type Expressions - - ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression - BooleanPrimary | CaseExpression | EntityTypeExpression - CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | - CoalesceExpression | NullifExpression - GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression - "END" - WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression - SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* - "ELSE" ScalarExpression "END" - CaseOperand ::= StateFieldPathExpression | TypeDiscriminator - SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression - CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" - NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" - StringExpression ::= StringPrimary | "(" Subselect ")" - StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression - BooleanExpression ::= BooleanPrimary | "(" Subselect ")" - BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter - EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression - SimpleEntityExpression ::= IdentificationVariable | InputParameter - DatetimeExpression ::= DatetimePrimary | "(" Subselect ")" - DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression - - -+++ Aggregate Expressions - - AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | - "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" - -+++ Other Expressions - -QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS - - QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" - BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression - ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) - InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" - LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] - NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" - ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" - ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" - -+++ Functions - - FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime - - FunctionsReturningNumerics ::= - "LENGTH" "(" StringPrimary ")" | - "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | - "ABS" "(" SimpleArithmeticExpression ")" | "SQRT" "(" SimpleArithmeticExpression ")" | - "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | - "SIZE" "(" CollectionValuedPathExpression ")" - - FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" - - FunctionsReturningStrings ::= - "CONCAT" "(" StringPrimary "," StringPrimary ")" | - "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | - "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | - "LOWER" "(" StringPrimary ")" | - "UPPER" "(" StringPrimary ")" - - - - - - - diff --git a/orm/manual/en/events.txt b/orm/manual/en/events.txt deleted file mode 100644 index f8f848563..000000000 --- a/orm/manual/en/events.txt +++ /dev/null @@ -1,304 +0,0 @@ -Doctrine 2 features a lightweight event system that is part of the Common package. - -++ The Event System - -The event system is controlled by the `EventManager`. It is the central point -of Doctrine's event listener system. Listeners are registered on the manager -and events are dispatched through the manager. - - [php] - $evm = new EventManager(); - -Now we can add some event listeners to the `$evm`. Let's create a `EventTest` class -to play around with. - - [php] - class EventTest - { - const preFoo = 'preFoo'; - const postFoo = 'postFoo'; - - private $_evm; - - public $preFooInvoked = false; - public $postFooInvoked = false; - - public function __construct($evm) - { - $evm->addEventListener(array(self::preFoo, self::postFoo), $this); - } - - public function preFoo(EventArgs $e) - { - $this->preFooInvoked = true; - } - - public function postFoo(EventArgs $e) - { - $this->postFooInvoked = true; - } - } - - // Create a new instance - $test = new EventTest($evm); - -Events can be dispatched by using the `dispatchEvent()` method. - - [php] - $evm->dispatchEvent(EventTest::preFoo); - $evm->dispatchEvent(EventTest::postFoo); - -You can easily remove a listener with the `removeEventListener()` method. - - [php] - $evm->removeEventListener(array(self::preFoo, self::postFoo), $this); - -The Doctrine 2 event system also has a simple concept of event subscribers. We -can define a simple `TestEventSubscriber` class which implements the -`\Doctrine\Common\EventSubscriber` interface and implements a `getSubscribedEvents()` -method which returns an array of events it should be subscribed to. - - [php] - class TestEventSubscriber implements \Doctrine\Common\EventSubscriber - { - const preFoo = 'preFoo'; - - public $preFooInvoked = false; - - public function preFoo() - { - $this->preFooInvoked = true; - } - - public function getSubscribedEvents() - { - return array(self::preFoo); - } - } - - $eventSubscriber = new TestEventSubscriber(); - $evm->addEventSubscriber($eventSubscriber); - -Now when you dispatch an event any event subscribers will be notified for that event. - - [php] - $evm->dispatchEvent(TestEventSubscriber::preFoo); - -Now the test the `$eventSubscriber` instance to see if the `preFoo()` method was invoked. - - [php] - if ($eventSubscriber->preFooInvoked) { - echo 'pre foo invoked!'; - } - -++ Lifecycle Events - -The EntityManager and UnitOfWork trigger a bunch of events during the life-time of their registered entities. - -* preRemove - The preRemove event occurs for a given entity before the respective EntityManager remove operation for that entity is executed. -* postRemove - The postRemove event occurs for an entity after the entity has been deleted. It will be invoked after the database delete operations. -* prePersist - The prePersist event occurs for a given entity before the respective EntityManager persist operation for that entity is executed. -* postPersist - The postPersist event occurs for an entity after the entity has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event. -* preUpdate - The preUpdate event occurs before the database update operations to entity data. -* postUpdate - The postUpdate event occurs after the database update operations to entity data. -* postLoad - The postLoad event occurs for an entity after the entity has been loaded into the current EntityManager from the database or after the refresh operation has been applied to it. -* loadClassMetadata - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (annotations/xml/yaml). -* onFlush - The onFlush event occours after the change-sets of all managed entities are computed. This event is not a lifecycle callback. - -> **NOTE** -> Note that the postLoad event occurs for an entity before any associations have been -> initialized. Therefore it is not safe to access associations in a postLoad callback -> or event handler. - -You can access the Event constants from the `Events` class in the ORM package. - - [php] - use Doctrine\ORM\Events; - echo Events::preUpdate; - -These can be hooked into by two different types of event listeners: - -* Lifecycle Callbacks are methods on the entity classes that are called when the event is triggered. They recieve absolutely no arguments and are specifically designed to allow changes inside the entity classes state. -* Lifecycle Event Listeners are classes with specific callback methods that recieves some kind of `EventArgs` instance which give access to the entity, EntityManager or other relevant data. - -> **NOTE** -> All Lifecycle events that happen during the `flush()` of an EntityManager have very specific constraints on the allowed -> operations that can be executed. Please read the *Implementing Event Listeners* section very carefully to understand -> which operations are allowed in which lifecycle event. - -++ Lifecycle Callbacks - -A lifecycle event is a regular event with the additional feature of providing -a mechanism to register direct callbacks inside the corresponding entity classes -that are executed when the lifecycle event occurs. - - [php] - - /** @Entity @HasLifecycleCallbacks */ - class User - { - // ... - - /** - * @Column(type="string", length=255) - */ - public $value; - - /** @Column(name="created_at", type="string", length=255) */ - private $createdAt; - - /** @PrePersist */ - public function doStuffOnPrePersist() - { - $this->createdAt = date('Y-m-d H:m:s'); - } - - /** @PrePersist */ - public function doOtherStuffOnPrePersist() - { - $this->value = 'changed from prePersist callback!'; - } - - /** @PostPersist */ - public function doStuffOnPostPersist() - { - $this->value = 'changed from postPersist callback!'; - } - - /** @PostLoad */ - public function doStuffOnPostLoad() - { - $this->value = 'changed from postLoad callback!'; - } - - /** @PreUpdate */ - public function doStuffOnPreUpdate() - { - $this->value = 'changed from preUpdate callback!'; - } - } - -Note that when using annotations you have to apply the @HasLifecycleCallbacks marker annotation on the entity class. - -If you want to register lifecycle callbacks from YAML or XML you can do it with -the following. - - [yml] - User: - type: entity - fields: - # ... - name: - type: string(50) - lifecycleCallbacks: - doStuffOnPrePersist: prePersist - doStuffOnPostPersist: postPersist - -XML would look something like this: - - [xml] - - - - - - - - - - - - - - - -You just need to make sure a public `doStuffOnPrePersist()` and `doStuffOnPostPersist()` method is defined on your `User` model. - - [php] - // ... - - class User - { - // ... - - public function doStuffOnPrePersist() - { - // ... - } - - public function doStuffOnPostPersist() - { - // ... - } - } - -The `key` of the lifecycleCallbacks is the name of the method and the value is -the event type. The allowed event types are the ones listed in the previous Lifecycle Events section. - -++ Listening to Lifecycle Events - -Lifecycle event listeners are much more powerful than the simple lifecycle callbacks that are defined on the entity -classes. They allow to implement re-usable behaviours between different entity classes, yet require much more detailed -knowledge about the inner workings of the EntityManager and UnitOfWork. Please read the *Implementing Event Listeners* -section carefully if you are trying to write your own listener. - -To register an event listener you have to hook it into the EventManager that is passed to the EntityManager factory: - - [php] - $eventManager = new EventManager(); - $eventManager->addEventListener(array(Events::preUpdate), MyEventListener()); - $eventManager->addEventSubscriber(new MyEventSubscriber()); - - $entityManager = EntityManager::create($dbOpts, $config, $eventManager); - -You can also retrieve the event manager instance after the EntityManager was created: - - [php] - $entityManager->getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener()); - $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); - -++ Implementing Event Listeners - -This section explains what is and what is not allowed during specific lifecycle events of the UnitOfWork. -Although you get passed the EntityManager in all of these events, you have to follow this restrictions very -carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and -lost updates/persists/removes. - -+++ prePersist - -+++ preRemove - -+++ onFlush - -+++ preUpdate - -+++ postUpdate, postRemove, postPersist - -++ Load ClassMetadata Event - -When the mapping information for an entity is read, it is populated in to a -`ClassMetadataInfo` instance. You can hook in to this process and manipulate -the instance. - - [php] - $test = new EventTest(); - $metadataFactory = $em->getMetadataFactory(); - $evm = $em->getEventManager(); - $evm->addEventListener(Events::loadClassMetadata, $test); - - class EventTest - { - public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) - { - $classMetadata = $eventArgs->getClassMetadata(); - $fieldMapping = array( - 'fieldName' => 'about', - 'type' => 'string', - 'length' => 255 - ); - $classMetadata->mapField($fieldMapping); - } - } diff --git a/orm/manual/en/improving-performance.txt b/orm/manual/en/improving-performance.txt deleted file mode 100644 index 644636fc9..000000000 --- a/orm/manual/en/improving-performance.txt +++ /dev/null @@ -1,25 +0,0 @@ - -++ Bytecode Cache - -It is highly recommended to make use of a bytecode cache like APC. A bytecode cache removes the need for parsing PHP code on every request and can greatly improve performance. - -> **NOTE** -> "If you care about performance and don’t use a bytecode cache then you don’t really care -> about performance. Please get one and start using it." (Stas Malyshev, Core Contributor -> to PHP and Zend Employee). - - -++ Metadata and Query caches - -As already mentioned earlier in the chapter about configuring Doctrine, it is strongly discouraged to use Doctrine without a Metadata and Query cache (preferrably with APC or Memcache as the cache driver). Operating Doctrine without these caches means Doctrine will need to load your mapping information on every single request and has to parse each DQL query on every single request. This is a waste of resources. - -++ Alternative Query Result Formats - -Make effective use of the available alternative query result formats like nested array graphs or pure scalar results, especially in scenarios where data is loaded for read-only purposes. - -++ Apply Best Practices - -A lot of the points mentioned in the Best Practices chapter will also positively affect the performance of Doctrine. - - - diff --git a/orm/manual/en/inheritance-mapping.txt b/orm/manual/en/inheritance-mapping.txt deleted file mode 100644 index 1b82b1d2a..000000000 --- a/orm/manual/en/inheritance-mapping.txt +++ /dev/null @@ -1,141 +0,0 @@ -++ Mapped Superclasses - -An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information -for its subclasses, but which is not itself an entity. Typically, the purpose of such a mapped superclass is to define -state and mapping information that is common to multiple entity classes. - -Mapped superclasses, just as regular, non-mapped classes, can appear in the middle of an otherwise -mapped inheritance hierarchy (through Single Table Inheritance or Class Table Inheritance). - -> *NOTICE* -> -> A mapped superclass cannot be an entity, it is not queryable and persistent relationships defined by a mapped -> superclass must be unidirectional. For further support of inheritance, the single or joined table inheritance -> features have to be used. - -Example: - - [php] - /** @MappedSuperclass */ - class MappedSuperclassBase - { - /** @Column(type="integer") */ - private $mapped1; - /** @Column(type="string") */ - private $mapped2; - /** - * @OneToOne(targetEntity="MappedSuperclassRelated1") - * @JoinColumn(name="related1_id", referencedColumnName="id") - */ - private $mappedRelated1; - - // ... more fields and methods - } - - /** @Entity */ - class EntitySubClass extends MappedSuperclassBase - { - /** @Id @Column(type="integer") */ - private $id; - /** @Column(type="string") */ - private $name; - - // ... more fields and methods - } - -The DDL for the corresponding database schema would look something like this (this is for SQLite): - - [sql] - CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id)) - -As you can see from this DDL snippet, there is only a single table for the entity subclass. All the mappings from the mapped superclass were inherited to the subclass as if they had been defined on that class directly. - - -++ Single Table Inheritance - -[Single Table Inheritance](http://martinfowler.com/eaaCatalog/singleTableInheritance.html) is an inheritance mapping strategy where all classes of a hierarchy are mapped to a single database table. In order to distinguish which row represents which type in the hierarchy a so-called discriminator column is used. - -Example: - - [php] - namespace MyProject\Model; - - /** - * @Entity - * @InheritanceType("SINGLE_TABLE") - * @DiscriminatorColumn(name="discr", type="string") - * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) - */ - class Person - { - // ... - } - - /** - * @Entity - */ - class Employee extends Person - { - // ... - } - -Things to note: - -* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified on the topmost class that is part of the mapped entity hierarchy. -* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of a certain type. In the case above a value of "person" identifies a row as being of type `Person` and "employee" identifies a row as being of type `Employee`. -* The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied. - -+++ Design-time considerations - -This mapping approach works well when the type hierarchy is fairly simple and stable. Adding a new type to the hierarchy and adding fields to existing supertypes simply involves adding new columns to the table, though in large deployments this may have an adverse impact on the index and column layout inside the database. - -+++ Performance impact - -This strategy is very efficient for querying across all types in the hierarchy or for specific types. No table joins are required, only a WHERE clause listing the type identifiers. In particular, relationships involving types that employ this mapping strategy are very performant. - - -++ Class Table Inheritance - -[Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html) is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. -Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hieararchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance. - -Example: - - [php] - namespace MyProject\Model; - - /** - * @Entity - * @InheritanceType("JOINED") - * @DiscriminatorColumn(name="discr", type="string") - * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) - */ - class Person - { - // ... - } - - /** @Entity */ - class Employee extends Person - { - // ... - } - -Things to note: - -* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified on the topmost class that is part of the mapped entity hierarchy. -* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of which type. In the case above a value of "person" identifies a row as being of type `Person` and "employee" identifies a row as being of type `Employee`. -* The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied. - -> **NOTE** -> When you do not use the SchemaTool to generate the required SQL you should know that deleting a class table inheritance -> makes use of the foreign key property `ON DELETE CASCADE` in all database implementations. A failure to implement this -> yourself will lead to dead rows in the database. - -+++ Design-time considerations - -Introducing a new type to the hierarchy, at any level, simply involves interjecting a new table into the schema. Subtypes of that type will automatically join with that new type at runtime. Similarly, modifying any entity type in the hierarchy by adding, modifying or removing fields affects only the immediate table mapped to that type. This mapping strategy provides the greatest flexibility at design time, since changes to any type are always limited to that type's dedicated table. - -+++ Performance impact - -This strategy inherently requires multiple JOIN operations to perform just about any query which can have a negative impact on performance, especially with large tables and/or large hierarchies. When partial objects are allowed, either globally or on the specific query, then querying for any type will not cause the tables of subtypes to be OUTER JOINed which can increase performance but the resulting partial objects will not fully load themselves on access of any subtype fields, so accessing fields of subtypes after such a query is not safe. \ No newline at end of file diff --git a/orm/manual/en/introduction.txt b/orm/manual/en/introduction.txt deleted file mode 100644 index 96f627d6a..000000000 --- a/orm/manual/en/introduction.txt +++ /dev/null @@ -1,222 +0,0 @@ -++ Welcome - -Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides -transparent persistence for PHP objects. It sits on top of a powerful database -abstraction layer (DBAL). One of its key features is the option to write -database queries in a proprietary object oriented SQL dialect called Doctrine -Query Language (DQL), inspired by Hibernates HQL. This provides developers with -a powerful alternative to SQL that maintains flexibility without requiring -unnecessary code duplication. - -++ Disclaimer - -This is the Doctrine 2 reference documentation. Introductory guides and tutorials that you can follow along from start to finish, like the "Guide to Doctrine" book known from the Doctrine 1.x series, will be available at a later date. - -++ Requirements - -Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved performance it -is also recommended that you use APC with PHP. - -++ Doctrine 2 Packages - -Doctrine 2 is divided into three main packages. - -* Common -* DBAL (includes Common) -* ORM (includes DBAL+Common) - -This manual mainly covers the ORM package, sometimes touching parts of the -underlying DBAL and Common packages. The Doctrine code base is split in to these -packages for a few reasons and they are to... - -* ...make things more maintainable and decoupled -* ...allow you to use the code in Doctrine Common without the ORM or DBAL -* ...allow you to use the DBAL without the ORM - -+++ The Common Package - -The Common package contains highly reusable components that have no dependencies -beyond the package itself (and PHP, of course). The root namespace of the -Common package is `Doctrine\Common`. - -+++ The DBAL Package - -The DBAL package contains an enhanced database abstraction layer on top of -PDO but is not strongly bound to PDO. The purpose of this layer is to provide a -single API that bridges most of the differences between the different RDBMS vendors. -The root namespace of the DBAL package is `Doctrine\DBAL`. - -+++ The ORM Package - -The ORM package contains the object-relational mapping toolkit that provides -transparent relational persistence for plain PHP objects. The root namespace of -the ORM package is `Doctrine\ORM`. - -++ Installing - -Doctrine can be installed many different ways. We will describe all the different -ways and you can choose which one suits you best. - -+++ PEAR - -You can easily install any of the three Doctrine packages from the PEAR command -line installation utility. - -To install just the `Common` package you can run the following command: - - $ sudo pear install pear.phpdoctrine.org/DoctrineCommon-2.0.0 - -If you want to use the Doctrine Database Abstraction Layer you can install it -with the following command. - - $ sudo pear install pear.phpdoctrine.org/DoctrineDBAL-2.0.0 - -Or, if you want to get the works and go for the ORM you can install it with the -following command. - - $ sudo pear install pear.phpdoctrine.org/DoctrineORM-2.0.0 - -When you have a package installed via PEAR you can required and load the -`ClassLoader` with the following code. - - [php] - require 'Doctrine/Common/ClassLoader.php'; - $classLoader = new \Doctrine\Common\ClassLoader(); - -The packages are installed in to your shared PEAR PHP code folder in a folder -named `Doctrine`. You also get a nice command line utility installed and made -available on your system. Now when you run the `doctrine` command you will -see what you can do with it. - - $ doctrine - Doctrine Command Line Interface - Available Tasks: - core:help - dbal:run-sql (--file= | --sql=) --depth= - orm:clear-cache (--query | --metadata | --result [--id=] [--regex=] [--prefix=] [--suffix=]) - orm:convert-mapping (--from= | --from-database) --to= --dest= - orm:ensure-production-settings - orm:generate-proxies --class-dir= [--to-dir=] - orm:run-dql --dql= --depth= - orm:schema-tool (--create | --drop | --update | --complete-update | --re-create) [--dump-sql] [--class-dir=] - orm:version - -+++ Package Download - -You can also use Doctrine 2 by downloading the latest release package -from [the download page](http://www.doctrine-project.org/download). - -+++ Subversion - -Alternatively you can check out the latest version of Doctrine 2 via SVN. - - $ svn co http://svn.doctrine-project.org/trunk doctrine - -++ Sandbox Quickstart - -> **NOTE** -> The sandbox is only available via SVN or soon as a separate download on the downloads -> page. - -The sandbox is a pre-configured environment for evaluating and playing -with Doctrine 2. - -+++ Overview - -After navigating to the sandbox directory, you should see the following structure: - - sandbox/ - Entities/ - Address.php - User.php - xml/ - Entities.Address.dcm.xml - Entities.User.dcm.xml - yaml/ - Entities.Address.dcm.yml - Entities.User.dcm.yml - cli-config.php - doctrine - doctrine.php - index.php - -Here is a short overview of the purpose of these folders and files: - - * The `Entities` folder is where any model classes are created. Two example entities are already there. - * The `xml` folder is where any XML mapping files are created (if you want to use XML mapping). Two example mapping documents for the 2 example entities are already there. - * The `yaml` folder is where any YAML mapping files are created (if you want to use YAML mapping). Two example mapping documents for the 2 example entities are already there. - * The `cli-config.php` contains bootstrap code for a configuration that is used by the CLI tool `doctrine` whenever you execute a task. - * `doctrine`/`doctrine.php` is a command-line tool. - * `index.php` is a basic classical bootstrap file of a php application that uses Doctrine 2. - -+++ Mini-tutorial - -1) From within the tools/sandbox folder, run the following command and you should -see the same output. - - $ php doctrine orm:schema-tool --create - Creating database schema... - Database schema created successfully. - -2) Take another look into the tools/sandbox folder. A SQLite database should -have been created with the name `database.sqlite`. - -3) Open `index.php` and edit it so that it looks as follows: - - [php] - //... bootstrap stuff - - ## PUT YOUR TEST CODE BELOW - - $user = new \Entities\User; - $user->setName('Garfield'); - $em->persist($user); - $em->flush(); - - echo "User saved!"; - -Open index.php in your browser or execute it on the command line. You should see -the output "User saved!". - -5) Inspect the SQLite database. Again from within the tools/sandbox folder, -execute the following command: - - $ php doctrine dbal:run-sql --sql="select * from users" - -You should get the following output: - - array(1) { - [0]=> - array(2) { - ["id"]=> - string(1) "1" - ["name"]=> - string(8) "Garfield" - } - } - -You just saved your first entity with a generated ID in an SQLite database. - -6) Replace the contents of index.php with the following: - - [php] - //... bootstrap stuff - - ## PUT YOUR TEST CODE BELOW - - $q = $em->createQuery('select u from Entities\User u where u.name = ?1'); - $q->setParameter(1, 'Garfield'); - $garfield = $q->getSingleResult(); - - echo "Hello " . $garfield->getName() . "!"; - -You just created your first DQL query to retrieve the user with the name -'Garfield' from an SQLite database (Yes, there is an easier way to do it, -but we wanted to introduce you to DQL at this point. Can you **find** the easier way?). - -> **TIP** -> When you create new model classes or alter existing ones you can recreate the database -> schema with the command `doctrine orm:schema-tool --drop` followed by -> `doctrine orm:schema-tool --create`. - -7) Explore Doctrine 2! diff --git a/orm/manual/en/native-sql.txt b/orm/manual/en/native-sql.txt deleted file mode 100644 index fb6359737..000000000 --- a/orm/manual/en/native-sql.txt +++ /dev/null @@ -1,196 +0,0 @@ -A `NativeQuery` lets you execute native SQL, mapping the results according to your specifications. Such a specification that describes how an SQL result set is mapped to a Doctrine result is represented by a `ResultSetMapping`. - -++ The NativeQuery class - -To create a `NativeQuery` you use the method `EntityManager#createNativeQuery($sql, $resultSetMapping)`. As you can see in the signature of this method, it expects 2 ingredients: The SQL you want to execute and the `ResultSetMapping` that describes how the results will be mapped. - -Once you obtained an instance of a `NativeQuery`, you can bind parameters to it and finally execute it. - -++ The ResultSetMapping - -Understanding the `ResultSetMapping` is the key to using a `NativeQuery`. -A Doctrine result can contain the following components: - - * Entity results. These represent root result elements. - * Joined entity results. These represent joined entities in associations of root entity results. - * Field results. These represent a column in the result set that maps to a field of an entity. A field result always belongs to an entity result or joined entity result. - * Scalar results. These represent scalar values in the result set that will appear in each result row. Adding scalar results to a ResultSetMapping can also cause the overall result to becomed **mixed** (see DQL - Doctrine Query Language) if the same ResultSetMapping also contains entity results. - * Meta results. These represent columns that contain meta-information, such as foreign keys and discriminator columns. - When querying for objects (`getResult()`), all meta columns of root entities or joined entities must be present in the SQL query - and mapped accordingly using `ResultSetMapping#addMetaResult`. - -> **TIP** -> It might not surprise you that Doctrine uses `ResultSetMapping`s internally when you -> create DQL queries. As the query gets parsed and transformed to SQL, Doctrine fills -> a `ResultSetMapping` that describes how the results should be processed by the hydration -> routines. - -We will now look at each of the result types that can appear in a ResultSetMapping in detail. - -+++ Entity results - -An entity result describes an entity type that appears as a root element in the transformed result. You add an entity result through `ResultSetMapping#addEntityResult()`. -Let's take a look at the method signature in detail: - - [php] - /** - * Adds an entity result to this ResultSetMapping. - * - * @param string $class The class name of the entity. - * @param string $alias The alias for the class. The alias must be unique among all entity - * results or joined entity results within this ResultSetMapping. - */ - public function addEntityResult($class, $alias) - -The first parameter is the fully qualified name of the entity class. The second parameter is some arbitrary alias for this entity result that must be unique within a `ResultSetMapping`. You use this alias to attach field results to the entity result. It is very similar to an identification variable that you use in DQL to alias classes or relationships. - -An entity result alone is not enough to form a valid `ResultSetMapping`. An entity result or joined entity result always needs a set of field results, which we will look at soon. - -+++ Joined entity results - -A joined entity result describes an entity type that appears as a joined relationship element in the transformed result, attached to a (root) entity result. You add a joined entity result through `ResultSetMapping#addJoinedEntityResult()`. Let's take a look at the method signature in detail: - - [php] - /** - * Adds a joined entity result. - * - * @param string $class The class name of the joined entity. - * @param string $alias The unique alias to use for the joined entity. - * @param string $parentAlias The alias of the entity result that is the parent of this joined result. - * @param object $relation The association field that connects the parent entity result with the joined entity result. - */ - public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) - -The first parameter is the class name of the joined entity. The second parameter is an arbitrary alias for the joined entity that must be unique within the `ResultSetMapping`. -You use this alias to attach field results to the entity result. The third parameter is the alias of the entity result that is the parent type of the joined relationship. The fourth and last parameter is the name of the field on the parent entity result that should contain the joined entity result. - - -+++ Field results - -A field result describes the mapping of a single column in an SQL result set to a field in an entity. As such, field results are inherently bound to entity results. You add a field result through `ResultSetMapping#addFieldResult()`. Again, let's examine the method signature in detail: - - [php] - /** - * Adds a field result that is part of an entity result or joined entity result. - * - * @param string $alias The alias of the entity result or joined entity result. - * @param string $columnName The name of the column in the SQL result set. - * @param string $fieldName The name of the field on the (joined) entity. - */ - public function addFieldResult($alias, $columnName, $fieldName) - -The first parameter is the alias of the entity result to which the field result will belong. The second parameter is the name of the column in the SQL result set. Note that this name is case sensitive, i.e. if you use a native query against Oracle it must be all uppercase. The third parameter is the name of the field on the entity result identified by `$alias` into which the value of the column should be set. - -+++ Scalar results - -A scalar result describes the mapping of a single column in an SQL result set to a scalar value in the Doctrine result. Scalar results are typically used for aggregate values but any column in the SQL result set can be mapped as a scalar value. To add a scalar result use `ResultSetMapping#addScalarResult()`. The method signature in detail: - - [php] - /** - * Adds a scalar result mapping. - * - * @param string $columnName The name of the column in the SQL result set. - * @param string $alias The result alias with which the scalar result should be placed in the result structure. - */ - public function addScalarResult($columnName, $alias) - -The first parameter is the name of the column in the SQL result set and the second parameter is the result alias under which the value of the column will be placed in the transformed Doctrine result. - -+++ Meta results - -A meta result describes a single column in an SQL result set that is either a foreign key or a discriminator column. -These columns are essential for Doctrine to properly construct objects out of SQL result sets. -To add a column as a meta result use `ResultSetMapping#addMetaResult()`. The method signature in detail: - - [php] - /** - * Adds a meta column (foreign key or discriminator column) to the result set. - * - * @param string $alias - * @param string $columnAlias - * @param string $columnName - */ - public function addMetaResult($alias, $columnAlias, $columnName) - -The first parameter is the alias of the entity result to which the meta column belongs. -A meta result column (foreign key or discriminator column) always belongs to to an entity result. -The second parameter is the column alias/name of the column in the SQL result set and the third parameter is the -column name used in the mapping. - - -+++ Examples - -Understanding a ResultSetMapping is probably easiest through looking at some examples. - -First a basic example that describes the mapping of a single entity. - - [php] - // Equivalent DQL query: "select u from User u where u.name=?1" - // User owns no associations. - $rsm = new ResultSetMapping; - $rsm->addEntityResult('User', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - - $query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm); - $query->setParameter(1, 'romanb'); - - $users = $query->getResult(); - -The result would look like this: - - array( - [0] => User (Object) - ) - -Note that this would be a partial object if the entity has more fields than just id and name. In the example above the column and field names are identical but that is not necessary, of course. Also note that the query string passed to createNativeQuery is **real native SQL**. Doctrine does not touch this SQL in any way. - -In the previous basic example, a User had no relations and the table the class is mapped to owns no foreign keys. -The next an example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress, -where the User is the owning side and thus owns the foreign key. - - [php] - // Equivalent DQL query: "select u from User u where u.name=?1" - // User owns an association to an Address but the Address is not loaded in the query. - $rsm = new ResultSetMapping; - $rsm->addEntityResult('User', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - $rsm->addMetaResult('u', 'address_id', 'address_id'); - - $query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm); - $query->setParameter(1, 'romanb'); - - $users = $query->getResult(); - -Foreign keys are used by Doctrine for lazy-loading purposes when querying for objects. -In the previous example, each user object in the result will have a proxy (a "ghost") in place -of the address that contains the address_id. When the ghost proxy is accessed, it loads itself -based on this key. - -Consequently, associations that are *fetch-joined* do not require the foreign keys to be present -in the SQL result set, only associations that are lazy. - -If a fetched entity is part of a mapped hierarchy that requires a discriminator column, this -column must be present in the result set as a meta column so that Doctrine can create the -appropriate concrete type. This is shown in the following example where we assume that there -are one or more subclasses that extend User and either Class Table Inheritance or Single Table Inheritance -is used to map the hierarchy (both use a discriminator column). - - [php] - // Equivalent DQL query: "select u from User u where u.name=?1" - // User is a mapped base class for other classes. User owns no associations. - $rsm = new ResultSetMapping; - $rsm->addEntityResult('User', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - $rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column - - $query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm); - $query->setParameter(1, 'romanb'); - - $users = $query->getResult(); - -Note that in the case of Class Table Inheritance, an example as above would result in partial objects -if any objects in the result are actually a subtype of User. When using DQL, Doctrine automatically -includes the necessary joins for this mapping strategy but with native SQL it is your responsibility. \ No newline at end of file diff --git a/orm/manual/en/query-builder.txt b/orm/manual/en/query-builder.txt deleted file mode 100644 index 1080d4601..000000000 --- a/orm/manual/en/query-builder.txt +++ /dev/null @@ -1,356 +0,0 @@ - -++ The QueryBuilder - -A `QueryBuilder` provides an API that is designed for conditionally constructing a DQL query in several steps. - -It provides a set of classes and methods that is able to programatically build you queries, and also provides a fluent API. -This means that you can change between one methodology to the other as you want, and also pick one if you prefer. - -+++ Constructing a new QueryBuilder object - -The same way you build a normal Query, you build a `QueryBuilder` object, just providing the correct method name. -Here is an example how to build a `QueryBuilder` object: - - [php] - // $em instanceof EntityManager - - // example1: creating a QueryBuilder instance - $qb = $em->createQueryBuilder(); - -Once you created an instance of QueryBuilder, it provides a set of useful informative functions that you can use. -One good example is to inspect what type of object the `QueryBuilder` is. - - [php] - // $qb instanceof QueryBuilder - - // example2: retrieving type of QueryBuilder - echo $qb->getType(); // Prints: 0 - -There're currently 3 possible return values for `getType()`: - -* `QueryBuilder::SELECT`, which returns value 0 -* `QueryBuilder::DELETE`, returning value 1 -* `QueryBuilder::UPDATE`, which returns value 2 - -It is possible to retrieve the associated `EntityManager` of the current `QueryBuilder`, its DQL and also a `Query` object when you finish building your DQL. - - [php] - // $qb instanceof QueryBuilder - - // example3: retrieve the associated EntityManager - $em = $qb->getEntityManager(); - - // example4: retrieve the DQL string of what was defined in QueryBuilder - $dql = $qb->getDql(); - - // example5: retrieve the associated Query object with the processed DQL - $q = $qb->getQuery(); - -Internally, `QueryBuilder` works with a DQL cache, which prevents multiple processment if called multiple times. Any changes that may affect the generated DQL actually modifies the state of `QueryBuilder` to a stage we call as STATE_DIRTY. -One `QueryBuilder`can be in two different state: - -* `QueryBuilder::STATE_CLEAN`, which means DQL haven't been altered since last retrieval or nothing were added since its instantiation -* `QueryBuilder::STATE_DIRTY`, means DQL query must (and will) be processed on next retrieval - -+++ Working with QueryBuilder - -All helper methods in `QueryBuilder` relies actually on a single one: `add()`. -This method is the responsable to build every piece of DQL. It takes 3 parameters: `$dqlPartName`, `$dqlPart` and `$append` (default=false) - -* `$dqlPartName`: Where the `$dqlPart` should be placed. Possible values: select, from, where, groupBy, having, orderBy -* `$dqlPart`: What should be placed in `$dqlPartName`. Accepts a string or any instance of `Doctrine\ORM\Query\Expr\*` -* `$append`: Optional flag (default=false) if the `$dqlPart` should override all previously defined items in `$dqlPartName` or not - -- - - [php] - // $qb instanceof QueryBuilder - - // example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support - $qb->add('select', 'u') - ->add('from', 'User u') - ->add('where', 'u.id = ?1') - ->add('orderBy', 'u.name ASC'); - -++++ Expr\* classes - -When you call `add()` with string, it internally evaluates to an instance of `Doctrine\ORM\Query\Expr\Expr\*` class. -Here is the same query of example 6 written using `Doctrine\ORM\Query\Expr\Expr\*` classes: - - [php] - // $qb instanceof QueryBuilder - - // example7: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder using Expr\* instances - $qb->add('select', new Expr\Select(array('u'))) - ->add('from', new Expr\From('User', 'u')) - ->add('where', new Expr\Comparison('u.id', '=', '?1')) - ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); - -Of course this is the hardest way to build a DQL query in Doctrine. To simplify some of these efforts, we introduce what we call as `Expr` helper class. - -++++ The Expr class - -To workaround most of the issues that `add()` method may cause, Doctrine created a class that can be considered as a helper for building queries. -This class is called `Expr`, which provides a set of useful static methods to help building queries: - - [php] - // $qb instanceof QueryBuilder - - // example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.surname DESC" using Expr class - $qb->add('select', $qb->expr()->select('u')) - ->add('from', $qb->expr()->from('User', 'u')) - ->add('where', $qb->expr()->orx( - $qb->expr()->eq('u.id', '?1'), - $qb->expr()->like('u.nickname', '?2') - )) - ->add('orderBy', $qb->expr()->orderBy('u.surname', 'ASC')); - -Although it still sounds complex, the ability to programatically create conditions are the main feature of `Expr`. -Here it is a complete list of supported helper methods available: - - [php] - class Expr - { - /** Base objects **/ - - // Example usage - $qb->expr()->select('u') - public function select($select = null); // Returns Expr\Select instance - - // Example - $qb->expr()->from('User', 'u') - public function from($from, $alias); // Returns Expr\From instance - - // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', Expr\Join::ON, 'p.user_id = u.id AND p.country_code = 55'); - // Example - $qb->expr()->leftJoin('u. Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); - public function leftJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance - - // Example - $qb->expr()->innerJoin('u.Group', 'g', Expr\Join::WITH, 'g.manager_level = 100'); - // Example - $qb->expr()->innerJoin('u.Group', 'g', 'WITH', $qb->expr()->eq('g.manager_level', '100')); - public function innerJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance - - // Example - $qb->expr()->orderBy('u.surname', 'ASC')->add('u.firstname', 'ASC')->... - public function orderBy($sort = null, $order = null); // Returns Expr\OrderBy instance - - // Example - $qb->expr()->groupBy()->add('u.id')->... - public function groupBy($groupBy = null); // Returns Expr\GroupBy instance - - - /** Conditional objects **/ - - // Example - $qb->expr()->andx($cond1 [, $condN])->add(...)->... - public function andx($x = null); // Returns Expr\Andx instance - - // Example - $qb->expr()->orx($cond1 [, $condN])->add(...)->... - public function orx($x = null); // Returns Expr\Orx instance - - - /** Comparison objects **/ - - // Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1 - public function eq($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1 - public function neq($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1 - public function lt($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1 - public function lte($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1 - public function gt($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1 - public function gte($x, $y); // Returns Expr\Comparison instance - - - /** Arithmetic objects **/ - - // Example - $qb->expr()->prod('u.id', '2') => u.id * 2 - public function prod($x, $y); // Returns Expr\Math instance - - // Example - $qb->expr()->diff('u.id', '2') => u.id - 2 - public function diff($x, $y); // Returns Expr\Math instance - - // Example - $qb->expr()->sum('u.id', '2') => u.id + 2 - public function sum($x, $y); // Returns Expr\Math instance - - // Example - $qb->expr()->quot('u.id', '2') => u.id / 2 - public function quot($x, $y); // Returns Expr\Math instance - - - /** Pseudo-function objects **/ - - // Example - $qb->expr()->exists($qb2->getDql()) - public function exists($subquery); // Returns Expr\Func instance - - // Example - $qb->expr()->all($qb2->getDql()) - public function all($subquery); // Returns Expr\Func instance - - // Example - $qb->expr()->some($qb2->getDql()) - public function some($subquery); // Returns Expr\Func instance - - // Example - $qb->expr()->any($qb2->getDql()) - public function any($subquery); // Returns Expr\Func instance - - // Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1')) - public function not($restriction); // Returns Expr\Func instance - - // Example - $qb->expr()->in('u.id', array(1, 2, 3)) - public function in($x, $y); // Returns Expr\Func instance - - // Example - $qb->expr()->notIn('u.id', '2') - public function notIn($x, $y); // Returns Expr\Func instance - - // Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%')) - public function like($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->between('u.id', '1', '10') - public function between($val, $x, $y); // Returns Expr\Func - - - /** Function objects **/ - - // Example - $qb->expr()->trim('u.firstname') - public function trim($x); // Returns Expr\Func - - // Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat(' ', 'u.lastname')) - public function concat($x, $y); // Returns Expr\Func - - // Example - $qb->expr()->substr('u.firstname', 0, 1) - public function substr($x, $from, $len); // Returns Expr\Func - - // Example - $qb->expr()->lower('u.firstname') - public function lower($x); // Returns Expr\Func - - // Example - $qb->expr()->upper('u.firstname') - public function upper($x); // Returns Expr\Func - - // Example - $qb->expr()->length('u.firstname') - public function length($x); // Returns Expr\Func - - // Example - $qb->expr()->avg('u.age') - public function avg($x); // Returns Expr\Func - - // Example - $qb->expr()->max('u.age') - public function max($x); // Returns Expr\Func - - // Example - $qb->expr()->min('u.age') - public function min($x); // Returns Expr\Func - - // Example - $qb->expr()->abs('u.currentBalance') - public function abs($x); // Returns Expr\Func - - // Example - $qb->expr()->sqrt('u.currentBalance') - public function sqrt($x); // Returns Expr\Func - - // Example - $qb->expr()->count('u.firstname') - public function count($x); // Returns Expr\Func - - // Example - $qb->expr()->countDistinct('u.surname') - public function countDistinct($x); // Returns Expr\Func - } - -++++ Helper methods - -Until now it was described the hardcore level of creating queries. It may be useful to work that way for optimization purposes, but most of the time it is preferred to work higher level. -To simplify even more the way you build a query in Doctrine, we can take advantage of what we call as helper methods. For all base code, it has a set of useful methods to simplify programmer's life. -Illustrating how to work with it, here is the same example 6 written now using `QueryBuilder` helper methods: - - [php] - // $qb instanceof QueryBuilder - - // example9: how to define: "SELECT u FROM User u WHERE u.id = ?1 ORDER BY u.name ASC" using QueryBuilder helper methods - $qb->select('u') - ->from('User', 'u') - ->where('u.id = ?1') - ->orderBy('u.name ASC'); - -`QueryBuilder` helper methods are considered the standard way to build DQL queries. Although it is supported, it should be avoided to use string based queries and greatly encouraged to use `$qb->expr()->*` methods. -Here is a converted example 8 to suggested standard way to build queries: - - [php] - // $qb instanceof QueryBuilder - - // example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ?1 OR u.nickname LIKE ?2 ORDER BY u.surname DESC" using QueryBuilder helper methods - $qb->select(array('u')) // string 'u' is converted to array internally - ->from('User', 'u') - ->where($qb->expr()->orx( - $qb->expr()->eq('u.id', '?1'), - $qb->expr()->like('u.nickname', '?2') - )) - ->orderBy('u.surname', 'ASC')); - -Here is a complete list of helper methods in `QueryBuilder`: - - [php] - class QueryBuilder - { - // Example - $qb->select('u') - // Example - $qb->select(array('u', 'p')) - // Example - $qb->select($qb->expr()->select('u', 'p')) - public function select($select = null); - - // Example - $qb->delete('User', 'u') - public function delete($delete = null, $alias = null); - - // Example - $qb->update('Group', 'g') - public function update($update = null, $alias = null); - - // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold')) - // Example - $qb->set('u.numChilds', 'u.numChilds + ?1') - // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1')) - public function set($key, $value); - - // Example - $qb->from('Phonenumber', 'p') - public function from($from, $alias = null); - - // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::ON, $qb->expr()->and($qb->expr()->eq('u.group_id', 'g.id'), 'g.name = ?1')) - // Example - $qb->innerJoin('u.Group', 'g', 'ON', 'u.group_id = g.id AND g.name = ?1') - public function innerJoin($join, $alias = null, $conditionType = null, $condition = null); - - // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55)) - // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55') - public function leftJoin($join, $alias = null, $conditionType = null, $condition = null); - - // NOTE: ->where() overrides all previously set conditions - // - // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2')) - // Example - $qb->where($qb->expr()->andx($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2'))) - // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2') - public function where($where); - - // Example - $qb->andWhere($qb->expr()->orx($qb->expr()->lte('u.age', 40), 'u.numChild = 0')) - public function andWhere($where); - - // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10)); - public function orWhere($where); - - // NOTE: -> groupBy() overrides all previously set grouping items - // - // Example - $qb->groupBy('u.id') - public function groupBy($groupBy); - - // Example - $qb->addGroupBy('g.name') - public function addGroupBy($groupBy); - - // NOTE: -> having() overrides all previously set having conditions - // - // Example - $qb->having('u.salary >= ?1') - // Example - $qb->having($qb->expr()->gte('u.salary', '?1')) - public function having($having); - - // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0)) - public function andHaving($having); - - // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100')) - public function orHaving($having); - - // NOTE: -> orderBy() overrides all previously set ordering items - // - // Example - $qb->orderBy('u.surname', 'DESC') - public function orderBy($sort, $order = null); - - // Example - $qb->addOrderBy('u.firstName') - public function addOrderBy($sort, $order = null); // Default $order = 'ASC' - } \ No newline at end of file diff --git a/orm/manual/en/tools.txt b/orm/manual/en/tools.txt deleted file mode 100644 index d97136c69..000000000 --- a/orm/manual/en/tools.txt +++ /dev/null @@ -1,192 +0,0 @@ -++ The Doctrine CLI - -The Doctrine CLI (Command Line Interface) is a tool for simplifying many common tasks during the development of a project that uses Doctrine. - -+++ Installation - -If you installed Doctrine 2 through PEAR, the `doctrine` command line tool should already be available to you. - -If you use Doctrine through SVN or a release package you need to copy the `doctrine` and `doctrine.php` files from the `tools/sandbox` or `bin` folder, respectively, to a location of your choice, for example a `tools` folder of your project. -In addition you may need to edit `doctrine.php` and adjust some paths to the new environment. You may want to add require_once() statement at the top of doctrine.php to set up the include_path for Doctrine classes. - -+++ Getting Help - -Type `doctrine` on the command line and you should see an overview of the available tasks or use the --help flag to get information on the available tasks. If you want to know more about the use of the schema tool for example you can call: - - doctrine orm:schema-tool --help - -+++ Configuration - -Whenever the `doctrine` command line tool is invoked it requires an instance of `Doctrine\Common\Cli\CliConfiguration` to be able to correctly work. When using ORM package, it is required to define an attribute inside Configuration: `em`. `em` must be an `EntityManager` instance that is used by ORM command-line tasks. - -Many tasks of the Doctrine CLI require the `em` attribute to be an `EntityManager` in order to execute. The `EntityManager` instance implicitly defines a database connection. -If you invoke a task that requires an EntityManager (and/or a database connection) and the `em` attribute is not defined in your CLI Configuration instance, the task invoking will report an error for you. - -CLI COnfiguration instance can be in a separate file (ie. `cli-config.php`) that contains typical Doctrine bootstrap code and predefines the needed attributes mentioned above. A typical `cli-config.php` file looks as follows: - - [php] - require_once '/path/to/lib/Doctrine/Common/ClassLoader.php'; - - $classLoader = new \Doctrine\Common\ClassLoader('MyProject', '/path/to/myproject/lib'); - $classLoader->register(); - - $ormConfig = new \Doctrine\ORM\Configuration(); - $ormConfig->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); - $ormConfig->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); - $ormConfig->setProxyNamespace('MyProject\Proxies'); - - $connectionOptions = array( - 'driver' => 'pdo_sqlite', - 'path' => 'database.sqlite' - ); - - $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $ormConfig); - - $cliConfig = new \Doctrine\Common\Cli\Configuration(); - $cliConfig->setAttribute('em', $em); - $cliConfig->setAttribute('globalArguments', array( - 'class-dir' => '/path/to/myproject/lib/MyProject/Models/' - )); - -It is important to define an instance of Doctrine\Common\Cli\Configuration that the doctrine.php script will ultimately use. For the required name of this variable, check the doctrine.php included in your package; the most new ones will automatically find the variable no matter what its name is, as long as it is an instance of the right class. -The CLI Configuration should contain at least the 'em' attribute, set to the EntityManager. To use many tasks a 'globalOptions' array should be set. -The `globalArguments` content will be passed to every command line task. For instance, to use the orm:schema-tool task to generate database tables from annotations, the class-dir global option must be set as in the example. - -+++ Task Overview - -The following tasks are currently available: - - * `dbal:run-sql`: Used to run arbitrary SQL on the command-line. - * `orm:convert-mapping`: Used to convert between annotations/xml/yaml mapping informations as well as for class generating from xml/yaml mapping documents or for reverse engineering. - * `orm:generate-proxies`: Used to generate proxy classes used by Doctrine. - * `orm:run-dql`: Used to run arbitrary DQL on the command-line. - * `orm:schema-tool`: Used to forward-engineer the relational database schema from existing classes and mappings. - * `orm:version`: Used to show the current version of the CLI and Doctrine. - -++ Database Schema Generation - -To generate your database schema from your Doctrine mapping files you can use the -`SchemaTool` class or the `schema-tool` CLI task. - -When using the SchemaTool class directly, create your schema using the `createSchema()` method. First create an instance of the `SchemaTool` and pass it an instance of the `EntityManager` that you want to use to create the schema. This method receives an array of `ClassMetadataInfo` instances. - - [php] - $tool = new \Doctrine\ORM\Tools\SchemaTool($em); - $classes = array( - $em->getClassMetadata('Entities\User'), - $em->getClassMetadata('Entities\Profile') - ); - $tool->createSchema($classes); - -To drop the schema you can use the `dropSchema()` method. - - [php] - $tool->dropSchema($classes); - -This drops all the tables that are currently used by your metadata model. -When you are changing your metadata alot during development you might want -to drop the complete database instead of only the tables of the current model -to clean up with orphaned tables. - - [php] - $tool->dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE); - -You can also use database introspection to update your schema easily with the -`updateSchema()` method. It will compare your existing database schema to the -passed array of `ClassMetdataInfo` instances. - - [php] - $tool->updateSchema($classes); - -If you want to use this functionality from the command line you can use the -`schema-tool` task. - -To create the schema use the `--create` option: - - $ php doctrine orm:schema-tool --create - -To drop the scheme use the `--drop` option: - - $ php doctrine orm:schema-tool --drop - -If you want to drop and then recreate the schema then use both options: - - $ php doctrine orm:schema-tool --drop --create - -As you would think, if you want to update your schema use the `--update` option: - - $ php doctrine orm:schema-tool --update - -All of the above tasks also accept a `--dump-sql` option that will output the SQL -for the ran operation. - - $ php doctrine orm:schema-tool --create --dump-sql - -Before using the orm:schema-tool task, remember to configure your cli-config.php properly. - -++ Convert Mapping Information - -Doctrine comes with some special tools for working with the various supported -formats for specifying mapping information. - -You have the ability to convert from a few different sources. - -* An existing database -* A directory of YAML schema files -* A directory of XML schema files -* A directory of PHP scripts which populate `ClassMetadataInfo` instances -* A directory of PHP classes defining Doctrine entities with annotations - -To convert a mapping source you can do everything you need with the `ClassMetadataExporter`. - - [php] - $cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter(); - -Once you have an instance you can start adding mapping sources to convert. - - [php] - $cme->addMappingSource('/path/to/yml', 'yml'); - $cme->addMappingSource('/path/to/xml', 'xml'); - $cme->addMappingSource('/path/to/php', 'php'); - $cme->addMappingSource('/path/to/annotations', 'annotation'); - -Now to convert the added mapping sources you can do so by using the exporter drivers. - - [php] - $metadatas = $cme->getMetadatasForMappingSources(); - - $exporter = $cme->getExporter('yml', '/path/to/export/yml'); - $exporter->setMetadatas($metadatas); - $exporter->export(); - -This functionality functionality is also available from the command line to for -example convert some YAML mapping files to XML. - - $ php doctrine orm:convert-mapping --from=/path/to/yml --to=xml --dest=/path/to/xml - -++ Reverse Engineering - -You can use the same `ClassMetadataExporter` to reverse engineer a database and -generate YAML, XML, etc. from your existing databases. - - [php] - $sm = $em->getConnection()->getSchemaManager(); - - $cme->addMappingSource($sm, 'database'); - $metadatas = $cme->getMetadatasForMappingSources(); - - $exporter = $cme->getExporter('yml', '/path/to/export/yml'); - $exporter->setMetadatas($metadatas); - $exporter->export(); - -From the command line it is very simple to do something like reverse engineer -your existing database to set of YAML mapping files. - - $ php doctrine orm:convert-mapping --from=database --to=yml --dest=/path/to/yml - -> **CAUTION** -> Reverse Engineering is not always working perfectly depending on special cases. -> It will only detect Many-To-One relations (even if they are One-To-One) and -> will try to create entities from Many-To-Many tables. It also has problems -> with naming of foreign keys that have multiple column names. Any Reverse Engineered -> Database-Schema needs considerable manual work to become a useful domain model. diff --git a/orm/manual/en/transactions-and-concurrency.txt b/orm/manual/en/transactions-and-concurrency.txt deleted file mode 100644 index 302efd37e..000000000 --- a/orm/manual/en/transactions-and-concurrency.txt +++ /dev/null @@ -1,124 +0,0 @@ -++ Transaction Demarcation - -Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very important because if not done properly it can have a negative effect on the performance of your application. Many databases and database abstraction layers like PDO by default operate in auto-commit mode, which means that every single SQL statement is wrapped in a small transaction that is immediately committed. Without any explicit transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap and many small transactions degrade the performance of your application. - -For the most part, Doctrine 2 already takes care of proper transaction demarcation for you: All the write operations (INSERT/UPDATE/DELETE) are queued until `EntityManager#flush()` is invoked which wraps all of these changes in a single, small transaction. This is a strategy called "transactional write-behind" that is frequently used in ORM solutions to increase efficiency. - -However, Doctrine 2 also allows you to take over and control transaction demarcation yourself, thereby "widening" the transaction boundaries. This is possible due to transparent nesting of transactions that is described in the following section. - -++ Transaction Nesting - -Each `Doctrine\DBAL\Driver\Connection` instance is wrapped in a `Doctrine\DBAL\Connection` that adds support for transparent nesting of transactions. For that purpose, the Connection class keeps an internal counter that represents the nesting level and is increased/decreased as beginTransaction(), commit() and rollback() are invoked. beginTransaction() increases the nesting level whilst commit() and rollback() decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1, beginTransaction() is invoked on the underlying driver and whenever the nesting level transitions from 1 to 0, commit() or rollback() is invoked on the underlying driver, depending on whether the transition was caused by `Connection#commit()` or `Connection#rollback()`. - -Lets visualize what that means in practice. It means that the first call to `Doctrine\DBAL\Connection#beginTransaction()` will increase the nesting level from 0 to 1 and invoke beginTransaction() on the underlying driver, effectively starting a "real" transaction by suspending auto-commit mode. Any subsequent, nested calls to `Doctrine\DBAL\Connection#beginTransaction()` would only increase the nesting level. - -Here is an example to help visualize how this works: - - [php] - // $conn instanceof Doctrine\DBAL\Connection - try { - $conn->beginTransaction(); // 0 => 1, "real" transaction started - - ... - - try { - $conn->beginTransaction(); // 1 => 2 - - ... - - $conn->commit(); // 2 => 1 - } catch (Exception $e) { - $conn->rollback(); // 2 => 1 - throw $e; - } - - ... - - $conn->commit(); // 1 => 0, "real" transaction committed - } catch (Exception $e) { - $conn->rollback(); // 1 => 0, "real" transaction rollback - throw $e; - } - -What is the benefit of this? It allows reliable and transparent widening of transaction boundaries. Given the following code snippet, without any explicit transaction demarcation: - - [php] - // $em instanceof EntityManager - $user = new User; - $user->setName('George'); - $em->persist($user); - $em->flush(); - -Inside `EntityManager#flush()` something like this happens: - - [php] - try { - $conn->beginTransaction(); // suspend auto-commit - - ... commit all changes to the database ... - - $conn->commit(); - } catch (Exception $e) { - $conn->rollback(); - throw $e; - } - -Since we do not do any custom transaction demarcation in the first snippet, `EntityManager#flush()` will begin and commit/rollback a "real" transaction. Now, if we want to widen the transaction boundaries, say, because we want to include some manual work with a `Doctrine\DBAL\Connection` in the same transaction, we can simply do this: - - [php] - // $em instanceof EntityManager - $conn = $em->getConnection(); - try { - $conn->beginTransaction(); // suspend auto-commit - - // Direct use of the Connection - $conn->insert(...); - - $user = new User; - $user->setName('George'); - $em->persist($user); - $em->flush(); - - $conn->commit(); - } catch (Exception $e) { - $conn->rollback(); - // handle or rethrow - } - -Now, our own code controls the "real" transaction and the transaction demarcation that happens inside `EntityManager#flush()` will merely affect the nesting level. When flush() returns, either by throwing an exception or regularly, the nesting level is the same as before the invocation of flush(), in this case 1, and thus our own $conn->commit() / $conn->rollback() affect the "real" transaction as expected, since we were the ones who started the transaction. - -> **CAUTION** -> Directly invoking `PDO#beginTransaction()`, `PDO#commit()` or `PDO#rollback()` or the -> corresponding methods on the particular `Doctrine\DBAL\Driver\Connection` instance in -> use bybasses the transparent transaction nesting that is provided by -> `Doctrine\DBAL\Connection` and can therefore corrupt the nesting level, causing errors -> with broken transaction boundaries that may be hard to debug. - -++ Optimistic Locking - -Database transactions are fine for concurrency control during a single request. However, a database transaction should not span across requests, the so-called "user think time". Therefore a long-running "business transaction" that spans multiple requests needs to involve several database transactions. Thus, database transactions alone can no longer control concurrency during such a long-running business transaction. Concurrency control becomes the partial responsibility of the application itself. - -Doctrine has integrated support for automatic optimistic locking via a version field. In this approach any entity that should be protected against concurrent modifications during long-running business transactions gets a version field that is either a simple number (mapping type: integer) or a timestamp (mapping type: datetime). When changes to such an entity are persisted at the end of a long-running conversation the version of the entity is compared to the version in the database and if they dont match, an `OptimisticLockException` is thrown, indicating that the entity has been modified by someone else already. - -You designate a version field in an entity as follows. In this example we'll use an integer. - - [php] - class User - { - // ... - /** @Version @Column(type="integer") */ - private $version; - // ... - } - -You could also just as easily use a datetime column and instead of incrementing an integer, a timestamp will be kept up to date. - - - [php] - class User - { - // ... - /** @Version @Column(type="integer") */ - private $version; - // ... - } \ No newline at end of file diff --git a/orm/manual/en/working-with-objects.txt b/orm/manual/en/working-with-objects.txt deleted file mode 100644 index d6c76ad07..000000000 --- a/orm/manual/en/working-with-objects.txt +++ /dev/null @@ -1,445 +0,0 @@ -++ Understanding - -In this chapter we will help you understand the `EntityManager` and the `UnitOfWork`. -A Unit of Work is similar to an object-level transaction. A new Unit of Work is -implicity started when an EntityManager is initially created or after -`EntityManager#flush()` has been invoked. A Unit of Work is committed -(and a new one started) by invoking `EntityManager#flush()`. - -A Unit of Work can be manually closed by calling EntityManager#close(). Any -changes to objects within this Unit of Work that have not yet been persisted -are lost. - -+++ The size of a Unit of Work - -The size of a Unit of Work mainly refers to the number of managed entities at -a particular point in time. - -+++ The cost of flush() - -How costly a flush operation is in terms of performance mainly depends on 2 factors: - -* The size of your current Unit of Work -* The configured change tracking policies - -You can get the size of your Unit of Work as follows: - - [php] - $uowSize = $em->getUnitOfWork()->size(); - -The size represents the number of managed entities in the Unit of Work. This -size affects the performance of flush() operations due to change tracking -(see "Change Tracking Policies") and, of course, memory consumption, so you -may want to check it from time to time during development. - -> **CAUTION** -> Do not invoke `flush` after every change to an entity or every single invocation of -> persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the -> performance of your application. Instead form units of work that operate on your objects -> and call `flush` when you are done. While serving a single HTTP request there should -> be no need for invoking `flush` more than 0-2 times. - -+++ Direct access to a Unit of Work - -You can get direct access to the Unit of Work by calling `EntityManager#getUnitOfWork()`. -This will return the UnitOfWork instance the EntityManager is currently using. - - [php] - $uow = $em->getUnitOfWork(); - -> **NOTE** -> Directly manipulating a UnitOfWork is not recommended. When working directly with the -> UnitOfWork API respect methods marked as INTERNAL by not using them and carefully read -> the API documentation. - -++ Persisting entities - -An entity can be made persistent by passing it to the `EntityManager#persist($entity)` -method. By applying the persist operation on some entity, that entity becomes MANAGED, -which means that its persistence is from now on managed by an EntityManager. As a -result the persistent state of such an entity will subsequently be properly -synchronized with the database when `EntityManager#flush()` is invoked. - -> **CAUTION** -> Invoking the `persist` method on an entity does NOT cause an immediate SQL INSERT to be -> issued on the database. Doctrine applies a strategy called "transactional write-behind", -> which means that it will delay most SQL commands until `EntityManager#flush()` is -> invoked which will then issue all necessary SQL statements to synchronize your objects -> with the database in the most efficient way and a single, short transaction, -> taking care of maintaining referential integrity. - -- - -> **CAUTION** -> Generated entity identifiers / primary keys are guaranteed to be available after the -> next invocation of `EntityManager#flush()` that involves the entity in question. -> YOU CAN NOT RELY ON A GENERATED IDENTIFIER TO BE AVAILABLE AFTER INVOKING `persist`! - -Example: - - [php] - $user = new User; - $user->setName('Mr.Right'); - $em->persist($user); - $em->flush(); - // If $user had a generated identifier, it would now be available. - -The semantics of the persist operation, applied on an entity X, are as follows: - -* If X is a new entity, it becomes managed. The entity X will be entered into the database at or before transaction commit or as a result of the flush operation. -* If X is a preexisting managed entity, it is ignored by the persist operation. However, the persist operation is cascaded to entities referenced by X, if the relationships from X to these other entities are mapped with cascade=PERSIST or cascade=ALL (see "Transitive Persistence"). -* If X is a removed entity, it becomes managed. -* If X is a detached entity, an InvalidArgumentException will be thrown. - - -++ Removing entities - -An entity can be removed from persistent storage by passing it to the `EntityManager#remove($entity)` method. By applying the `remove` operation on some entity, that entity becomes REMOVED, which means that its persistent state will be deleted once `EntityManager#flush()` is invoked. The in-memory state of an entity is unaffected by the `remove` operation. - -> **CAUTION** -> Just like `persist`, invoking `remove` on an entity does NOT cause an immediate SQL -> DELETE to be issued on the database. The entity will be deleted on the next invocation -> of `EntityManager#flush()` that involves that entity. - -Example: - - [php] - $em->remove($user); - $em->flush(); - -The semantics of the remove operation, applied to an entity X are as follows: - -* If X is a new entity, it is ignored by the remove operation. However, the remove operation is cascaded to entities referenced by X, if the relationship from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). -* If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). -* If X is a detached entity, an InvalidArgumentException will be thrown. -* If X is a removed entity, it is ignored by the remove operation. -* A removed entity X will be removed from the database at or before transaction commit or as a result of the flush operation. - - -++ Detaching entities - -An entity is detached from an EntityManager and thus no longer managed by -invoking the `EntityManager#detach($entity)` method on it or by cascading -the detach operation to it. Changes made to the detached entity, if any -(including removal of the entity), will not be synchronized to the database -after the entity has been detached. - -Doctrine will not hold on to any references to a detached entity. - -Example: - - [php] - $em->detach($entity); - -The semantics of the detach operation, applied to an entity X are as follows: - -* If X is a managed entity, the detach operation causes it to become detached. The detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X. -* If X is a new or detached entity, it is ignored by the detach operation. -* If X is a removed entity, the detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X. - -There are several situations in which an entity is detached automatically without invoking the `detach` method: - -* When `EntityManager#clear()` is invoked, all entities that are currently managed by the EntityManager instance become detached. -* When serializing an entity. The entity retrieved upon subsequent unserialization will be detached (This is the case for all entities that are serialized and stored in some cache, i.e. when using the Query Result Cache). - -The `detach` operation is usually not as frequently needed and used as `persist` and `remove`. - - -++ Merging entities - -Merging entities refers to the merging of (usually detached) entities into the -context of an EntityManager so that they become managed again. To merge the -state of an entity into an EntityManager use the `EntityManager#merge($entity)` -method. The state of the passed entity will be merged into a managed copy of -this entity and this copy will subsequently be returned. - -Example: - - [php] - $detachedEntity = unserialize($serializedEntity); // some detached entity - $entity = $em->merge($detachedEntity); - // $entity now refers to the fully managed copy returned by the merge operation. - // The EntityManager $em now manages the persistence of $entity as usual. - -> **WARNING** -> When you want to serialize/unserialize entities you have to make all entity properties -> protected, never private. The reason for this is, if you serialize a class that was a proxy -> instance before the private variables won't be serialized and a PHP Notice is thrown. - -The semantics of the merge operation, applied to an entity X, are as follows: - -* If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity or a new managed copy X' of X is created. -* If X is a new entity instance, an InvalidArgumentException will be thrown. -* If X is a removed entity instance, an InvalidArgumentException will be thrown. -* If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been mapped with the cascade element value MERGE or ALL (see "Transitive Persistence"). -* For all entities Y referenced by relationships from X having the cascade element value -MERGE or ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.) -* If X is an entity merged to X', with a reference to another entity Y, where cascade=MERGE or cascade=ALL is not specified, then navigation of the same association from X' yields a reference to a managed object Y' with the same persistent identity as Y. - -The `merge` operation will throw an `OptimisticLockException` if the entity -being merged uses optimistic locking through a version field and the versions -of the entity being merged and the managed copy dont match. This usually means -that the entity has been modified while being detached. - -The `merge` operation is usually not as frequently needed and used as `persist` -and `remove`. The most common scenario for the `merge` operation is to reattach -entities to an EntityManager that come from some cache (and are therefore detached) -and you want to modify and persist such an entity. - -> **NOTE** -> If you load some detached entities from a cache and you do not need to persist or -> delete them or otherwise make use of them without the need for persistence services -> there is no need to use `merge`. I.e. you can simply pass detached objects from a cache -> directly to the view. - - -++ Associations - -Associations between entities are represented just like in regular object-oriented PHP, with references to other objects or collections of objects. When it comes to persistence, it is important to understand three main things: - - * The concept of owning and inverse sides in bidirectional associations as described [here](http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#owning-side-and-inverse-side). - * A collection of entities always only represents the association to the containing entities. If an entity is removed from a collection, the association is removed, not the entity itself. - * Collection-valued persistent fields and properties must be defined in terms of the Doctrine\Common\Collections\Collection interface. [See here](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities:persistent-fields) for more details. - - -++ Establishing Associations - -Establishing an association between two entities is straight-forward. Here are some examples: - - [php] - // Article <- one-to-many -> Comment - $article->getComments()->add($comment); - $comment->setArticle($article); - - // User <- many-to-many -> Groups - $user->getGroups()->add($group); - $group->getUsers()->add($user); - - // User <- one-to-one -> Address - $user->setAddress($address); - $address->setUser($user); - - -Notice how always both sides of the bidirectional association are updated. Unidirectional associations are consequently simpler to handle. - -++ Removing Associations - -Removing an association between two entities is similarly straight-forward. There are two strategies -to do so, by key and by element. Here are some examples: - - [php] - // Remove by Elements - // Article <- one-to-many -> Comment - $article->getComments()->removeElement($comment); - $comment->setArticle(null); - - // User <- many-to-many -> Group - $user->getGroups()->removeElement($group); - $group->getUsers()->removeElement($user); - - // Remove by key - $article->getComments()->remove($ithComment); - $comment->setArticle(null); - - // User <- one-to-one -> Address - $user->setAddress(null); - $address->setUser(null); - - -Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does not allow null values and setAddress(null) will fail for removing the association. If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`. This can also provide better encapsulation as it hides the internal meaning of not having an address. - -Since Doctrine always only looks at the owning side of a bidirectional association, it is essentially not necessary that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. - - -++ Association Management Methods - -It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained. - -The following code shows a simple, idiomatic example for a bidirectional one-to-many association between an Article and its Comments. - - [php] - // Mappings not shown. - class Article { - // The comments of the article. - private $comments; - // ... constructor omitted ... - public function addComment(Comment $comment) { - $this->comments->add($comment); - $comment->setArticle($this); - } - public function getComments() { - return $this->comments; - } - } - class Comment { - // The article the comment refers to. - private $article; - // ... constructor omitted ... - public function setArticle($article) { - $this->article = $article; - } - public function getArticle() { - return $this->article; - } - } - -With the above implementation, it is always ensured that at least the owning side from Doctrine's point of view (Comment) is properly updated. You will notice that `setArticle` does not call `addComment`, thus the bidirectional association is strictly-speaking still incomplete, if a user of the class only invokes `setArticle`. If you naively call `addComment` in `setArticle`, however, you end up with an infinite loop, so more work is needed. As you can see, proper bidirectional association management in plain OOP is a non-trivial task and encapsulating all the details inside the classes can be challenging. - -There is no single, best way for association management. It greatly depends on the requirements of your concrete domain model as well as your preferences. - - -++ Transitive persistence - -Persisting, removing, detaching and merging individual entities can become pretty -cumbersome, especially when a larger object graph with collections is involved. -Therefore Doctrine 2 provides a mechanism for transitive persistence through -cascading of these operations. Each association to another entity or a collection -of entities can be configured to automatically cascade certain operations. By -default, no operations are cascaded. - -The following cascade options exist: - - * persist : Cascades persist operations to the associated entities. - * remove : Cascades remove operations to the associated entities. - * merge : Cascades merge operations to the associated entities. - * detach : Cascades detach operations to the associated entities. - * all : Cascades persist, remove, merge and detach operations to associated entities. - -The following example shows an association to a number of addresses. If persist() -or remove() is invoked on any User entity, it will be cascaded to all associated -Address entities in the $addresses collection. - - [php] - class User - { - //... - /** - * @OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "remove"}) - */ - private $addresses; - //... - } - -Even though automatic cascading is convenient it should be used with care. -Do not blindly apply cascade=all to all associations as it will unnecessarily -degrade the performance of your application. - - -++ Querying - -Doctrine 2 provides the following ways, in increasing level of power and flexibility, to query for persistent objects. You should always start with the simplest one that suits your needs. - -+++ By Primary Key - -The most basic way to query for a persistent object is by its identifier / primary key using the `EntityManager#find($entityName, $id)` method. Here is an example: - - [php] - // $em instanceof EntityManager - $user = $em->find('MyProject\Domain\User', $id); - -The return value is either the found entity instance or null if no instance could be found with the given identifier. - -Essentially, `EntityManager#find()` is just a shortcut for the following: - - [php] - // $em instanceof EntityManager - $user = $em->getRepository('MyProject\Domain\User')->find($id); - -`EntityManager#getRepository($entityName)` returns a repository object which provides many ways to retreive entities of the specified type. By default, the repository instance is of type `Doctrine\ORM\EntityRepository`. You can also use custom repository classes as shown later. - -+++ By Simple Conditions - -To query for one or more entities based on several conditions that form a logical conjunction, use the `findBy` and `findOneBy` methods on a repository as follows: - - [php] - // $em instanceof EntityManager - - // All users that are 20 years old - $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20)); - - // All users that are 20 years old and have a surname of 'Miller' - $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller')); - - // A single user by its nickname - $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); - -An EntityRepository also provides a mechanism for more concise calls through its use of `__call`. Thus, the following two examples are equivalent: - - [php] - // A single user by its nickname - $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); - - // A single user by its nickname (__call magic) - $user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb'); - - -+++ By Eager Loading - -Whenever you query for an entity that has persistent associations and these associations are mapped as EAGER, they will automatically be loaded together with the entity being queried and is thus immediately available to your application. - - -+++ By Lazy Loading - -Whenever you have a managed entity instance at hand, you can traverse and use any associations of that entity that are configured LAZY as if they were in-memory already. Doctrine will automatically load the associated objects on demand through the concept of lazy-loading. - - -+++ By DQL - -The most powerful and flexible method to query for persistent objects is the Doctrine Query Language, an object query language. DQL enables you to query for persistent objects in the language of objects. DQL understands classes, fields, inheritance and associations. -DQL is syntactically very similar to the familar SQL but *it is not SQL*. - -A DQL query is represented by an instance of the `Doctrine\ORM\Query` class. You create a query using `EntityManager#createQuery($dql)`. Here is a simple example: - - [php] - // $em instanceof EntityManager - - // All users with an age between 20 and 30 (inclusive). - $q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30"); - $users = $q->getResult(); - -Note that this query contains no knowledge about the relational schema, only about the object model. DQL supports positional as well as named parameters, many functions, (fetch) joins, aggregates, subqueries and much more. Detailed information about DQL and its syntax as well as the Doctrine\ORM\Query class can be found in [the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language). For programmatically building up queries based on conditions that are only known at runtime, Doctrine provides the special `Doctrine\ORM\QueryBuilder` class. More information on constructing queries with a QueryBuilder can be found [in the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/query-builder). - - -+++ By Native Queries - -As an alternative to DQL or as a fallback for special SQL statements native queries can be used. -Native queries are built by using a hand-crafted SQL query and a ResultSetMapping that describes -how the SQL result set should be transformed by Doctrine. More information about native queries -can be found in [the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/native-sql). - -+++ Custom Repositories - -By default the EntityManager returns a default implementation of `Doctrine\ORM\EntityRepository` when -you call `EntityManager#getRepository($entityClass)`. You can overwrite this behaviour by specifying -the class name of your own Entity Repository in the Annotation, XML or YAML metadata. -In large applications that require lots of specialized DQL queries using a custom repository is -one recommended way of grouping these queries in a central location. - - [php] - namespace MyDomain\Model; - use Doctrine\ORM\EntityRepository; - - /** - * @entity(repositoryClass="MyDomain\Model\UserRepository") - */ - class User - { - - } - - class UserRepository extends EntityRepository - { - public function getAllAdminUsers() - { - return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"') - ->getResult(); - } - } - -You can access your repository now by calling: - - [php] - // $em instanceof EntityManager - - $admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers(); - diff --git a/orm/manual/en/xml-mapping.txt b/orm/manual/en/xml-mapping.txt deleted file mode 100644 index 0db537be7..000000000 --- a/orm/manual/en/xml-mapping.txt +++ /dev/null @@ -1,83 +0,0 @@ -The XML mapping driver enables you to provide the ORM metadata in form of XML documents. - -The XML driver is backed by an XML Schema document that describes the structure of a mapping document. The most recent version of the XML Schema document is available online at [http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd](http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd). In order to point to the latest version of the document of a particular stable release branch, just append the release number, i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with XML mapping files is to use an IDE/editor that can provide code-completion based on such an XML Schema document. The following is an outline of a XML mapping document with the proper xmlns/xsi setup for the latest code in trunk. - - [xml] - - - ... - - - -The XML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: - - * Each entity/mapped superclass must get its own dedicated XML mapping document. - * The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.). - * All mapping documents should get the extension ".dcm.xml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough. - -- - - [php] - $driver->setFileExtension('.xml'); - -It is recommended to put all XML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the XmlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: - - [php] - // $config instanceof Doctrine\ORM\Configuration - $driver = new XmlDriver(array('/path/to/files')); - $config->setMetadataDriverImpl($driver); - - -++ Example - -As a quick start, here is a small example document that makes use of several common elements: - - [xml] - // Doctrine.Tests.ORM.Mapping.User.dcm.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Be aware that class-names specified in the XML files should be fully qualified. \ No newline at end of file diff --git a/orm/manual/en/yaml-mapping.txt b/orm/manual/en/yaml-mapping.txt deleted file mode 100644 index a5c6b29c7..000000000 --- a/orm/manual/en/yaml-mapping.txt +++ /dev/null @@ -1,66 +0,0 @@ -The YAML mapping driver enables you to provide the ORM metadata in form of YAML documents. - -The YAML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: - - * Each entity/mapped superclass must get its own dedicated YAML mapping document. - * The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.). - * All mapping documents should get the extension ".dcm.yml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough. - -- - - [php] - $driver->setFileExtension('.yml'); - -It is recommended to put all YAML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the YamlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: - - [php] - // $config instanceof Doctrine\ORM\Configuration - $driver = new YamlDriver(array('/path/to/files')); - $config->setMetadataDriverImpl($driver); - - -++ Example - -As a quick start, here is a small example document that makes use of several common elements: - - [yml] - # Doctrine.Tests.ORM.Mapping.User.dcm.yml - Doctrine\Tests\ORM\Mapping\User: - type: entity - table: cms_users - id: - id: - type: integer - generator: - strategy: AUTO - fields: - name: - type: string - length: 50 - oneToOne: - address: - targetEntity: Address - joinColumn: - name: address_id - referencedColumnName: id - oneToMany: - phonenumbers: - targetEntity: Phonenumber - mappedBy: user - cascade: cascadePersist - manyToMany: - groups: - targetEntity: Group - joinTable: - name: cms_users_groups - joinColumns: - user_id: - referencedColumnName: id - inverseJoinColumns: - group_id: - referencedColumnName: id - lifecycleCallbacks: - prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] - postPersist: [ doStuffOnPostPersist ] - - Be aware that class-names specified in the YAML files should be fully qualified. \ No newline at end of file From ee34c42697d960ab1be470d32e1ad0f04eaf6861 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Tue, 6 Apr 2010 14:43:34 -0400 Subject: [PATCH 005/430] Adding dbal chapter back --- manual/en.txt | 1 + manual/en/dbal.txt | 245 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 manual/en/dbal.txt diff --git a/manual/en.txt b/manual/en.txt index b17808194..7cc3be93f 100644 --- a/manual/en.txt +++ b/manual/en.txt @@ -17,4 +17,5 @@ + Caching + Improving Performance + Tools ++ DBAL + Best Practices \ No newline at end of file diff --git a/manual/en/dbal.txt b/manual/en/dbal.txt new file mode 100644 index 000000000..452f45646 --- /dev/null +++ b/manual/en/dbal.txt @@ -0,0 +1,245 @@ +The Doctrine 2 database layer can be used independently of the object-relational mapping. It offers a leightweight abstraction layer around a PDO like API and allows optional access to lots of convenience functionality aswell as the ability to generate platform independent DQL and DDL statements. + +++ Configuration + +You can create a Doctrine Connection by using the `Doctrine\DBAL\DriverManager` class. + + [php] + $config = new \Doctrine\DBAL\Configuration(); + //.. + + $connectionParams = array( + 'dbname' => 'mydb', + 'user' => 'user', + 'password' => 'secret', + 'host' => 'locahlost', + 'driver' => 'pdo_mysql', + ); + $conn = DriverManager::getConnection($connectionParams); + +The `DriverManager` returns an instance of `Doctrine\DBAL\Connection` which is a wrapper around any configured database driver, for example the PDO Mysql driver in the previous example. + ++++ Connection Options + +The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array +or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the +DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The following option keys can be specified +to create your connection: + +++++ Driver Management Options: + +* driver - Allows to specify the default drivers shipped with Doctrine 2, 'pdo_mysql', 'pdo_sqlite', 'pdo_pgsql, and 'oci'. +* driverClass - If no 'driver' is specified this allows usage of a userland implementation of Doctrine\DBAL\Driver. +* pdo - If PDO is already instantiated for Mysql, SqLite or PgSQL this key can be used to pass this instance into Doctrine. +* wrapperClass - By default Doctrine\DBAL\Connection is wrapped around each driver, however this option allows to specify a userland sub-class. + +++++ Driver Configuration Options: + +Common Configuration Options across all database drivers: + +* platform - An instance of `Doctrine\DBAL\Platforms\AbstractPlatform`. This is only required for userland implementations, each driver shipped with Doctrine 2 has a default platform. +* user - Username required to connect to the database. +* password - Password required to connect to the database. +* driverOptions - Array of options passed to the driver instance on calling to Driver::connect. + +Driver Configuration Options are different for each Database Driver, here are some of the db specific ones: + +* host - Database host (Mysql, Pgsql, Oracle) +* port - Database port (Mysql, Pgsql, Oracle) +* dbname - Name of the database/schema to connect to. (Mysql, Pgsql, Oracle) +* unix_socket - Name of the socket used to connect to the database. (Mysql) +* charset - The charset used when connecting to the database. (Oracle) +* path - The filesystem path to the database (Sqlite) +* memory - True if the sqlite database should be in memory. (Sqlite) + ++++ DBAL Events + +Both `Doctrine\DBAL\DriverManager` and `Doctrine\DBAL\Connection` accept an instance of `Doctrine\Common\EventManager`. +The EventManager has a couple of events inside the DBAL layer that are triggered for the user to listen to: + +++++ PostConnect Event + +`Doctrine\DBAL\Events::postConnect` is triggered right after the connection to the database is established. It allows +to specify any relevant connection specific options and gives access to the `Doctrine\DBAL\Connection` instance +that is responsible for the connection management via an instance of `Doctrine\DBAL\Event\ConnectionEventArgs` +event arguments instance. + +Doctrine is already shipped with two implementations for the "PostConnect" event: + +* `Doctrine\DBAL\Event\Listeners\OracleSessionInit` allows to specify any number of Oracle Session related enviroment variables that are set right after the connection is established. +* `Doctrine\DBAL\Event\Listeners\MysqlSessionInit` allows to specify the Charset and Collation of the Client Connection if these options are not configured correctly on the MySQL server side. + +You can register events by subscribing them to the `EventManager` instance passed to the Connection factory: + + [php] + $evm = new EventManager(), + $evm->addEventSubscriber(new MysqlSessionInit('UTF-8')); + + $conn = DriverManager::getConnection($connectionParams, null, $evm); + +++ DBAL API + ++++ DBAL Architecture + +The DBAL is seperated into several different packages that perfectly seperate responsibilities of the different RDBMS layers. + +* **Drivers** abstract a PHP specific database API by enforcing two interfaces `\Doctrine\DBAL\Driver\Driver` and `\Doctrine\DBAL\Driver\Statement` which require exactly the same methods as PDO. +* **Platforms** abstract the generation of queries and which database features a platform supports. The `\Doctrine\DBAL\Platforms\AbstractPlatform` defines the common denominator of what a database platform has to publish to the userland, to be fully supportable by Doctrine. This includes the SchemaTool, Transaction Isolation and many other features. The Database platform for MySQL for example can be used by all 3 mysql extensions, PDO, Mysqli and ext/mysql. +* **Logging** holds the interface and some implementations for debugging of Doctrine SQL query execution during a request. +* **Schema** offers an API for each database platform to execute DDL statements against your platform or retrieve metadata about it. It also holds the Schema Abstraction Layer which is used by the different Schema Management facilities of Doctrine DBAL and ORM. +* **Types** offers an abstraction layer for the converting and generation of types between Databases and PHP. + ++++ Data Retrieval and Manipulation + + The following methods exist for executing queries against your configured database, three very generic methods and some advanced retrievial methods: + +* `prepare($sql)` - Prepare a given sql statement and return the `\Doctrine\DBAL\Driver\Statement` instance. +* `executeUpdate($sql, array $params)` - Executes a prepared statement with the given sql and parameters and returns the affected rows count. +* `execute($sql, array $params)` - Creates a prepared statement for the given sql and passes the parameters to the execute method, then returning the statement. +* `fetchAll($sql, array $params)` - Execute the query and fetch all results into an array. +* `fetchArray($sql, array $params)` - Numeric index retrieval of first result row of the given query. +* `fetchBoth($sql, array $params)` - Both numeric and assoc column name retrieval of the first result row. +* `fetchColumn($sql, array $params, $colnum)` - Retrieve only the given column of the first result row. +* `fetchRow($sql, array $params)` - Retrieve assoc row of the first result row. +* `select($sql, $limit, $offset)` - Modify the given query with a limit clause. + +There are also convenience methods for data manipulation queries: + +* `delete($tableName, array $identifier)` - Delete all rows of a table matching the given identifier, where keys are column names. +* `insert($tableName, array $data)` - Insert a row into the given table name using the key value pairs of data. +* `update($tableName, array $data, array $identifier)` - Update all rows for the matching key value identifiers with the given data. + +By default the Doctrine DBAL does no escaping. Escaping is a very tricky business to do automagically, therefore there is none by default. The ORM internally escapes all your values, because it has lots of metadata available about the current context. When you use the Doctrine DBAL as standalone, you have to take care of this yourself. The following methods help you with it: + +* `quote($input, $type=null)` - Quote a value +* `quoteIdentifier($identifier)`- Quote an identifier according to the platform details. + ++++ Transactions + +Doctrine handles transactions with a PDO like API, having methods for `beginTransaction()`, `commit()` and `rollBack()`. For consistency across different drivers Doctrine also handles the nesting of transactions internally. You can call `beginTransaction()` more than once, and only a matching amount of calls to `commit()` triggers the commit to the database. The Doctrine connectionalso has a method to set the transaction isolation level of the connection as supported by the underlying database. + + [php] + class Connection + { + /** + * Constant for transaction isolation level READ UNCOMMITTED. + */ + const TRANSACTION_READ_UNCOMMITTED = 1; + + /** + * Constant for transaction isolation level READ COMMITTED. + */ + const TRANSACTION_READ_COMMITTED = 2; + + /** + * Constant for transaction isolation level REPEATABLE READ. + */ + const TRANSACTION_REPEATABLE_READ = 3; + + /** + * Constant for transaction isolation level SERIALIZABLE. + */ + const TRANSACTION_SERIALIZABLE = 4; + } + +A transaction with Doctrine DBAL might then look like: + + [php] + $conn->setTransactionIsolationLevel(Connection::TRANSACTION_SERIALIZABLE); + + try{ + $conn->beginTransaction(); + // do stuff + $conn->commit(); + } catch(\Exception $e) { + $conn->rollback(); + } + +++ Schema Representation + +Doctrine has a very powerful abstraction of database schemas. It offers an object-oriented representation of a database schema with support for all the details of Tables, Sequences, Indexes and Foreign Keys. These Schema instances generate a representation that is equal for all the supported platforms. Internally this functionality is used by the ORM Schema Tool to offer you create, drop and update database schema methods from your Doctrine ORM Metadata model. Up to very specific functionality of your database system this allows you to generate SQL code that makes your Domain model work. + +You will be pleased to hear, that Schema representation is completly decoupled from the Doctrine ORM though, that is you can also use it in any other project to implement database migrations or for SQL schema generation for any metadata model that your application has. You can easily generate a Schema, as a simple example shows: + + [php] + $schema = new \Doctrine\DBAL\Schema\Schema(); + $myTable = $schema->createTable("my_table"); + $myTable->addColumn("id", "integer", array("unsigned" => true)); + $myTable->addColumn("username", "string", array("length" => 32)); + $myTable->setPrimaryKey(array("id")); + $myTable->addUniqueIndex(array("username")); + $schema->createSequence("my_table_seq"); + + $myForeign = $schema->createTable("my_foreign"); + $myForeign->addColumn("id", "integer"); + $myForeign->addColumn("user_id", "integer"); + $myForeign->addForeignKeyConstraint($myTable, array("user_id"), array("id"), array("onUpdate" => "CASCADE")); + + $queries = $schema->toSql($myPlatform); // get queries to create this schema. + $dropSchema = $schema->toDropSql($myPlatform); // get queries to safely delete this schema. + +Now if you want to compare this schema with another schema, you can use the `Comparator` class to get instances of `SchemaDiff`, `TableDiff` and `ColumnDiff`, aswell as information about other foreign key, sequence and index changes. + + [php] + $comparator = new \Doctrine\DBAL\Schema\Comparator(); + $schemaDiff = $comparator->compare($fromSchema, $toSchema); + + $queries = $schemaDiff->toSql($myPlatform); // queries to get from one to another schema. + $saveQueries = $schemaDiff->toSaveSql($myPlatform); + +The Save Diff mode is a specific mode that prevents the deletion of tables and sequences that might occour when making a diff of your schema. This is often necessary when your target schema is not complete but only describes a subset of your application. + +All methods that generate SQL queries for you make much effort to get the order of generation correct, so that no problems will ever occour with missing links of foreign keys. + +++ Platforms + +Platforms abstract query generation and specifics of the RDBMS featuresets. In most cases you don't need to interact with this package alot, but there might be certain cases when you are programming database independent where you want to access the platform to generate queries for you. + +The platform can be accessed from any `Doctrine\DBAL\Connection` instance by calling the `getDatabasePlatform()` method. + +You can use your own platform by specifying the 'platform' key with an instance of your own platform: + + [php] + $myPlatform = new MyPlatform(); + $options = array( + 'driver' => 'pdo_sqlite', + 'path' => 'database.sqlite', + 'platform' => $myPlatform + ); + +This way you can optimize your schema or generated SQL code with features that might not be portable for instance, +however are required for your special needs. + +++ Schema Manager + +A Schema Manager instance helps you with the abstraction of the generation of SQL assets such as Tables, Sequences, Foreign Keys and Indexes. You can use any of the Schema Asset classes `Table`, `Sequence`, `ForeignKeyConstraint` and `Index` for use with the methods of the style `dropAndCreate(AssetName)($asset)`, `drop(AssetName)($asset)` and `create(AssetName)($asset)`. + +You also have methods to retrieve instances of those types from the current database you are connected to. These methods are: + +* `listDatabases()` +* `listSequences()` +* `listTableColumns($tableName)` +* `listTableDetails($tableName)` +* `listTableForeignKeys($tableName)` +* `listTableIndexes($tableName)` +* `listTables()` +* `listUsers()` +* `listViews()` + +For a complete representation of the current database you can use the `createSchema()` method which returns an instance of Schema, which you can use in conjunction with the SchemaTool or Schema Comparator. + +++ Supporting other Databases + +To support a database which is not currently shipped with Doctrine you have to implement the following interfaces and abstract classes: + +* `\Doctrine\DBAL\Driver\Driver` +* `\Doctrine\DBAL\Driver\Statement` +* `\Doctrine\DBAL\Platforms\AbstractPlatform` +* `\Doctrine\DBAL\Schema\AbstractSchemaManager` + +For an already supported platform but unsupported driver you only need to implement the first two interfaces, since the SQL Generation and Schema Management is already supported by the respective platform and schema instances. You can also make use of several Abstract Unittests in the `\Doctrine\Tests\DBAL` package to check if your platform behaves like all the others which is necessary for SchemaTool support, namely: + +* `\Doctrine\Tests\DBAL\Platforms\AbstractPlatformTestCase` +* `\Doctrine\Tests\DBAL\Functional\Schema\AbstractSchemaManagerTestCase` + +We would be very happy if any support for new databases would be contributed back to Doctrine to make it an even better product. From d1fbaae91fdc8600e7908cdded692772d5a77b6b Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 9 Apr 2010 01:09:14 -0300 Subject: [PATCH 006/430] [2.0-DOC] Changed manual with new Console implementation --- manual/en/configuration.txt | 4 +- manual/en/introduction.txt | 52 ++++++++++---- manual/en/tools.txt | 135 ++++++++++++++++++++++++------------ 3 files changed, 129 insertions(+), 62 deletions(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index b9a144bb3..3e62ba177 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -379,14 +379,14 @@ Just like other proxies it will transparently initialize itself on first access. +++ Generating Proxy classes -Proxy classes can either be generated manually through the Doctrine CLI or automatically by Doctrine. The configuration option that controls this behavior is: +Proxy classes can either be generated manually through the Doctrine Console or automatically by Doctrine. The configuration option that controls this behavior is: [php] $config->setAutoGenerateProxyClasses($bool); $config->getAutoGenerateProxyClasses(); The default value is `TRUE` for convenient development. However, this setting is not optimal for performance and therefore not recommended for a production environment. -To eliminate the overhead of proxy class generation during runtime, set this configuration option to `FALSE`. When you do this in a development environment, note that you may get class/file not found errors if certain proxy classes are not available or failing lazy-loads if new methods were added to the entity class that are not yet in the proxy class. In such a case, simply use the Doctrine CLI to (re)generate the proxy classes like so: +To eliminate the overhead of proxy class generation during runtime, set this configuration option to `FALSE`. When you do this in a development environment, note that you may get class/file not found errors if certain proxy classes are not available or failing lazy-loads if new methods were added to the entity class that are not yet in the proxy class. In such a case, simply use the Doctrine Console to (re)generate the proxy classes like so: doctrine orm:generate-proxies diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index 96f627d6a..1b4d7bf6c 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -89,17 +89,39 @@ available on your system. Now when you run the `doctrine` command you will see what you can do with it. $ doctrine - Doctrine Command Line Interface - Available Tasks: - core:help - dbal:run-sql (--file= | --sql=) --depth= - orm:clear-cache (--query | --metadata | --result [--id=] [--regex=] [--prefix=] [--suffix=]) - orm:convert-mapping (--from= | --from-database) --to= --dest= - orm:ensure-production-settings - orm:generate-proxies --class-dir= [--to-dir=] - orm:run-dql --dql= --depth= - orm:schema-tool (--create | --drop | --update | --complete-update | --re-create) [--dump-sql] [--class-dir=] - orm:version + Doctrine Command Line Interface version 2.0-DEV + + Usage: + [options] command [arguments] + + Options: + --help -h Display this help message. + --quiet -q Do not output any message. + --verbose -v Increase verbosity of messages. + --version -V Display this program version. + --color -c Force ANSI color output. + --no-interaction -n Do not ask any interactive question. + + Available commands: + help Displays help for a command (?) + list Lists commands + dbal + :import Import SQL file(s) directly to Database. + :run-sql Executes arbitrary SQL directly from the command line. + orm + :clear-cache:metadata Clear all metadata cache of the various cache drivers. + :clear-cache:query Clear all query cache of the various cache drivers. + :clear-cache:result Clear result cache of the various cache drivers. + :convert-d1-schema Converts Doctrine 1.X schema into a Doctrine 2.X schema. + :convert-mapping Convert mapping information between supported formats. + :ensure-production-settings Verify that Doctrine is properly configured for a production environment. + :generate-entities Generate entity classes and method stubs from your mapping information. + :generate-proxies Generates proxy classes for entity classes. + :generate-repositories Generate repository classes from your mapping information. + :run-dql Executes arbitrary DQL directly from the command line. + :schema-tool:create Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output. + :schema-tool:drop Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output. + :schema-tool:update Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output. +++ Package Download @@ -145,7 +167,7 @@ Here is a short overview of the purpose of these folders and files: * The `Entities` folder is where any model classes are created. Two example entities are already there. * The `xml` folder is where any XML mapping files are created (if you want to use XML mapping). Two example mapping documents for the 2 example entities are already there. * The `yaml` folder is where any YAML mapping files are created (if you want to use YAML mapping). Two example mapping documents for the 2 example entities are already there. - * The `cli-config.php` contains bootstrap code for a configuration that is used by the CLI tool `doctrine` whenever you execute a task. + * The `cli-config.php` contains bootstrap code for a configuration that is used by the Console tool `doctrine` whenever you execute a task. * `doctrine`/`doctrine.php` is a command-line tool. * `index.php` is a basic classical bootstrap file of a php application that uses Doctrine 2. @@ -154,9 +176,9 @@ Here is a short overview of the purpose of these folders and files: 1) From within the tools/sandbox folder, run the following command and you should see the same output. - $ php doctrine orm:schema-tool --create + $ php doctrine orm:schema-tool:create ./Entities Creating database schema... - Database schema created successfully. + Database schema created successfully! 2) Take another look into the tools/sandbox folder. A SQLite database should have been created with the name `database.sqlite`. @@ -181,7 +203,7 @@ the output "User saved!". 5) Inspect the SQLite database. Again from within the tools/sandbox folder, execute the following command: - $ php doctrine dbal:run-sql --sql="select * from users" + $ php doctrine dbal:run-sql "select * from users" You should get the following output: diff --git a/manual/en/tools.txt b/manual/en/tools.txt index d97136c69..e18d5f8e7 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -1,6 +1,6 @@ -++ The Doctrine CLI +++ The Doctrine Console -The Doctrine CLI (Command Line Interface) is a tool for simplifying many common tasks during the development of a project that uses Doctrine. +The Doctrine Console is a Command Line Interface tool for simplifying many common commands during the development of a project that uses Doctrine. +++ Installation @@ -11,62 +11,102 @@ In addition you may need to edit `doctrine.php` and adjust some paths to the new +++ Getting Help -Type `doctrine` on the command line and you should see an overview of the available tasks or use the --help flag to get information on the available tasks. If you want to know more about the use of the schema tool for example you can call: +Type `doctrine` on the command line and you should see an overview of the available commands or use the --help flag to get information on the available commands. If you want to know more about the use of generate entities for example, you can call: - doctrine orm:schema-tool --help + doctrine orm:generate-entities --help +++ Configuration -Whenever the `doctrine` command line tool is invoked it requires an instance of `Doctrine\Common\Cli\CliConfiguration` to be able to correctly work. When using ORM package, it is required to define an attribute inside Configuration: `em`. `em` must be an `EntityManager` instance that is used by ORM command-line tasks. +Whenever the `doctrine` command line tool is invoked, it is only able to access Commands that were defined by developer. Dependency Injection (DI) is the responsable to inject support into this utility, but it is up to the developer define it. -Many tasks of the Doctrine CLI require the `em` attribute to be an `EntityManager` in order to execute. The `EntityManager` instance implicitly defines a database connection. -If you invoke a task that requires an EntityManager (and/or a database connection) and the `em` attribute is not defined in your CLI Configuration instance, the task invoking will report an error for you. - -CLI COnfiguration instance can be in a separate file (ie. `cli-config.php`) that contains typical Doctrine bootstrap code and predefines the needed attributes mentioned above. A typical `cli-config.php` file looks as follows: +To include a new command on Doctrine Console, you need to do: [php] - require_once '/path/to/lib/Doctrine/Common/ClassLoader.php'; + $cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand()); - $classLoader = new \Doctrine\Common\ClassLoader('MyProject', '/path/to/myproject/lib'); +Additionally, include multiple commands (and overriding previously defined ones) is possible through the command: + + [php] + $cli->addCommands(array( + new \MyProject\Tools\Console\Commands\MyCustomCommand(), + new \MyProject\Tools\Console\Commands\SomethingCommand(), + new \MyProject\Tools\Console\Commands\AnotherCommand(), + new \MyProject\Tools\Console\Commands\OneMoreCommand(), + )); + +Many commands of the Doctrine Console requires either the `db` or the `em` helpers to be defined in order to work correctly. Doctrine Console requires the definition of a HelperSet that is the DI tool to be injected in the Console. +In case of a project that is dealing exclusvely with DBAL, the ConnectionHelper is required: + + [php] + $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( + 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn) + )); + $cli->setHelperSet($helperSet); + +When dealing with the ORM package, the EntityManagerHelper is required: + + [php] + $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( + 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) + )); + $cli->setHelperSet($helperSet); + +The HelperSet instance can be in a separate file (ie. `cli-config.php`) that contains typical Doctrine bootstrap code and predefines the needed HelperSet attributes mentioned above. A typical `cli-config.php` file looks as follows: + + [php] + require_once __DIR__ . '/../../lib/Doctrine/Common/ClassLoader.php'; + + $classLoader = new \Doctrine\Common\ClassLoader('Entities', __DIR__); $classLoader->register(); - $ormConfig = new \Doctrine\ORM\Configuration(); - $ormConfig->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); - $ormConfig->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); - $ormConfig->setProxyNamespace('MyProject\Proxies'); + $classLoader = new \Doctrine\Common\ClassLoader('Proxies', __DIR__); + $classLoader->register(); + + $config = new \Doctrine\ORM\Configuration(); + $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); + $config->setProxyDir(__DIR__ . '/Proxies'); + $config->setProxyNamespace('Proxies'); $connectionOptions = array( 'driver' => 'pdo_sqlite', 'path' => 'database.sqlite' ); - $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $ormConfig); + $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); - $cliConfig = new \Doctrine\Common\Cli\Configuration(); - $cliConfig->setAttribute('em', $em); - $cliConfig->setAttribute('globalArguments', array( - 'class-dir' => '/path/to/myproject/lib/MyProject/Models/' + $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( + 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), + 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); -It is important to define an instance of Doctrine\Common\Cli\Configuration that the doctrine.php script will ultimately use. For the required name of this variable, check the doctrine.php included in your package; the most new ones will automatically find the variable no matter what its name is, as long as it is an instance of the right class. -The CLI Configuration should contain at least the 'em' attribute, set to the EntityManager. To use many tasks a 'globalOptions' array should be set. -The `globalArguments` content will be passed to every command line task. For instance, to use the orm:schema-tool task to generate database tables from annotations, the class-dir global option must be set as in the example. +It is important to define a correct HelperSet that doctrine.php script will ultimately use. For the required name of this variable, check the doctrine.php included in your package; the most new ones will automatically find the variable no matter what its name is, as long as it is an instance of the right class. -+++ Task Overview ++++ Command Overview -The following tasks are currently available: +The following Commands are currently available: - * `dbal:run-sql`: Used to run arbitrary SQL on the command-line. - * `orm:convert-mapping`: Used to convert between annotations/xml/yaml mapping informations as well as for class generating from xml/yaml mapping documents or for reverse engineering. - * `orm:generate-proxies`: Used to generate proxy classes used by Doctrine. - * `orm:run-dql`: Used to run arbitrary DQL on the command-line. - * `orm:schema-tool`: Used to forward-engineer the relational database schema from existing classes and mappings. - * `orm:version`: Used to show the current version of the CLI and Doctrine. + * `help` Displays help for a command (?) + * `list` Lists commands + * `dbal:import` Import SQL file(s) directly to Database. + * `dbal:run-sql` Executes arbitrary SQL directly from the command line. + * `orm:clear-cache:metadata` Clear all metadata cache of the various cache drivers. + * `orm:clear-cache:query` Clear all query cache of the various cache drivers. + * `orm:clear-cache:result` Clear result cache of the various cache drivers. + * `orm:convert-d1-schema` Converts Doctrine 1.X schema into a Doctrine 2.X schema. + * `orm:convert-mapping` Convert mapping information between supported formats. + * `orm:ensure-production-settings` Verify that Doctrine is properly configured for a production environment. + * `orm:generate-entities` Generate entity classes and method stubs from your mapping information. + * `orm:generate-proxies` Generates proxy classes for entity classes. + * `orm:generate-repositories` Generate repository classes from your mapping information. + * `orm:run-dql` Executes arbitrary DQL directly from the command line. + * `orm:schema-tool:create` Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output. + * `orm:schema-tool:drop` Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output. + * `orm:schema-tool:update` Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output. ++ Database Schema Generation To generate your database schema from your Doctrine mapping files you can use the -`SchemaTool` class or the `schema-tool` CLI task. +`SchemaTool` class or the `schema-tool` Console Command. When using the SchemaTool class directly, create your schema using the `createSchema()` method. First create an instance of the `SchemaTool` and pass it an instance of the `EntityManager` that you want to use to create the schema. This method receives an array of `ClassMetadataInfo` instances. @@ -99,30 +139,31 @@ passed array of `ClassMetdataInfo` instances. $tool->updateSchema($classes); If you want to use this functionality from the command line you can use the -`schema-tool` task. +`schema-tool` command. -To create the schema use the `--create` option: +To create the schema use the `create` command: - $ php doctrine orm:schema-tool --create + $ php doctrine orm:schema-tool:create /path/to/mapping-path -To drop the scheme use the `--drop` option: +To drop the schema use the `drop` command: - $ php doctrine orm:schema-tool --drop + $ php doctrine orm:schema-tool:drop /path/to/mapping-path If you want to drop and then recreate the schema then use both options: - $ php doctrine orm:schema-tool --drop --create + $ php doctrine orm:schema-tool:drop /path/to/mapping-path + $ php doctrine orm:schema-tool:create /path/to/mapping-path -As you would think, if you want to update your schema use the `--update` option: +As you would think, if you want to update your schema use the `update` command: - $ php doctrine orm:schema-tool --update + $ php doctrine orm:schema-tool:update /path/to/mapping-path -All of the above tasks also accept a `--dump-sql` option that will output the SQL +All of the above commands also accept a `--dump-sql` option that will output the SQL for the ran operation. - $ php doctrine orm:schema-tool --create --dump-sql + $ php doctrine orm:schema-tool:create --dump-sql /path/to/mapping-path -Before using the orm:schema-tool task, remember to configure your cli-config.php properly. +Before using the orm:schema-tool commands, remember to configure your cli-config.php properly. ++ Convert Mapping Information @@ -162,7 +203,11 @@ Now to convert the added mapping sources you can do so by using the exporter dri This functionality functionality is also available from the command line to for example convert some YAML mapping files to XML. - $ php doctrine orm:convert-mapping --from=/path/to/yml --to=xml --dest=/path/to/xml + $ php doctrine orm:convert-mapping /path/to/mapping-path xml /path/to/mapping-path-converted-to-xml + +It is even possible to define more than one path as source: + + $ php doctrine orm:convert-mapping --from /path/to/mapping-path1 --from /path/to/mapping-path2 /path/to/mapping-path3 xml /path/to/mapping-path-converted-to-xml ++ Reverse Engineering @@ -182,7 +227,7 @@ generate YAML, XML, etc. from your existing databases. From the command line it is very simple to do something like reverse engineer your existing database to set of YAML mapping files. - $ php doctrine orm:convert-mapping --from=database --to=yml --dest=/path/to/yml + $ php doctrine orm:convert-mapping database yml /path/to/mapping-path-converted-to-yml > **CAUTION** > Reverse Engineering is not always working perfectly depending on special cases. From 8aed5c72dfc5b6c1df3cb00009b137602e71e517 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 10 Apr 2010 11:50:40 +0200 Subject: [PATCH 007/430] Updated ORM Tools Documentation --- manual/en/tools.txt | 54 ++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/manual/en/tools.txt b/manual/en/tools.txt index e18d5f8e7..bec25dddd 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -18,24 +18,10 @@ Type `doctrine` on the command line and you should see an overview of the availa +++ Configuration Whenever the `doctrine` command line tool is invoked, it is only able to access Commands that were defined by developer. Dependency Injection (DI) is the responsable to inject support into this utility, but it is up to the developer define it. +The Doctrine CLI tool from the bin/ folder already defines all the DBAL and ORM commands shipped with Doctrine. -To include a new command on Doctrine Console, you need to do: - - [php] - $cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand()); - -Additionally, include multiple commands (and overriding previously defined ones) is possible through the command: - - [php] - $cli->addCommands(array( - new \MyProject\Tools\Console\Commands\MyCustomCommand(), - new \MyProject\Tools\Console\Commands\SomethingCommand(), - new \MyProject\Tools\Console\Commands\AnotherCommand(), - new \MyProject\Tools\Console\Commands\OneMoreCommand(), - )); - -Many commands of the Doctrine Console requires either the `db` or the `em` helpers to be defined in order to work correctly. Doctrine Console requires the definition of a HelperSet that is the DI tool to be injected in the Console. -In case of a project that is dealing exclusvely with DBAL, the ConnectionHelper is required: +All the commands of the Doctrine Console require either the `db` or the `em` helpers to be defined in order to work correctly. Doctrine Console requires the definition of a HelperSet that is the DI tool to be injected in the Console. +In case of a project that is dealing exclusively with DBAL, the ConnectionHelper is required: [php] $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( @@ -51,7 +37,8 @@ When dealing with the ORM package, the EntityManagerHelper is required: )); $cli->setHelperSet($helperSet); -The HelperSet instance can be in a separate file (ie. `cli-config.php`) that contains typical Doctrine bootstrap code and predefines the needed HelperSet attributes mentioned above. A typical `cli-config.php` file looks as follows: +The HelperSet instance has to be generated in a separate file (ie. `cli-config.php`) that contains typical Doctrine +bootstrap code and predefines the needed HelperSet attributes mentioned above. A typical `cli-config.php` file looks as follows: [php] require_once __DIR__ . '/../../lib/Doctrine/Common/ClassLoader.php'; @@ -79,7 +66,24 @@ The HelperSet instance can be in a separate file (ie. `cli-config.php`) that con 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); -It is important to define a correct HelperSet that doctrine.php script will ultimately use. For the required name of this variable, check the doctrine.php included in your package; the most new ones will automatically find the variable no matter what its name is, as long as it is an instance of the right class. +It is important to define a correct HelperSet that doctrine.php script will ultimately use. The Doctrine Binary +will automatically find the first instance of HelperSet in the global variable namespace and use this. + +You can also add your own commands on-top of the Doctrine supported tools. +To include a new command on Doctrine Console, you need to do: + + [php] + $cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand()); + +Additionally, include multiple commands (and overriding previously defined ones) is possible through the command: + + [php] + $cli->addCommands(array( + new \MyProject\Tools\Console\Commands\MyCustomCommand(), + new \MyProject\Tools\Console\Commands\SomethingCommand(), + new \MyProject\Tools\Console\Commands\AnotherCommand(), + new \MyProject\Tools\Console\Commands\OneMoreCommand(), + )); +++ Command Overview @@ -143,25 +147,25 @@ If you want to use this functionality from the command line you can use the To create the schema use the `create` command: - $ php doctrine orm:schema-tool:create /path/to/mapping-path + $ php doctrine orm:schema-tool:create To drop the schema use the `drop` command: - $ php doctrine orm:schema-tool:drop /path/to/mapping-path + $ php doctrine orm:schema-tool:drop If you want to drop and then recreate the schema then use both options: - $ php doctrine orm:schema-tool:drop /path/to/mapping-path - $ php doctrine orm:schema-tool:create /path/to/mapping-path + $ php doctrine orm:schema-tool:drop + $ php doctrine orm:schema-tool:create As you would think, if you want to update your schema use the `update` command: - $ php doctrine orm:schema-tool:update /path/to/mapping-path + $ php doctrine orm:schema-tool:update All of the above commands also accept a `--dump-sql` option that will output the SQL for the ran operation. - $ php doctrine orm:schema-tool:create --dump-sql /path/to/mapping-path + $ php doctrine orm:schema-tool:create --dump-sql Before using the orm:schema-tool commands, remember to configure your cli-config.php properly. From 997ef97521c2242916bc0dfe93f0abd7404b120e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 10 Apr 2010 12:04:23 +0200 Subject: [PATCH 008/430] Updated SchemaTool usage docs with mapping paths information example --- manual/en/tools.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/manual/en/tools.txt b/manual/en/tools.txt index bec25dddd..31109b7f6 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -169,6 +169,13 @@ for the ran operation. Before using the orm:schema-tool commands, remember to configure your cli-config.php properly. +> **NOTE** +> +> When using the Annotation Mapping Driver you have to either setup your autoloader in the cli-config.php +> correctly to find all the entities, or you can use the second argument of the `EntityManagerHelper` to +> specifiy all the paths of your entities (or mapping files), i.e. +> `new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);` + ++ Convert Mapping Information Doctrine comes with some special tools for working with the various supported From 2a1f5e121bc0baaccacb0740efc682edcf2ab6da Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 11 Apr 2010 14:38:19 +0200 Subject: [PATCH 009/430] DDC-442 - Fix example for timestamp optimistic lock --- manual/en/transactions-and-concurrency.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/transactions-and-concurrency.txt b/manual/en/transactions-and-concurrency.txt index 302efd37e..0729872e3 100644 --- a/manual/en/transactions-and-concurrency.txt +++ b/manual/en/transactions-and-concurrency.txt @@ -118,7 +118,7 @@ You could also just as easily use a datetime column and instead of incrementing class User { // ... - /** @Version @Column(type="integer") */ + /** @Version @Column(type="datetime") */ private $version; // ... } \ No newline at end of file From 8426a3da11bd4ee538c28eb7f9d814934fc58e24 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 12 Apr 2010 21:58:21 +0200 Subject: [PATCH 010/430] DDC-510 - Updated docs to reflect that Annotations Metadata Driver is not a default anymore, but required to specifiy --- manual/en/configuration.txt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index 3e62ba177..f3f8a4fbe 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -52,6 +52,8 @@ with the following minimalist configuration: $config = new Configuration; $cache = new \Doctrine\Common\Cache\ApcCache; $config->setMetadataCacheImpl($cache); + $driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); + $config->setMetadataDriverImpl($driverImpl); $config->setQueryCacheImpl($cache); $config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); $config->setProxyNamespace('MyProject\Proxies'); @@ -112,8 +114,14 @@ There are currently 4 available implementations: Throughout the most part of this manual the AnnotationDriver is used in the examples. For information on the usage of the XmlDriver or YamlDriver please refer to the dedicated chapters `XML Mapping` and `YAML Mapping`. -The annotation driver is configured as the default metadata driver, using Annotations as Metadata source -does not require you to set this option explicitly. +The annotation driver can be configured with a factory method on the `Doctrine\ORM\Configuration`: + + [php] + $driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); + $config->setMetadataDriverImpl($driverImpl); + +The path information to the entities is requied for the annotation driver, because otherwise +mass-operations on all entities through the console could not work correctly. +++ Metadata Cache (***RECOMMENDED***) @@ -151,8 +159,8 @@ The recommended implementations are: +++ SQL Logger (***Optional***) [php] - $config->setSqlLogger($logger); - $config->getSqlLogger(); + $config->setSQLLogger($logger); + $config->getSQLLogger(); Gets or sets the logger to use for logging all SQL statements executed by Doctrine. The logger class must implement the `Doctrine\DBAL\Logging\SqlLogger` interface. A simple default implementation that logs to the standard output using `echo` and `var_dump` can be found at `Doctrine\DBAL\Logging\EchoSqlLogger`. From d837a2110bc34530f3322e813ebd2f9ffbd83234 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 18 Apr 2010 19:31:41 +0200 Subject: [PATCH 011/430] Updated XML Getting Started Tutorial to comply with current Console API --- cookbook/en/getting-started-xml-edition.txt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cookbook/en/getting-started-xml-edition.txt b/cookbook/en/getting-started-xml-edition.txt index b9a9632d1..74c78bc4e 100644 --- a/cookbook/en/getting-started-xml-edition.txt +++ b/cookbook/en/getting-started-xml-edition.txt @@ -458,14 +458,19 @@ You can then use your favorite console tool to call: [console] doctrine@my-desktop> cd myproject/ - doctrine@my-desktop> doctrine orm:schema-tool --create + doctrine@my-desktop> doctrine orm:schema-tool:create During the development you probably need to re-create the database several times when changing the Entity -metadata. You can then either re-create the database, or use the update functionality: +metadata. You can then either re-create the database: [console] - doctrine@my-desktop> doctrine orm:schema-tool --re-create - doctrine@my-desktop> doctrine orm:schema-tool --update + doctrine@my-desktop> doctrine orm:schema-tool:drop + doctrine@my-dekstop> doctrine orm:schema-tool:create + +Or use the update functionality: + + [console] + doctrine@my-desktop> doctrine orm:schema-tool:update The updating of databases uses a Diff Algorithm for a given Database Schema, a cornerstone of the `Doctrine\DBAL` package, which can even be used without the Doctrine ORM package. However its not available in SQLite since From fc290404e1ee97993c40bcf9e876af9780011497 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 23 Apr 2010 12:16:31 -0400 Subject: [PATCH 012/430] Changing note to caution --- manual/en/events.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/events.txt b/manual/en/events.txt index f8f848563..254a43427 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -105,7 +105,7 @@ The EntityManager and UnitOfWork trigger a bunch of events during the life-time * loadClassMetadata - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (annotations/xml/yaml). * onFlush - The onFlush event occours after the change-sets of all managed entities are computed. This event is not a lifecycle callback. -> **NOTE** +> **CAUTION** > Note that the postLoad event occurs for an entity before any associations have been > initialized. Therefore it is not safe to access associations in a postLoad callback > or event handler. From 605ee881c59f0d75bdb5890d723ec63cda06dc0b Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Mon, 26 Apr 2010 11:55:43 -0400 Subject: [PATCH 013/430] Misc. fixes and initial entry of PHP Mapping and Metadata Drivers chapters. --- manual/en.txt | 2 + manual/en/association-mapping.txt | 23 +++- manual/en/configuration.txt | 26 ++-- manual/en/inheritance-mapping.txt | 2 +- manual/en/introduction.txt | 29 +++-- manual/en/metadata-drivers.txt | 155 ++++++++++++++++++++++ manual/en/php-mapping.txt | 199 +++++++++++++++++++++++++++++ manual/en/working-with-objects.txt | 16 +-- 8 files changed, 412 insertions(+), 40 deletions(-) create mode 100644 manual/en/metadata-drivers.txt create mode 100644 manual/en/php-mapping.txt diff --git a/manual/en.txt b/manual/en.txt index 7cc3be93f..56e3ff339 100644 --- a/manual/en.txt +++ b/manual/en.txt @@ -14,8 +14,10 @@ + XML Mapping + YAML Mapping + Annotations Reference ++ PHP Mapping + Caching + Improving Performance + Tools ++ Metadata Drivers + DBAL + Best Practices \ No newline at end of file diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index 5190f6a14..02c59675d 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -412,18 +412,27 @@ to all the explicitly given ORDER BY items. * To keep the database impact low, these implicit ORDER BY items are only added to an DQL Query if the collection is fetch joined in the DQL query. -Given our previously defined Example: +Given our previously defined example, the following would not add ORDER BY, since g is not fetch joined: [sql] - -- Would not add ORDER BY, since g is not fetch joined SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10 - -- However +However the following: + + [sql] SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 - -- would internally be rewritten to + +...would internally be rewritten to: + + [sql] SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC - -- You can't reverse the order, an explicit: +You can't reverse the order, an explicit: + + [sql] SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC - -- is internally be rewritten to - SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC + +...is internally rewritten to: + + [sql] + SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC \ No newline at end of file diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index f3f8a4fbe..6ddb421aa 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -158,9 +158,9 @@ The recommended implementations are: +++ SQL Logger (***Optional***) - [php] - $config->setSQLLogger($logger); - $config->getSQLLogger(); + [php] + $config->setSQLLogger($logger); + $config->getSQLLogger(); Gets or sets the logger to use for logging all SQL statements executed by Doctrine. The logger class must implement the `Doctrine\DBAL\Logging\SqlLogger` interface. A simple default implementation that logs to the standard output using `echo` and `var_dump` can be found at `Doctrine\DBAL\Logging\EchoSqlLogger`. @@ -191,14 +191,14 @@ 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. +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 @@ -218,6 +218,7 @@ on the way so you can pass them to EntityManager#persist(). This policy can be configured as follows: + [php] /** * @Entity * @ChangeTrackingPolicy("DEFERRED_EXPLICIT") @@ -268,7 +269,6 @@ Then, in each property setter of this class or derived classes, you need to invoke `_onPropertyChanged` as follows to notify listeners: [php] - *NOTICE* +> **NOTE** > > A mapped superclass cannot be an entity, it is not queryable and persistent relationships defined by a mapped > superclass must be unidirectional. For further support of inheritance, the single or joined table inheritance diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index 1b4d7bf6c..76206e811 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -10,7 +10,9 @@ unnecessary code duplication. ++ Disclaimer -This is the Doctrine 2 reference documentation. Introductory guides and tutorials that you can follow along from start to finish, like the "Guide to Doctrine" book known from the Doctrine 1.x series, will be available at a later date. +This is the Doctrine 2 reference documentation. Introductory guides and tutorials +that you can follow along from start to finish, like the "Guide to Doctrine" book +known from the Doctrine 1.x series, will be available at a later date. ++ Requirements @@ -64,19 +66,19 @@ line installation utility. To install just the `Common` package you can run the following command: - $ sudo pear install pear.phpdoctrine.org/DoctrineCommon-2.0.0 + $ sudo pear install pear.doctrine-project.org/DoctrineCommon-2.0.0 If you want to use the Doctrine Database Abstraction Layer you can install it with the following command. - $ sudo pear install pear.phpdoctrine.org/DoctrineDBAL-2.0.0 + $ sudo pear install pear.doctrine-project.org/DoctrineDBAL-2.0.0 Or, if you want to get the works and go for the ORM you can install it with the following command. - $ sudo pear install pear.phpdoctrine.org/DoctrineORM-2.0.0 + $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.0 -When you have a package installed via PEAR you can required and load the +When you have a package installed via PEAR you can require and load the `ClassLoader` with the following code. [php] @@ -128,12 +130,19 @@ see what you can do with it. You can also use Doctrine 2 by downloading the latest release package from [the download page](http://www.doctrine-project.org/download). ++++ GitHub + +Alternatively you can clone the latest version of Doctrine 2 via GitHub.com: + + $ git clone git://github.com/doctrine/doctrine2.git doctrine + +++ Subversion -Alternatively you can check out the latest version of Doctrine 2 via SVN. +If you prefer subversion you can also checkout the code from GitHub.com through +the subversion protocol: + + $ svn co http://svn.github.com/doctrine/doctrine2.git doctrine2 - $ svn co http://svn.doctrine-project.org/trunk doctrine - ++ Sandbox Quickstart > **NOTE** @@ -183,7 +192,7 @@ see the same output. 2) Take another look into the tools/sandbox folder. A SQLite database should have been created with the name `database.sqlite`. -3) Open `index.php` and edit it so that it looks as follows: +3) Open `index.php` and at the bottom edit it so it looks like the following: [php] //... bootstrap stuff @@ -241,4 +250,4 @@ but we wanted to introduce you to DQL at this point. Can you **find** the easier > schema with the command `doctrine orm:schema-tool --drop` followed by > `doctrine orm:schema-tool --create`. -7) Explore Doctrine 2! +7) Explore Doctrine 2! \ No newline at end of file diff --git a/manual/en/metadata-drivers.txt b/manual/en/metadata-drivers.txt new file mode 100644 index 000000000..b07310346 --- /dev/null +++ b/manual/en/metadata-drivers.txt @@ -0,0 +1,155 @@ +The heart of an object relational mapper is the mapping information that glues +everything together. It instructs the EntityManager how it should behave when +dealing with the different entities. + +++ Core Metadata Drivers + +Doctrine provides a few different ways for you to specify your metadata: + + * **XML files** (XmlDriver) + * **Class DocBlock Annotations** (AnnotationDriver) + * **YAML files** (YamlDriver) + * **PHP Code in files or static functions** (PhpDriver) + +Something important to note about the above drivers is they are all an intermediate +step to the same end result. The mapping information is populated to +`Doctrine\ORM\Mapping\ClassMetadata` instances. So in the end, Doctrine +only ever has to work with the api of the `ClassMetadata` class to get mapping +information for an entity. + +> **TIP** +> The populated `ClassMetadata` instances are also cached so in a production +> environment the parsing and populating only ever happens once. You can configure +> the metadata cache implementation using the `setMetadataCacheImpl()` method on +> the `Doctrine\ORM\Configuration` class: +> +> [php] +> $em->getConfiguration()->setMetadataCacheImpl(new ApcCache()); + +If you want to use one of the included core metadata drivers you just need to +configure it. All the drivers are in the `Doctrine\ORM\Mapping\Driver` namespace: + + [php] + $driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver('/path/to/mapping/files'); + $em->getConfiguration()->setMetadataDriverImpl($driver); + +++ Implementing Metadata Drivers + +In addition to the included metadata drivers you can very easily implement +your own. All you need to do is define a class which implements the `Driver` +interface: + + [php] + namespace Doctrine\ORM\Mapping\Driver; + + use Doctrine\ORM\Mapping\ClassMetadataInfo; + + interface Driver + { + /** + * Loads the metadata for the specified class into the provided container. + * + * @param string $className + * @param ClassMetadataInfo $metadata + */ + function loadMetadataForClass($className, ClassMetadataInfo $metadata); + + /** + * Gets the names of all mapped classes known to this driver. + * + * @return array The names of all mapped classes known to this driver. + */ + function getAllClassNames(); + + /** + * Whether the class with the specified name should have its metadata loaded. + * This is only the case if it is either mapped as an Entity or a + * MappedSuperclass. + * + * @param string $className + * @return boolean + */ + function isTransient($className); + } + +If you want to write a metadata driver to parse information from some file format +we've made your life a little easier by providing the `AbstractFileDriver` +implementation for you to extend from: + + [php] + class MyMetadataDriver extends AbstractFileDriver + { + /** + * {@inheritdoc} + */ + protected $_fileExtension = '.dcm.ext'; + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadataInfo $metadata) + { + $data = $this->_loadMappingFile($file); + + // populate ClassMetadataInfo instance from $data + } + + /** + * {@inheritdoc} + */ + protected function _loadMappingFile($file) + { + // parse contents of $file and return php data structure + } + } + +> **NOTE** +> When using the `AbstractFileDriver` it requires that you only have one entity +> defined per file and the file named after the class described inside where +> namespace separators are replaced by periods. So if you have an entity named +> `Entities\User` and you wanted to write a mapping file for your driver above +> you would need to name the file `Entities.User.dcm.ext` for it to be recognized. + +Now you can use your `MyMetadataDriver` implementation by setting it with the +`setMetadataDriverImpl()` method: + + [php] + $driver = new MyMetadataDriver('/path/to/mapping/files'); + $em->getConfiguration()->setMetadataDriverImpl($driver); + +++ ClassMetadata + +The last piece you need to know and understand about metadata in Doctrine 2 is +the API of the `ClassMetadata` classes. You need to be familiar with them in order +to implement your own drivers but more importantly to retrieve mapping information +for a certain entity when needed. + +You have all the methods you need to manually specify the mapping information +instead of using some mapping file to populate it from. The base `ClassMetadataInfo` +class is responsible for only data storage and is not meant for runtime use. It +does not require that the class actually exists yet so it is useful for describing some +entity before it exists and using that information to generate for example the +entities themselves. The class `ClassMetadata` extends `ClassMetadataInfo` and +adds some functionality required for runtime usage and requires that the PHP +class is present and can be autoloaded. + +You can read more about the API of the `ClassMetadata` classes in the PHP Mapping +chapter. + +++ Getting ClassMetadata Instances + +If you want to get the `ClassMetadata` instance for an entity in your project +to programatically use some mapping information to generate some HTML or something +similar you can retrieve it through the `ClassMetadataFactory`: + + [php] + $cmf = $em->getMetadataFactory(); + $class = $cmf->getMetadataFor('MyEntityName'); + +Now you can learn about the entity and use the data stored in the `ClassMetadata` +instance to get all mapped fields for example and iterate over them: + + [php] + foreach ($class->fieldMappings as $fieldMapping) { + echo $fieldMapping['fieldName'] . "\n"; + } \ No newline at end of file diff --git a/manual/en/php-mapping.txt b/manual/en/php-mapping.txt new file mode 100644 index 000000000..19a4f66a5 --- /dev/null +++ b/manual/en/php-mapping.txt @@ -0,0 +1,199 @@ +Doctrine 2 also allows you to provide the ORM metadata in the form of plain +PHP code using the `ClassMetadata` API. You can write the code in PHP files or +inside of a static function named `loadMetadata($class)` on the entity class itself. + +++ PHP Files + +If you wish to write your mapping information inside PHP files that are named +after the entity and included to populate the metadata for an entity you can do +so by using the `PHPDriver`: + + [php] + $driver = new PHPDriver('/path/to/php/mapping/files'); + $em->getConfiguration()->setMetadataDriverImpl($driver); + +Now imagine we had an entity named `Entities\User` and we wanted to write a mapping +file for it using the above configured `PHPDriver` instance: + + [php] + namespace Entities; + + class User + { + private $id; + private $username; + } + +To write the mapping information you just need to create a file named +`Entities.User.php` inside of the `/path/to/php/mapping/files` folder: + + [php] + // /path/to/php/mapping/files/Entities.User.php + + $metadata->mapField(array( + 'id' => true, + 'fieldName' => 'id', + 'type' => 'integer' + )); + + $metadata->mapField(array( + 'fieldName' => 'username', + 'type' => 'string' + )); + +Now we can easily retrieve the populated `ClassMetadata` instance where the `PHPDriver` +includes the file and the `ClassMetadataFactory` caches it for later retrieval: + + [php] + $class = $em->getMetadataFor('Entities\User'); + +++ Static Function + +In addition to the PHP files you can also specify your mapping information inside +of a static function defined on the entity class itself. This is useful for cases +where you want to keep your entity and mapping information together but don't want +to use annotations. For this you just need to use the `StaticPHPDriver`: + + [php] + $driver = new StaticPHPDriver('/path/to/entities'); + $em->getConfiguration()->setMetadataDriverImpl($driver); + +Now you just need to define a static function named `loadMetadata($metadata)` on your entity: + + [php] + namespace Entities; + + use Doctrine\ORM\Mapping\ClassMetadata; + + class User + { + // ... + + public static function loadMetadata(ClassMetadata $metadata) + { + $metadata->mapField(array( + 'id' => true, + 'fieldName' => 'id', + 'type' => 'integer' + )); + + $metadata->mapField(array( + 'fieldName' => 'username', + 'type' => 'string' + )); + } + } + +++ ClassMetadataInfo API + +The `ClassMetadataInfo` class is the base data object for storing the mapping +metadata for a single entity. It contains all the getters and setters you need +populate and retrieve information for an entity. + ++++ General Setters + + * `setTableName($tableName)` + * `setPrimaryTable(array $primaryTableDefinition)` + * `setCustomRepositoryClass($repositoryClassName)` + * `setIdGeneratorType($generatorType)` + * `setIdGenerator($generator)` + * `setSequenceGeneratorDefinition(array $definition)` + * `setChangeTrackingPolicy($policy)` + * `setIdentifier(array $identifier)` + ++++ Inheritance Setters + + * `setInheritanceType($type)` + * `setSubclasses(array $subclasses)` + * `setParentClasses(array $classNames)` + * `setDiscriminatorColumn($columnDef)` + * `setDiscriminatorMap(array $map)` + ++++ Field Mapping Setters + + * `mapField(array $mapping)` + * `mapOneToOne(array $mapping)` + * `mapOneToMany(array $mapping)` + * `mapManyToOne(array $mapping)` + * `mapManyToMany(array $mapping)` + ++++ Lifecycle Callback Setters + + * `addLifecycleCallback($callback, $event)` + * `setLifecycleCallbacks(array $callbacks)` + ++++ Versioning Setters + + * `setVersionMapping(array &$mapping)` + * `setVersioned($bool)` + * `setVersionField()` + ++++ General Getters + + * `getTableName()` + * `getTemporaryIdTableName()` + ++++ Identifier Getters + + * `getIdentifierColumnNames()` + * `usesIdGenerator()` + * `isIdentifier($fieldName)` + * `isIdGeneratorIdentity()` + * `isIdGeneratorSequence()` + * `isIdGeneratorTable()` + * `isIdentifierNatural()` + * `getIdentifierFieldNames()` + * `getSingleIdentifierFieldName()` + * `getSingleIdentifierColumnName()` + ++++ Inheritance Getters + + * `isInheritanceTypeNone()` + * `isInheritanceTypeJoined()` + * `isInheritanceTypeSingleTable()` + * `isInheritanceTypeTablePerClass()` + * `isInheritedField($fieldName)` + * `isInheritedAssociation($fieldName)` + ++++ Change Tracking Getters + + * `isChangeTrackingDeferredExplicit()` + * `isChangeTrackingDeferredImplicit()` + * `isChangeTrackingNotify()` + ++++ Field & Association Getters + + * `isUniqueField($fieldName)` + * `isNullable($fieldName)` + * `getColumnName($fieldName)` + * `getFieldMapping($fieldName)` + * `getAssociationMapping($fieldName)` + * `getAssociationMappings()` + * `getFieldName($columnName)` + * `hasField($fieldName)` + * `getColumnNames(array $fieldNames = null)` + * `getTypeOfField($fieldName)` + * `getTypeOfColumn($columnName)` + * `hasAssociation($fieldName)` + * `isSingleValuedAssociation($fieldName)` + * `isCollectionValuedAssociation($fieldName)` + ++++ Lifecycle Callback Getters + + * `hasLifecycleCallbacks($lifecycleEvent)` + * `getLifecycleCallbacks($event)` + +++ ClassMetadata API + +The `ClassMetadata` class extends `ClassMetadataInfo` and adds the runtime functionality +required by Doctrine. It adds a few extra methods related to runtime reflection +for working with the entities themselves. + + * `getReflectionClass()` + * `getReflectionProperties()` + * `getReflectionProperty($name)` + * `getSingleIdReflectionProperty()` + * `getIdentifierValues($entity)` + * `setIdentifierValues($entity, $id)` + * `setFieldValue($entity, $field, $value)` + * `getFieldValue($entity, $field)` \ No newline at end of file diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index d6c76ad07..43fdce820 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -68,13 +68,6 @@ synchronized with the database when `EntityManager#flush()` is invoked. > with the database in the most efficient way and a single, short transaction, > taking care of maintaining referential integrity. -- - -> **CAUTION** -> Generated entity identifiers / primary keys are guaranteed to be available after the -> next invocation of `EntityManager#flush()` that involves the entity in question. -> YOU CAN NOT RELY ON A GENERATED IDENTIFIER TO BE AVAILABLE AFTER INVOKING `persist`! - Example: [php] @@ -82,7 +75,11 @@ Example: $user->setName('Mr.Right'); $em->persist($user); $em->flush(); - // If $user had a generated identifier, it would now be available. + +> **CAUTION** +> Generated entity identifiers / primary keys are guaranteed to be available after the +> next invocation of `EntityManager#flush()` that involves the entity in question. +> YOU CAN NOT RELY ON A GENERATED IDENTIFIER TO BE AVAILABLE AFTER INVOKING `persist`! The semantics of the persist operation, applied on an entity X, are as follows: @@ -161,7 +158,7 @@ Example: // $entity now refers to the fully managed copy returned by the merge operation. // The EntityManager $em now manages the persistence of $entity as usual. -> **WARNING** +> **CAUTION** > When you want to serialize/unserialize entities you have to make all entity properties > protected, never private. The reason for this is, if you serialize a class that was a proxy > instance before the private variables won't be serialized and a PHP Notice is thrown. @@ -417,6 +414,7 @@ one recommended way of grouping these queries in a central location. [php] namespace MyDomain\Model; + use Doctrine\ORM\EntityRepository; /** From 6d34b91425b5bc4384a2bb59bf0ff314d78c7876 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 26 Apr 2010 21:54:10 +0200 Subject: [PATCH 014/430] DDC-539 - Fix typo --- cookbook/en/getting-started-xml-edition.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbook/en/getting-started-xml-edition.txt b/cookbook/en/getting-started-xml-edition.txt index 74c78bc4e..b5834ed5a 100644 --- a/cookbook/en/getting-started-xml-edition.txt +++ b/cookbook/en/getting-started-xml-edition.txt @@ -2,7 +2,7 @@ Doctrine 2 is a project that aims to handle the persistence of the domain model The Data Mapper pattern is at the heart of this project, aiming for a complete separation of the domain/business logic from the persistence in a relational database management system. The benefit of Doctrine for the programmer is the possibility can focus soley on the business and worry about persistence only as a secondary task. This doesn't mean -persistence is not important to Doctrine 2, however it is our believe that there are considerable benefits for object-oriented +persistence is not important to Doctrine 2, however it is our belief that there are considerable benefits for object-oriented programming, if persistence and entities are kept perfectly seperated. ## What are Entities? From 74ecfca43ee714578c1a2d4ac03edd6b66b4e05f Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 29 Apr 2010 23:44:25 +0200 Subject: [PATCH 015/430] Fixed section about partial objects. --- manual/en/configuration.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index 6ddb421aa..01c3f422c 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -343,17 +343,15 @@ 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 `Query#HINT_FORCE_PARTIAL_LOAD` query hint as follows: +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 u.id, u.name from MyApp\Domain\User u"); - $q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); + $q = $em->createQuery("select partial u.{id,name} from MyApp\Domain\User u"); +++ When should I force partial objects? -Mainly for optimization purposes, especially since the stateless nature of PHP -applications means that any fields or objects that are loaded unnecessarily in -a request are useless (though often minimal) overhead. Be careful of premature optimization. Only force partial objects if it proves to provide an improvement to a performance problem. +Mainly for optimization purposes, but be careful of premature optimization as partial objects +lead to potentially more fragile code. ++ Proxy Objects From e01510953eaa771391ad26effb3f5606fe15ad04 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 30 Apr 2010 16:11:35 -0400 Subject: [PATCH 016/430] [DDC-443] Adding many-to-one unidirectional example --- manual/en/association-mapping.txt | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index 02c59675d..d89756220 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -224,6 +224,33 @@ The following example sets up such a unidirectional one-to-many association: > **NOTE** > One-To-Many uni-directional relations with join-table only work using the @ManyToMany annotation and a unique-constraint. +++ Many-To-One, Unidirectional + +You can easily implement a many-to-one unidirectional association with the following: + + [php] + /** @Entity */ + class User + { + // ... + + /** + * @ManyToOne(targetEntity="Address") + * @JoinColumn(name="address_id", referencedColumnName="id") + */ + private $address + } + + /** @Entity */ + class Address + { + // ... + } + +> **TIP** +> The above `@JoinColumn` is optional as it would default to `address_id` and `id` +> anyways. You can omit it and let it use the defaults. + ++ One-To-Many, Bidirectional Bidirectional one-to-many associations are very common. The following code shows an example with a Product and a Feature class: From c0dc0112d3a38711d3928cb59c881d5b14109a4c Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 1 May 2010 03:36:40 +0200 Subject: [PATCH 017/430] Fix DDC-557 --- manual/en/basic-mapping.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 17910de81..d4ec04fe1 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -130,6 +130,8 @@ the methods as you wish. Here is an example skeleton of such a custom type class */ class MyType extends Type { + const MYTYPE = 'mytype'; // modify to match your type name + public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { // return the SQL used to create your column type. To create a portable column type, use the $platform. @@ -144,6 +146,11 @@ the methods as you wish. Here is an example skeleton of such a custom type class { // This is executed when the value is written to the database. Make your conversions here, optionally using the $platform. } + + public function getName() + { + return self::MYTYPE; // modify to match your constant name + } } Restrictions to keep in mind: From b4bc4b029d280b0f7b38b244c88d6b663d1377b3 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 1 May 2010 03:38:03 +0200 Subject: [PATCH 018/430] Fix DDC-560 --- manual/en/dbal.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/en/dbal.txt b/manual/en/dbal.txt index 452f45646..d2fb8ebf3 100644 --- a/manual/en/dbal.txt +++ b/manual/en/dbal.txt @@ -72,8 +72,8 @@ Doctrine is already shipped with two implementations for the "PostConnect" event You can register events by subscribing them to the `EventManager` instance passed to the Connection factory: [php] - $evm = new EventManager(), - $evm->addEventSubscriber(new MysqlSessionInit('UTF-8')); + $evm = new EventManager(); + $evm->addEventSubscriber(new MysqlSessionInit('UTF8')); $conn = DriverManager::getConnection($connectionParams, null, $evm); From e155e86517f21df781adb4832b40576cf588a756 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Wed, 5 May 2010 10:58:11 +0200 Subject: [PATCH 019/430] Clarified docs on persisting new objects. --- manual/en/working-with-objects.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 43fdce820..abb48d40d 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -78,8 +78,10 @@ Example: > **CAUTION** > Generated entity identifiers / primary keys are guaranteed to be available after the -> next invocation of `EntityManager#flush()` that involves the entity in question. -> YOU CAN NOT RELY ON A GENERATED IDENTIFIER TO BE AVAILABLE AFTER INVOKING `persist`! +> next successful flush operation that involves the entity in question. +> You can not reply on a generated identifier to be available directly after invoking `persist`. +> The inverse is also true. You can not rely on a generated identifier being not available +> after a failed flush operation. The semantics of the persist operation, applied on an entity X, are as follows: From 7ffdd80bb2ec8ce37f264450059675e1849cfa32 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Wed, 5 May 2010 11:10:00 +0200 Subject: [PATCH 020/430] Clarified docs on working with objects. --- manual/en/working-with-objects.txt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index abb48d40d..35cf22b0d 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -82,13 +82,16 @@ Example: > You can not reply on a generated identifier to be available directly after invoking `persist`. > The inverse is also true. You can not rely on a generated identifier being not available > after a failed flush operation. - + The semantics of the persist operation, applied on an entity X, are as follows: -* If X is a new entity, it becomes managed. The entity X will be entered into the database at or before transaction commit or as a result of the flush operation. +* If X is a new entity, it becomes managed. The entity X will be entered into the database as a result of the flush operation. * If X is a preexisting managed entity, it is ignored by the persist operation. However, the persist operation is cascaded to entities referenced by X, if the relationships from X to these other entities are mapped with cascade=PERSIST or cascade=ALL (see "Transitive Persistence"). * If X is a removed entity, it becomes managed. -* If X is a detached entity, an InvalidArgumentException will be thrown. +* If X is a detached entity, the behavior is undefined. + +> **CAUTION** +> Do not pass detached entities to the persist operation. ++ Removing entities @@ -112,7 +115,7 @@ The semantics of the remove operation, applied to an entity X are as follows: * If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). * If X is a detached entity, an InvalidArgumentException will be thrown. * If X is a removed entity, it is ignored by the remove operation. -* A removed entity X will be removed from the database at or before transaction commit or as a result of the flush operation. +* A removed entity X will be removed from the database as a result of the flush operation. ++ Detaching entities @@ -163,7 +166,7 @@ Example: > **CAUTION** > When you want to serialize/unserialize entities you have to make all entity properties > protected, never private. The reason for this is, if you serialize a class that was a proxy -> instance before the private variables won't be serialized and a PHP Notice is thrown. +> instance before, the private variables won't be serialized and a PHP Notice is thrown. The semantics of the merge operation, applied to an entity X, are as follows: From 4121c1ca727bd344cd2619d218c3fd5be3907648 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 6 May 2010 12:45:46 +0200 Subject: [PATCH 021/430] Clarified docs on identifier generation strategies. --- manual/en/basic-mapping.txt | 36 +++++++++++++++++++----------- manual/en/working-with-objects.txt | 10 ++++++--- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 17910de81..6b0e07361 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -208,24 +208,28 @@ A common alternative strategy is to use a generated value as the identifier. To private $id; } -This tells Doctrine to automatically generate a value for the identifier. How this value is generated is specified by the `strategy` attribute, which is optional and defaults to 'AUTO'. A value of `AUTO` tells Doctrine to use the generation strategy that is preferred by the currently used database platform. For MySql, for example, Doctrine would use the `IDENTITY` strategy which means a typical AUTO_INCREMENT column. For PostgreSql it would choose to use the `SEQUENCE` strategy which would result in using a database sequence. +This tells Doctrine to automatically generate a value for the identifier. How this value is generated is specified by the `strategy` attribute, which is optional and defaults to 'AUTO'. A value of `AUTO` tells Doctrine to use the generation strategy that is preferred by the currently used database platform. See below for details. -+++ Id Generation Strategies ++++ Identifier Generation Strategies -The previous example showed how to use the default Id generation strategy without knowing the underlying database with the AUTO-detection strategy. -It is also possible to specifiy the Id Generation strategy more explicitly, which allows to make use of some additional features. +The previous example showed how to use the default identifier generation strategy without knowing the underlying database with the AUTO-detection strategy. +It is also possible to specifiy the identifier generation strategy more explicitly, which allows to make use of some additional features. Here is the list of possible generation strategies: -* `AUTO` (default): Tells Doctrine to pick the strategy that is preferred by the used database platform. This strategy provides full portability. +* `AUTO` (default): Tells Doctrine to pick the strategy that is preferred by the used database platform. + The preferred strategies are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle and PostgreSQL. + This strategy provides full portability. * `NONE`: Tells Doctrine that you generated the entities primary key value in userland before `EntityManager#persist()` is called. * `SEQUENCE`: Tells Doctrine to use a database sequence for ID generation. This strategy does currently not provide full portability. Sequences are supported by Oracle and PostgreSql. -* `IDENTITY`: Tells Doctrine to use special identity columns in the database that usually generate a value on insertion of a row (i.e. MySql AUTO_INCREMENT). This strategy does currently not provide full portability. IDENTITY is supported by MySql, Sqlite and MsSql. +* `IDENTITY`: Tells Doctrine to use special identity columns in the database that generate a value on insertion of a row. This strategy does currently not provide full portability and + is supported by the following platforms: MySQL/SQLite (AUTO_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL). * `TABLE`: Tells Doctrine to use a separate table for ID generation. This strategy provides full portability. ***This strategy is not yet implemented!*** + ++++ Sequence Generator -The Sequence Generator can be used in conjunction with Oracle or Postgres Platforms and allows some additional configuration options besides +The Sequence Generator can currently be used in conjunction with Oracle or Postgres and allows some additional configuration options besides specifiying the sequence's name: [php] @@ -240,17 +244,23 @@ specifiying the sequence's name: The initial value specifies at which value the sequence should start. -Allocation Size is a powerful feature to optimize INSERT performance of Doctrine. The allocation size specifies -by how much values the sequence is incremented whenever the next value is retrieved. If this is larger than one -Doctrine 2 can generate Id values for the allocationSizes amount of entities. In the above example with +The allocationSize is a powerful feature to optimize INSERT performance of Doctrine. The allocationSize specifies +by how much values the sequence is incremented whenever the next value is retrieved. If this is larger than 1 (one) +Doctrine can generate identifier values for the allocationSizes amount of entities. In the above example with `allocationSize=100` Doctrine 2 would only need to access the sequence once to generate the identifiers for 100 new entities. > **CAUTION** +> The allocationSize is detected by SchemaTool and transformed into an "INCREMENT BY " clause +> in the CREATE SEQUENCE statement. For a database schema created manually (and not SchemaTool) you have to +> make sure that the allocationSize configuration option is never larger than the actual sequences INCREMENT BY value, +> otherwise you may get duplicate keys. + +> **TIP** +> It is possible to use strategy="AUTO" and at the same time specifying a @SequenceGenerator. +> In such a case, your custom sequence settings are used in the case where the preferred +> strategy of the underlying platform is SEQUENCE, such as for Oracle and PostgreSQL. -> Allocation Size is detected by SchemaTool and transformed into an "INCREMENT BY " clause -> in the CREATE SEQUENCE statement. For a database schema created by you (and not SchemaTool) you have to -> make sure that the allocationSize configuration option is never larger than the actual sequences INCREMENT BY value. +++ Composite Keys diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 35cf22b0d..588082e11 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -35,9 +35,9 @@ may want to check it from time to time during development. > **CAUTION** > Do not invoke `flush` after every change to an entity or every single invocation of > persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the -> performance of your application. Instead form units of work that operate on your objects +> performance of your application. Instead, form units of work that operate on your objects > and call `flush` when you are done. While serving a single HTTP request there should -> be no need for invoking `flush` more than 0-2 times. +> be usually no need for invoking `flush` more than 0-2 times. +++ Direct access to a Unit of Work @@ -49,7 +49,7 @@ This will return the UnitOfWork instance the EntityManager is currently using. > **NOTE** > Directly manipulating a UnitOfWork is not recommended. When working directly with the -> UnitOfWork API respect methods marked as INTERNAL by not using them and carefully read +> UnitOfWork API, respect methods marked as INTERNAL by not using them and carefully read > the API documentation. ++ Persisting entities @@ -250,6 +250,10 @@ to do so, by key and by element. Here are some examples: Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does not allow null values and setAddress(null) will fail for removing the association. If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`. This can also provide better encapsulation as it hides the internal meaning of not having an address. +When working with collections, keep in mind that a Collection is essentially an ordered map (just like a PHP array). +That is why the `remove` operation accepts an index/key. `removeElement` is a separate method +that has O(n) complexity, where n is the size of the map. + Since Doctrine always only looks at the owning side of a bidirectional association, it is essentially not necessary that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. From c18e23b8176959b33d6fb2fbe8c84aa12c42160f Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 6 May 2010 13:02:21 +0200 Subject: [PATCH 022/430] Clarified docs on identifier generation strategies. --- manual/en/basic-mapping.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 6b0e07361..7c71c9976 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -250,6 +250,8 @@ Doctrine can generate identifier values for the allocationSizes amount of entiti `allocationSize=100` Doctrine 2 would only need to access the sequence once to generate the identifiers for 100 new entities. +*The default allocationSize for a @SequenceGenerator is currently 10.* + > **CAUTION** > The allocationSize is detected by SchemaTool and transformed into an "INCREMENT BY " clause > in the CREATE SEQUENCE statement. For a database schema created manually (and not SchemaTool) you have to From a3feeef88cb48706acd75bfe3a2aa49210cc086a Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 6 May 2010 13:07:54 +0200 Subject: [PATCH 023/430] Clarified docs on identifier generation strategies. --- manual/en/basic-mapping.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 7c71c9976..427473d81 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -220,7 +220,9 @@ Here is the list of possible generation strategies: * `AUTO` (default): Tells Doctrine to pick the strategy that is preferred by the used database platform. The preferred strategies are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle and PostgreSQL. This strategy provides full portability. -* `NONE`: Tells Doctrine that you generated the entities primary key value in userland before `EntityManager#persist()` is called. +* `NONE`: Tells Doctrine that the identifiers are assigned (and thus generated) by your code. + The assignment must take place before a new entity is passed to `EntityManager#persist`. + NONE is the same as leaving off the @GeneratedValue entirely. * `SEQUENCE`: Tells Doctrine to use a database sequence for ID generation. This strategy does currently not provide full portability. Sequences are supported by Oracle and PostgreSql. * `IDENTITY`: Tells Doctrine to use special identity columns in the database that generate a value on insertion of a row. This strategy does currently not provide full portability and is supported by the following platforms: MySQL/SQLite (AUTO_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL). From bc0f853b52e8314e7858215cd3cc9cdfa074c155 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 6 May 2010 13:08:36 +0200 Subject: [PATCH 024/430] Clarified docs on identifier generation strategies. --- manual/en/basic-mapping.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 427473d81..632d4ae3f 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -220,13 +220,13 @@ Here is the list of possible generation strategies: * `AUTO` (default): Tells Doctrine to pick the strategy that is preferred by the used database platform. The preferred strategies are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle and PostgreSQL. This strategy provides full portability. -* `NONE`: Tells Doctrine that the identifiers are assigned (and thus generated) by your code. - The assignment must take place before a new entity is passed to `EntityManager#persist`. - NONE is the same as leaving off the @GeneratedValue entirely. * `SEQUENCE`: Tells Doctrine to use a database sequence for ID generation. This strategy does currently not provide full portability. Sequences are supported by Oracle and PostgreSql. * `IDENTITY`: Tells Doctrine to use special identity columns in the database that generate a value on insertion of a row. This strategy does currently not provide full portability and is supported by the following platforms: MySQL/SQLite (AUTO_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL). * `TABLE`: Tells Doctrine to use a separate table for ID generation. This strategy provides full portability. ***This strategy is not yet implemented!*** +* `NONE`: Tells Doctrine that the identifiers are assigned (and thus generated) by your code. + The assignment must take place before a new entity is passed to `EntityManager#persist`. + NONE is the same as leaving off the @GeneratedValue entirely. ++++ Sequence Generator From f9b77f98545cf1bcd3a85ee08f36725529599045 Mon Sep 17 00:00:00 2001 From: Christian Heinrich Date: Mon, 10 May 2010 18:26:32 +0200 Subject: [PATCH 025/430] Added strategy cookbook preview --- .../en/strategy-cookbook-introduction.txt | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 cookbook/en/strategy-cookbook-introduction.txt diff --git a/cookbook/en/strategy-cookbook-introduction.txt b/cookbook/en/strategy-cookbook-introduction.txt new file mode 100644 index 000000000..987018e3c --- /dev/null +++ b/cookbook/en/strategy-cookbook-introduction.txt @@ -0,0 +1,208 @@ + +This recipe will give you a short introduction on how to design similar entities without using expensive (i.e. slow) inheritance but with not more than +* the well-known strategy pattern +* event listeners + +++ Scenario / Problem + +Given a Content-Management-System, we probably want to add / edit some so-called "blocks" and "panels". What are they for? + +* A block might be a registration form, some text content, a table with information. A good example might also be a small calendar. +* A panel is by definition a block that can itself contain blocks. A good example for a panel might be a sidebar box: You could easily add a small calendar into it. + +So, in this scenario, when building your CMS, you will surely add lots of blocks and panels to your pages and you will find yourself highly uncomfortable because of the following: + +* Every existing page needs to know about the panels it contains - therefore, you'll have an association to your panels. But if you've got several types of panels - what do you do? Add an association to +every paneltype? This wouldn't be flexible. You might be tempted to add an AbstractPanelEntity and an AbstractBlockEntity that use class inheritance. Your page could then only confer to the AbstractPanelType and Doctrine 2 would do the rest for you, i.e. load the right entites. But - you'll for sure have lots of panels and blocks, and even worse, you'd have to edit the discriminator map *manually* every time you or another developer implements a new block / entity. This would tear down any effort of modular programming. + +Therefore, we need something thats far more flexible. + +++ Solution + +The solution itself is pretty easy. We will have one base class that will be loaded via the page and that has specific behaviour - a Block class might render the frontend and even the backend, for example. Now, every block that you'll write might look different or need different data - therefore, we'll offer an API to these methods but internally, we use a strategy that exactly knows what to do. + +First of all, we need to make sure that we have an interface that contains every needed action. Such actions would be rendering the frontend or the backend, solving dependencies (blocks that are supposed to be placed in the sidebar could refuse to be placed in the middle of your page, for example). + +Such an interface could look like this: + + [php] + /** + * This interface defines the basic actions that a block / panel needs to support. + * + * Every blockstrategy is *only* responsible for rendering a block and declaring some basic + * support, but *not* for updating its configuration etc. For this purpose, use controllers + * and models. + * + * @author Christian Heinrich + * @version $Id: BlockStrategyInterface.php 657 2010-03-16 00:08:50Z cheinrich $ + */ + interface BlockStrategyInterface { + /** + * This could configure your entity + */ + public function setConfig(Config\EntityConfig $config); + + /** + * Returns the config this strategy is configured with. + * @return Core\Model\Config\EntityConfig + */ + public function getConfig(); + + /** + * Set the view object. + * + * @param \Zend_View_Interface $view + * @return \Zend_View_Helper_Interface + */ + public function setView(\Zend_View_Interface $view); + + /** + * @return \Zend_View_Interface + */ + public function getView(); + + /** + * Renders this strategy. This method will be called when the user + * displays the site. + * + * @return string + */ + public function renderFrontend(); + + /** + * Renders the backend of this block. This method will be called when + * a user tries to reconfigure this block instance. + * + * Most of the time, this method will return / output a simple form which in turn + * calls some controllers. + * + * @return string + */ + public function renderBackend(); + + /** + * Returns all possible types of panels this block can be stacked onto + * + * @return array + */ + public function getRequiredPanelTypes(); + + /** + * Determines whether a Block is able to use a given type or not + * @param string $typeName The typename + * @return boolean + */ + public function canUsePanelType($typeName); + + public function setBlockEntity(AbstractBlock $block); + + public function getBlockEntity(); + } + +As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this: + + [php] + /** + * This is the base class for both Panels and Blocks. + * It shouldn't be extended by your own blocks - simply write a strategy! + */ + abstract class AbstractBlock { + /** + * The id of the block item instance + * @var integer + */ + private $id; + + // Add code for relation to the parent panel, configuration objects, .... + + /** + * This var contains the classname of the strategy + * that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2) + * + * @var string + */ + protected $strategyClassName; + + /** + * This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2 + * @var BlockStrategyInterface + */ + protected $strategyInstance; + + /** + * Returns the strategy that is used for this blockitem. + * + * The strategy itself defines how this block can be rendered etc. + * + * @return string + */ + public function getStrategyClassName() { + return $this->strategyClassName; + } + + /** + * Returns the instantiated strategy + * + * @return BlockStrategyInterface + */ + public function getStrategyInstance() { + return $this->strategyInstance; + } + + /** + * Sets the strategy this block / panel should work as. Make sure that you've used + * this method before persisting the block! + * + * @param BlockStrategyInterface $strategy + */ + public function setStrategy(BlockStrategyInterface $strategy) { + $this->strategyInstance = $strategy; + $this->strategyClassName = get_class($strategy); + $strategy->setBlockEntity($this); + } + +Now, the important point is that $strategyClassName is a Doctrine 2 field, i.e. Doctrine will persist this value. This is only the class name of your strategy and not an instance! + +Finishing your strategy pattern, we hook into the Doctrine postLoad event and check whether a block has been loaded. If so, you will initialize it - i.e. get the strategies classname, create an instance of it and set it via setStrategyBlock(). + +This might look like this: + + [php] + use \Doctrine\ORM, + \Doctrine\Common; + + /** + * The BlockStrategyEventListener will initialize a strategy after the + * block itself was loaded. + * + * @author Christian Heinrich + */ + class BlockStrategyEventListener implements Common\EventSubscriber { + + protected $view; + + public function __construct(\Zend_View_Interface $view) { + $this->view = $view; + } + + public function getSubscribedEvents() { + return array(ORM\Events::postLoad); + } + + public function postLoad(ORM\Event\LifecycleEventArgs $args) { + $blockItem = $args->getEntity(); + + // Both blocks and panels are instances of Block\AbstractBlock + if ($blockItem instanceof Block\AbstractBlock) { + $strategy = $blockItem->getStrategyClassName(); + $strategyInstance = new $strategy(); + if (null !== $blockItem->getConfig()) { + $strategyInstance->setConfig($blockItem->getConfig()); + } + $strategyInstance->setView($this->view); + $blockItem->setStrategy($strategyInstance); + } + } + } + +In this example, even some variables are set - like a view object or a specific configuration object. From a1079e692c1095caef7984c91e1ca067cb5bd71f Mon Sep 17 00:00:00 2001 From: Matthieu Bontemps Date: Tue, 11 May 2010 11:46:10 +0200 Subject: [PATCH 026/430] Fix a few typos in doctrine manual --- manual/en/configuration.txt | 2 +- manual/en/dql-doctrine-query-language.txt | 4 ++-- manual/en/native-sql.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index 01c3f422c..adb746373 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -120,7 +120,7 @@ The annotation driver can be configured with a factory method on the `Doctrine\O $driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); $config->setMetadataDriverImpl($driverImpl); -The path information to the entities is requied for the annotation driver, because otherwise +The path information to the entities is required for the annotation driver, because otherwise mass-operations on all entities through the console could not work correctly. +++ Metadata Cache (***RECOMMENDED***) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index 87aa54a93..fe96c7d47 100755 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -469,7 +469,7 @@ The following methods exist on the `AbstractQuery` which both `Query` and `Nativ ++++ Parameters Prepared Statements that use numerical or named wildcards require additional parameters to be executable -agains the database. To pass parameters to the query the following methods can be used: +against the database. To pass parameters to the query the following methods can be used: * `AbstractQuery::setParameter($param, $value)` - Set the numerical or named wildcard to the given value. * `AbstractQuery::setParameters(array $params)` - Set an array of parameter key-value pairs. @@ -516,7 +516,7 @@ are to be used in userland: * Query::HINT_FORCE_PARTIAL_LOAD - Allows to hydrate objects although not all their columns are fetched. This query hint can be used to handle memory consumption problems with large result-sets that contain char or binary data. -Doctrine has no way of implicitly reloaded this data. Partially loaded objects have to be passed to +Doctrine has no way of implicitly reloading this data. Partially loaded objects have to be passed to `EntityManager::refresh()` if they are to be reloaded fully from the database. * Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland aswell. If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the diff --git a/manual/en/native-sql.txt b/manual/en/native-sql.txt index fb6359737..2e2b9b34b 100644 --- a/manual/en/native-sql.txt +++ b/manual/en/native-sql.txt @@ -146,7 +146,7 @@ The result would look like this: Note that this would be a partial object if the entity has more fields than just id and name. In the example above the column and field names are identical but that is not necessary, of course. Also note that the query string passed to createNativeQuery is **real native SQL**. Doctrine does not touch this SQL in any way. In the previous basic example, a User had no relations and the table the class is mapped to owns no foreign keys. -The next an example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress, +The next example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress, where the User is the owning side and thus owns the foreign key. [php] From 0799a5b5e9931fdb63972ab151a429552aac0d86 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Tue, 11 May 2010 15:26:35 +0200 Subject: [PATCH 027/430] Polished docs on DBAL, Transactions and Concurrency. --- manual/en/dbal.txt | 259 ++++++++++++++++----- manual/en/transactions-and-concurrency.txt | 134 +++++------ 2 files changed, 259 insertions(+), 134 deletions(-) diff --git a/manual/en/dbal.txt b/manual/en/dbal.txt index 452f45646..215bd8b29 100644 --- a/manual/en/dbal.txt +++ b/manual/en/dbal.txt @@ -1,13 +1,40 @@ -The Doctrine 2 database layer can be used independently of the object-relational mapping. It offers a leightweight abstraction layer around a PDO like API and allows optional access to lots of convenience functionality aswell as the ability to generate platform independent DQL and DDL statements. +The Doctrine database abstraction & access layer (DBAL) offers a leightweight and thin runtime layer around a PDO-like API and a lot of additional, horizontal features like database schema introspection and manipulation through an OO API. -++ Configuration +The fact that the Doctrine DBAL abstracts the concrete PDO API away through the use of interfaces that closely resemble the existing PDO API +makes it possible to implement custom drivers that may use existing native or self-made APIs. For example, the DBAL ships with a driver for Oracle databases +that uses the oci8 extension under the hood. -You can create a Doctrine Connection by using the `Doctrine\DBAL\DriverManager` class. +++ Architecture + +As already said, the DBAL is a thin layer on top of PDO. PDO itself is mainly defined in terms of 2 classes: +`PDO` and `PDOStatement`. The equivalent classes in the DBAL are `Doctrine\DBAL\Connection` and +`Doctrine\DBAL\Statement`. A `Doctrine\DBAL\Connection` wraps a `Doctrine\DBAL\Driver\Connection` +and a `Doctrine\DBAL\Statement` wraps a `Doctrine\DBAL\Driver\Statement`. + +`Doctrine\DBAL\Driver\Connection` and `Doctrine\DBAL\Driver\Statement` are just interfaces. +These interfaces are implemented by concrete drivers. For all PDO based drivers, `PDO` and +`PDOStatement` are the implementations of these interfaces. Thus, for PDO-based drivers, a +`Doctrine\DBAL\Connection` wraps a `PDO` instance and a `Doctrine\DBAL\Statement` wraps a +`PDOStatement` instance. Even more, a `Doctrine\DBAL\Connection` *is a* `Doctrine\DBAL\Driver\Connection` +and a `Doctrine\DBAL\Statement` *is a* `Doctrine\DBAL\Driver\Statement`. + +What does a `Doctrine\DBAL\Connection` or a `Doctrine\DBAL\Statement` add to the underlying +driver implementations? The enhancements include SQL logging, events and control over the +transaction isolation level in a portable manner, among others. + +A DBAL driver is defined to the outside in terms of 3 interfaces: `Doctrine\DBAL\Driver`, +`Doctrine\DBAL\Driver\Connection` and `Doctrine\DBAL\Driver\Statement`. +The latter two resemble (a subset of) the corresponding PDO API. +A concrete driver implementation must provide implementation classes for these 3 interfaces. + + +++ Getting a Connection + +You can get a DBAL Connection through the `Doctrine\DBAL\DriverManager` class. [php] $config = new \Doctrine\DBAL\Configuration(); //.. - $connectionParams = array( 'dbname' => 'mydb', 'user' => 'user', @@ -17,45 +44,89 @@ You can create a Doctrine Connection by using the `Doctrine\DBAL\DriverManager` ); $conn = DriverManager::getConnection($connectionParams); -The `DriverManager` returns an instance of `Doctrine\DBAL\Connection` which is a wrapper around any configured database driver, for example the PDO Mysql driver in the previous example. +The `DriverManager` returns an instance of `Doctrine\DBAL\Connection` which is a wrapper around the underlying driver connection (which is often a PDO instance). -+++ Connection Options +The following sections describe the available connection parameters in detail. -The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array -or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the -DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The following option keys can be specified -to create your connection: -++++ Driver Management Options: ++++ Driver -* driver - Allows to specify the default drivers shipped with Doctrine 2, 'pdo_mysql', 'pdo_sqlite', 'pdo_pgsql, and 'oci'. -* driverClass - If no 'driver' is specified this allows usage of a userland implementation of Doctrine\DBAL\Driver. -* pdo - If PDO is already instantiated for Mysql, SqLite or PgSQL this key can be used to pass this instance into Doctrine. -* wrapperClass - By default Doctrine\DBAL\Connection is wrapped around each driver, however this option allows to specify a userland sub-class. +The driver specifies the actual implementations of the DBAL interfaces to use. It can be configured in one of three ways: -++++ Driver Configuration Options: +* `driver`: The built-in driver implementation to use. The following drivers are currently available: + * `pdo_mysql`: A MySQL driver that uses the pdo_mysql PDO extension. + * `pdo_sqlite`: An SQLite driver that uses the pdo_sqlite PDO extension. + * `pdo_pgsql`: A PostgreSQL driver that uses the pdo_pgsql PDO extension. + * `pdo_oci`: An Oracle driver that uses the pdo_oci PDO extension. **Note that this driver caused problems in our tests. Prefer the oci8 driver if possible.** + * `oci8`:` An Oracle driver that uses the oci8 PHP extension. +* `driverClass`: Specifies a custom driver implementation if no 'driver' is specified. This allows the use of custom drivers that are not part of the Doctrine DBAL itself. +* `pdo`: Specifies an existing PDO instance to use. -Common Configuration Options across all database drivers: -* platform - An instance of `Doctrine\DBAL\Platforms\AbstractPlatform`. This is only required for userland implementations, each driver shipped with Doctrine 2 has a default platform. -* user - Username required to connect to the database. -* password - Password required to connect to the database. -* driverOptions - Array of options passed to the driver instance on calling to Driver::connect. ++++ Wrapper Class -Driver Configuration Options are different for each Database Driver, here are some of the db specific ones: +By default a `Doctrine\DBAL\Connection` is wrapped around a driver `Connection`. +The `wrapperClass` option allows to specify a custom wrapper implementation to use, however, +custom wrapper class must be a subclass of `Doctrine\DBAL\Connection`. -* host - Database host (Mysql, Pgsql, Oracle) -* port - Database port (Mysql, Pgsql, Oracle) -* dbname - Name of the database/schema to connect to. (Mysql, Pgsql, Oracle) -* unix_socket - Name of the socket used to connect to the database. (Mysql) -* charset - The charset used when connecting to the database. (Oracle) -* path - The filesystem path to the database (Sqlite) -* memory - True if the sqlite database should be in memory. (Sqlite) -+++ DBAL Events ++++ Connection Details -Both `Doctrine\DBAL\DriverManager` and `Doctrine\DBAL\Connection` accept an instance of `Doctrine\Common\EventManager`. -The EventManager has a couple of events inside the DBAL layer that are triggered for the user to listen to: +The connection details identify the database to connect to as well as the credentials to use. +The connection details can differ depending on the used driver. The following sections describe +the options recognized by each built-in driver. + +> *NOTE* +> When using an existing PDO instance through the `pdo` option, specifying connection details is obviously not necessary. + +++++ pdo_sqlite + +* `user` (string): Username to use when connecting to the database. +* `password` (string): Password to use when connecting to the database. +* `path` (string): The filesystem path to the database file. Mutually exclusive with `memory`. `path` takes precedence. +* `memory` (boolean): True if the SQLite database should be in-memory (non-persistent). Mutually exclusive with `path`. `path` takes precedence. + +++++ pdo_mysql + +* `user` (string): Username to use when connecting to the database. +* `password` (string): Password to use when connecting to the database. +* `host` (string): Hostname of the database to connect to. +* `port` (integer): Port of the database to connect to. +* `dbname` (string): Name of the database/schema to connect to. +* `unix_socket` (string): Name of the socket used to connect to the database. + +++++ pdo_pgsql + +* `user` (string): Username to use when connecting to the database. +* `password` (string): Password to use when connecting to the database. +* `host` (string): Hostname of the database to connect to. +* `port` (integer): Port of the database to connect to. +* `dbname` (string): Name of the database/schema to connect to. + +++++ pdo_oci / oci8 + +* `user` (string): Username to use when connecting to the database. +* `password` (string): Password to use when connecting to the database. +* `host` (string): Hostname of the database to connect to. +* `port` (integer): Port of the database to connect to. +* `dbname` (string): Name of the database/schema to connect to. +* `charset` (string): The charset used when connecting to the database. + + ++++ Custom Platform + +Each built-in driver uses a default implementation of `Doctrine\DBAL\Platforms\AbstractPlatform`. +If you wish to use a customized or custom implementation, you can pass a precreated instance +in the `platform` option. + + ++++ Custom Driver Options + +The `driverOptions` option allows to pass arbitrary options through to the driver. +This is equivalent to the 4th argument of the [PDO constructor](http://php.net/manual/en/pdo.construct.php). + + ++++ Events ++++ PostConnect Event @@ -77,6 +148,7 @@ You can register events by subscribing them to the `EventManager` instance passe $conn = DriverManager::getConnection($connectionParams, null, $evm); + ++ DBAL API +++ DBAL Architecture @@ -91,7 +163,7 @@ The DBAL is seperated into several different packages that perfectly seperate re +++ Data Retrieval and Manipulation - The following methods exist for executing queries against your configured database, three very generic methods and some advanced retrievial methods: +The following methods exist for executing queries against your configured database, three very generic methods and some advanced retrievial methods: * `prepare($sql)` - Prepare a given sql statement and return the `\Doctrine\DBAL\Driver\Statement` instance. * `executeUpdate($sql, array $params)` - Executes a prepared statement with the given sql and parameters and returns the affected rows count. @@ -116,44 +188,105 @@ By default the Doctrine DBAL does no escaping. Escaping is a very tricky busines +++ Transactions -Doctrine handles transactions with a PDO like API, having methods for `beginTransaction()`, `commit()` and `rollBack()`. For consistency across different drivers Doctrine also handles the nesting of transactions internally. You can call `beginTransaction()` more than once, and only a matching amount of calls to `commit()` triggers the commit to the database. The Doctrine connectionalso has a method to set the transaction isolation level of the connection as supported by the underlying database. +A `Doctrine\DBAL\Connection` provides a PDO-like API for transaction management, with the +methods `Connection#beginTransaction()`, `Connection#commit()` and `Connection#rollback()`. + +Transaction demarcation with the Doctrine DBAL looks as follows: [php] - class Connection - { - /** - * Constant for transaction isolation level READ UNCOMMITTED. - */ - const TRANSACTION_READ_UNCOMMITTED = 1; - - /** - * Constant for transaction isolation level READ COMMITTED. - */ - const TRANSACTION_READ_COMMITTED = 2; - - /** - * Constant for transaction isolation level REPEATABLE READ. - */ - const TRANSACTION_REPEATABLE_READ = 3; - - /** - * Constant for transaction isolation level SERIALIZABLE. - */ - const TRANSACTION_SERIALIZABLE = 4; - } - -A transaction with Doctrine DBAL might then look like: - - [php] - $conn->setTransactionIsolationLevel(Connection::TRANSACTION_SERIALIZABLE); - + $conn->beginTransaction(); try{ - $conn->beginTransaction(); // do stuff $conn->commit(); } catch(\Exception $e) { $conn->rollback(); + //handle or rethrow } + +The `Doctrine\DBAL\Connection` also has methods control the transaction isolation level as supported by the underlying database. +`Connection#setTransactionIsolation($level)` and Connection#getTransactionIsolation() can be used for that purpose. +The possible isolation levels are represented by the following constants: + + [php] + Connection::TRANSACTION_READ_UNCOMMITTED + Connection::TRANSACTION_READ_COMMITTED + Connection::TRANSACTION_REPEATABLE_READ + Connection::TRANSACTION_SERIALIZABLE + +The default transaction isolation level of a `Doctrine\DBAL\Connection` is chosen by the underlying +platform but it is always at least READ_COMMITTED. + + ++++ Transaction Nesting + +A `Doctrine\DBAL\Connection` also adds support for nesting transactions, or rather propagating transaction control up the call stack. +For that purpose, the `Connection` class keeps an internal counter that represents the nesting level and is increased/decreased as +`beginTransaction()`, `commit()` and `rollback()` are invoked. `beginTransaction()` increases the nesting level whilst `commit()`` +and `rollback()` decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1, +`beginTransaction()` is invoked on the underlying driver connection and whenever the nesting level transitions from 1 to 0, +`commit()` or `rollback()` is invoked on the underlying driver, depending on whether the transition was caused by `Connection#commit()` +or `Connection#rollback()`. + +What this means is that transaction control is basically passed to code higher up in the call stack and +the inner transaction block is ignored, with one important exception that is described further below. +Do not confuse this with "real" nested transactions or savepoints. These are not supported by Doctrine. +There is always only a single, real database transaction. + +To visualize what this means in practice, consider the following example: + + [php] + // $conn instanceof Doctrine\DBAL\Connection + $conn->beginTransaction(); // 0 => 1, "real" transaction started + try { + + ... + + // nested transaction block, this might be in some other API/library code that is + // unaware of the outer transaction. + $conn->beginTransaction(); // 1 => 2 + try { + ... + + $conn->commit(); // 2 => 1 + } catch (Exception $e) { + $conn->rollback(); // 2 => 1, transaction marked for rollback only + throw $e; + } + + ... + + $conn->commit(); // 1 => 0, "real" transaction committed + } catch (Exception $e) { + $conn->rollback(); // 1 => 0, "real" transaction rollback + throw $e; + } + +However, **a rollback in a nested transaction block will always mark the current transaction so that the only possible outcome of the transaction is to be rolled back**. +That means in the above example, the rollback in the inner transaction block marks the whole transaction for rollback only. +Even if the nested transaction block would not rethrow the exception, the transaction is marked for rollback only and the commit of +the outer transaction would trigger an exception, leading to the final rollback. +This also means that you can not successfully commit some changes in an outer transaction if an inner transaction block fails and issues a rollback, +even if this would be the desired behavior (i.e. because the nested operation is "optional" for the purpose of the outer transaction block). +To achieve that, you need to restructure your application logic so as to avoid nesting transaction blocks. If this is not possible +because the nested transaction blocks are in a third-party API you're out of luck. + +All that is guaruanteed to the inner transaction is that it still happens atomically, all or nothing, the transaction just gets a wider scope +and the control is handed to the outer scope. + +> *CAUTION* +> The transaction nesting described here is a debated feature that has it's critics. Form your own opinion. +> We recommend avoiding nesting transaction blocks when possible, and most of the time, it is possible. +> Transaction control should mostly be left to a service layer and not be handled in data access objects or similar. + +- + +> **CAUTION** +> Directly invoking `PDO#beginTransaction()`, `PDO#commit()` or `PDO#rollback()` or the +> corresponding methods on the particular `Doctrine\DBAL\Driver\Connection` instance in +> use bybasses the transparent transaction nesting that is provided by +> `Doctrine\DBAL\Connection` and can therefore corrupt the nesting level, causing errors +> with broken transaction boundaries that may be hard to debug. + ++ Schema Representation diff --git a/manual/en/transactions-and-concurrency.txt b/manual/en/transactions-and-concurrency.txt index 0729872e3..ac89ab908 100644 --- a/manual/en/transactions-and-concurrency.txt +++ b/manual/en/transactions-and-concurrency.txt @@ -1,46 +1,17 @@ ++ Transaction Demarcation -Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very important because if not done properly it can have a negative effect on the performance of your application. Many databases and database abstraction layers like PDO by default operate in auto-commit mode, which means that every single SQL statement is wrapped in a small transaction that is immediately committed. Without any explicit transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap and many small transactions degrade the performance of your application. +Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very important because if not done properly it can negatively affect the performance of your application. Many databases and database abstraction layers like PDO by default operate in auto-commit mode, which means that every single SQL statement is wrapped in a small transaction. Without any explicit transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap. -For the most part, Doctrine 2 already takes care of proper transaction demarcation for you: All the write operations (INSERT/UPDATE/DELETE) are queued until `EntityManager#flush()` is invoked which wraps all of these changes in a single, small transaction. This is a strategy called "transactional write-behind" that is frequently used in ORM solutions to increase efficiency. +For the most part, Doctrine 2 already takes care of proper transaction demarcation for you: All the write operations (INSERT/UPDATE/DELETE) are queued until `EntityManager#flush()` is invoked which wraps all of these changes in a single transaction. -However, Doctrine 2 also allows you to take over and control transaction demarcation yourself, thereby "widening" the transaction boundaries. This is possible due to transparent nesting of transactions that is described in the following section. +However, Doctrine 2 also allows (and ecourages) you to take over and control transaction demarcation yourself. -++ Transaction Nesting +These are two ways to deal with transactions when using the Doctrine ORM and are now described in more detail. -Each `Doctrine\DBAL\Driver\Connection` instance is wrapped in a `Doctrine\DBAL\Connection` that adds support for transparent nesting of transactions. For that purpose, the Connection class keeps an internal counter that represents the nesting level and is increased/decreased as beginTransaction(), commit() and rollback() are invoked. beginTransaction() increases the nesting level whilst commit() and rollback() decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1, beginTransaction() is invoked on the underlying driver and whenever the nesting level transitions from 1 to 0, commit() or rollback() is invoked on the underlying driver, depending on whether the transition was caused by `Connection#commit()` or `Connection#rollback()`. ++++ Approach 1: Implicitly -Lets visualize what that means in practice. It means that the first call to `Doctrine\DBAL\Connection#beginTransaction()` will increase the nesting level from 0 to 1 and invoke beginTransaction() on the underlying driver, effectively starting a "real" transaction by suspending auto-commit mode. Any subsequent, nested calls to `Doctrine\DBAL\Connection#beginTransaction()` would only increase the nesting level. - -Here is an example to help visualize how this works: - - [php] - // $conn instanceof Doctrine\DBAL\Connection - try { - $conn->beginTransaction(); // 0 => 1, "real" transaction started - - ... - - try { - $conn->beginTransaction(); // 1 => 2 - - ... - - $conn->commit(); // 2 => 1 - } catch (Exception $e) { - $conn->rollback(); // 2 => 1 - throw $e; - } - - ... - - $conn->commit(); // 1 => 0, "real" transaction committed - } catch (Exception $e) { - $conn->rollback(); // 1 => 0, "real" transaction rollback - throw $e; - } - -What is the benefit of this? It allows reliable and transparent widening of transaction boundaries. Given the following code snippet, without any explicit transaction demarcation: +The first approach is to use the implicit transaction handling provided by the Doctrine ORM +EntityManager. Given the following code snippet, without any explicit transaction demarcation: [php] // $em instanceof EntityManager @@ -49,50 +20,58 @@ What is the benefit of this? It allows reliable and transparent widening of tran $em->persist($user); $em->flush(); -Inside `EntityManager#flush()` something like this happens: +Since we do not do any custom transaction demarcation in the above code, `EntityManager#flush()` will begin +and commit/rollback a transaction. This behavior is made possible by the aggregation of the DML operations +by the Doctrine ORM and is sufficient if all the data manipulation that is part of a unit of work happens +through the domain model and thus the ORM. - [php] - try { - $conn->beginTransaction(); // suspend auto-commit - - ... commit all changes to the database ... - - $conn->commit(); - } catch (Exception $e) { - $conn->rollback(); - throw $e; - } - -Since we do not do any custom transaction demarcation in the first snippet, `EntityManager#flush()` will begin and commit/rollback a "real" transaction. Now, if we want to widen the transaction boundaries, say, because we want to include some manual work with a `Doctrine\DBAL\Connection` in the same transaction, we can simply do this: + ++++ Approach 2: Explicitly + +The explicit alternative is to use the `Doctrine\DBAL\Connection` API +directly to control the transaction boundaries. The code then looks like this: [php] // $em instanceof EntityManager - $conn = $em->getConnection(); + $em->getConnection()->beginTransaction(); // suspend auto-commit try { - $conn->beginTransaction(); // suspend auto-commit - - // Direct use of the Connection - $conn->insert(...); - + //... do some work $user = new User; $user->setName('George'); $em->persist($user); $em->flush(); - - $conn->commit(); + $em->getConnection()->commit(); } catch (Exception $e) { - $conn->rollback(); - // handle or rethrow + $em->getConnection()->rollback(); + $em->close(); + throw $e; } - -Now, our own code controls the "real" transaction and the transaction demarcation that happens inside `EntityManager#flush()` will merely affect the nesting level. When flush() returns, either by throwing an exception or regularly, the nesting level is the same as before the invocation of flush(), in this case 1, and thus our own $conn->commit() / $conn->rollback() affect the "real" transaction as expected, since we were the ones who started the transaction. -> **CAUTION** -> Directly invoking `PDO#beginTransaction()`, `PDO#commit()` or `PDO#rollback()` or the -> corresponding methods on the particular `Doctrine\DBAL\Driver\Connection` instance in -> use bybasses the transparent transaction nesting that is provided by -> `Doctrine\DBAL\Connection` and can therefore corrupt the nesting level, causing errors -> with broken transaction boundaries that may be hard to debug. +Explicit transaction demarcation is required when you want to include custom DBAL operations in a unit of work +or when you want to make use of some methods of the `EntityManager` API that require an active transaction. +Such methods will throw a `TransactionRequiredException` to inform you of that requirement. + + +++ Exception Handling + +When using implicit transaction demarcation and an exception occurs during `EntityManager#flush()`, the transaction +is automatically rolled back and the `EntityManager` closed. + +When using explicit transaction demarcation and an exception occurs, the transaction should be rolled back immediately +and the `EntityManager` closed by invoking `EntityManager#close()` and subsequently discarded, as demonstrated in +the example above. Note that when catching `Exception` you should generally rethrow the exception. If you intend to +recover from some exceptions, catch them explicitly in earlier catch blocks (but do not forget to rollback the +transaction and close the `EntityManager` there as well). All other best practices of exception handling apply +similarly (i.e. either log or rethrow, not both, etc.). + +As a result of this procedure, all previously managed or removed instances of the `EntityManager` become detached. +The state of the detached objects will be the state at the point at which the transaction was rolled back. +The state of the objects is in no way rolled back and thus the objects are now out of synch with the database. +The application can continue to use the detached objects, knowing that their state is potentially no longer +accurate. + +If you intend to start another unit of work after an exception has occured you should do that with a new `EntityManager`. + ++ Optimistic Locking @@ -111,8 +90,7 @@ You designate a version field in an entity as follows. In this example we'll use // ... } -You could also just as easily use a datetime column and instead of incrementing an integer, a timestamp will be kept up to date. - +Alternatively a datetime type can be used (which maps to an SQL timestamp or datetime): [php] class User @@ -121,4 +99,18 @@ You could also just as easily use a datetime column and instead of incrementing /** @Version @Column(type="datetime") */ private $version; // ... - } \ No newline at end of file + } + +Version numbers (not timestamps) should however be preferred as they can not potentially conflict in a highly concurrent +environment, unlike timestamps where this is a possibility, depending on the resolution of the timestamp on the particular +database platform. + +When a version conflict is encountered during `EntityManager#flush()`, an `OptimisticLockException` is thrown +and the active transaction rolled back (or marked for rollback). This exception can be caught and handled. +Potential responses to an OptimisticLockException are to present the conflict to the user or to +refresh or reload objects in a new transaction and then retrying the transaction. + + + + + From b8576559928e665fccbe8a0321c52e500f7c4e5b Mon Sep 17 00:00:00 2001 From: Christian Heinrich Date: Wed, 12 May 2010 14:43:09 +0200 Subject: [PATCH 028/430] Improved query builder section -> Added a new section about binding params to the query -> Added a short note on how to use $qb->expr()->in() with string values --- manual/en/query-builder.txt | 54 ++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/manual/en/query-builder.txt b/manual/en/query-builder.txt index 1080d4601..00d499c33 100644 --- a/manual/en/query-builder.txt +++ b/manual/en/query-builder.txt @@ -72,6 +72,56 @@ This method is the responsable to build every piece of DQL. It takes 3 parameter ->add('where', 'u.id = ?1') ->add('orderBy', 'u.name ASC'); +++++ Binding parameters to your query + +Doctrine supports dynamic binding of parameters to your query, similar to preparing queries. You can use both strings and numbers as placeholders, although both have a slightly different syntax. Binding parameters can simply be achieved as follows: + + [php] + // $qb instanceof QueryBuilder + + // example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support + $qb->add('select', 'u') + ->add('from', 'User u') + ->add('where', 'u.id = ?1') + ->add('orderBy', 'u.name ASC'); + ->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100 + +You are not forced to enumerate your placeholders as the alternative syntax is available: + + + [php] + // $qb instanceof QueryBuilder + + // example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support + $qb->add('select', 'u') + ->add('from', 'User u') + ->add('where', 'u.id = :identifier') + ->add('orderBy', 'u.name ASC'); + ->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100 + +Note that numeric placeholders start with a ? followed by a number while the named placeholders start with a : followed by a string. + +If you've got several parameters to bind to your query, you can also use setParameters() instead of setParameter() with the following syntax: + + [php] + // $qb instanceof QueryBuilder + + // Query here... + $qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2', 'whatever' => 'your value for :whatever')); + + +Getting already bound parameters is easy - simply use the abovementioned syntax with "getParameter()" or "getParameters()": + + [php] + // $qb instanceof QueryBuilder + + // See example above + $params = qb->getParameters(array(1, 2, 'whatever')); + // Equivalent to + $param = array($qb->getParameter(1), $qb->getParameter(2), $qb->getParameter('whatever')); + +Note: If you try to get a parameter that was not bound yet, getParameter() simply returns NULL. + ++++ Expr\* classes When you call `add()` with string, it internally evaluates to an instance of `Doctrine\ORM\Query\Expr\Expr\*` class. @@ -197,6 +247,8 @@ Here it is a complete list of supported helper methods available: public function not($restriction); // Returns Expr\Func instance // Example - $qb->expr()->in('u.id', array(1, 2, 3)) + // Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception. + // Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above) public function in($x, $y); // Returns Expr\Func instance // Example - $qb->expr()->notIn('u.id', '2') @@ -353,4 +405,4 @@ Here is a complete list of helper methods in `QueryBuilder`: // Example - $qb->addOrderBy('u.firstName') public function addOrderBy($sort, $order = null); // Default $order = 'ASC' - } \ No newline at end of file + } From 177a9ce4d21d33111c61b346c49086cbb0fee87f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 12 May 2010 23:53:09 +0200 Subject: [PATCH 029/430] Update Getting Started XML Edition to work with inversed-by attributes --- cookbook/en/getting-started-xml-edition.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cookbook/en/getting-started-xml-edition.txt b/cookbook/en/getting-started-xml-edition.txt index b5834ed5a..8669d0ba6 100644 --- a/cookbook/en/getting-started-xml-edition.txt +++ b/cookbook/en/getting-started-xml-edition.txt @@ -295,11 +295,11 @@ We then go on specifying the definition of a Bug: - + - + @@ -326,7 +326,9 @@ After the field definitions the two qualified references to the user entity are the `many-to-one` tag. The class name of the related entity has to be specified with the `target-entity` attribute, which is enough information for the database mapper to access the foreign-table. The `join-column` tags are used to specifiy how the foreign and referend columns are named, an information -Doctrine needs to construct joins between those two entities correctly. +Doctrine needs to construct joins between those two entities correctly. Since `reporter` and `engineer` +are on the owning side of a bi-direcitonal relation we also have to specify the `inversed-by` attribute. +They have to point to the field names on the inverse side of the relationship. The last missing property is the `Bug::$products` collection. It holds all products where the specific bug is occouring in. Again you have to define the `target-entity` and `field` attributes on the `many-to-many` From 21bbbb1f1fbae22c4bf982359367cea41b1ddc15 Mon Sep 17 00:00:00 2001 From: Christian Heinrich Date: Thu, 13 May 2010 00:58:17 +0200 Subject: [PATCH 030/430] Updated query builder docs --- manual/en/query-builder.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/manual/en/query-builder.txt b/manual/en/query-builder.txt index 00d499c33..9c8c95219 100644 --- a/manual/en/query-builder.txt +++ b/manual/en/query-builder.txt @@ -74,7 +74,7 @@ This method is the responsable to build every piece of DQL. It takes 3 parameter ++++ Binding parameters to your query -Doctrine supports dynamic binding of parameters to your query, similar to preparing queries. You can use both strings and numbers as placeholders, although both have a slightly different syntax. Binding parameters can simply be achieved as follows: +Doctrine supports dynamic binding of parameters to your query, similar to preparing queries. You can use both strings and numbers as placeholders, although both have a slightly different syntax. Additionally, you must make your choice: Mixing both styles is not allowed. Binding parameters can simply be achieved as follows: [php] // $qb instanceof QueryBuilder @@ -107,7 +107,7 @@ If you've got several parameters to bind to your query, you can also use setPara // $qb instanceof QueryBuilder // Query here... - $qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2', 'whatever' => 'your value for :whatever')); + $qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2')); Getting already bound parameters is easy - simply use the abovementioned syntax with "getParameter()" or "getParameters()": @@ -116,9 +116,9 @@ Getting already bound parameters is easy - simply use the abovementioned syntax // $qb instanceof QueryBuilder // See example above - $params = qb->getParameters(array(1, 2, 'whatever')); + $params = qb->getParameters(array(1, 2)); // Equivalent to - $param = array($qb->getParameter(1), $qb->getParameter(2), $qb->getParameter('whatever')); + $param = array($qb->getParameter(1), $qb->getParameter(2)); Note: If you try to get a parameter that was not bound yet, getParameter() simply returns NULL. From 84c1d340288c4bfdd15fa6984557f40160f53383 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 13 May 2010 13:06:16 +0200 Subject: [PATCH 031/430] Added documentation about control abstractions for transaction handling. --- manual/en/dbal.txt | 14 +++++++++++-- manual/en/transactions-and-concurrency.txt | 24 ++++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/manual/en/dbal.txt b/manual/en/dbal.txt index 215bd8b29..e77314d4b 100644 --- a/manual/en/dbal.txt +++ b/manual/en/dbal.txt @@ -198,11 +198,21 @@ Transaction demarcation with the Doctrine DBAL looks as follows: try{ // do stuff $conn->commit(); - } catch(\Exception $e) { + } catch(Exception $e) { $conn->rollback(); - //handle or rethrow + throw $e; } + +Alternatively, the control abstraction `Connection#transactional($func)` can be used to make +the code more concise and to make sure you never forget to rollback the transaction in the case +of an exception. The following code snippet is functionally equivalent to the previous one: + + [php] + $conn->transactional(function($conn) { + // do stuff + }); + The `Doctrine\DBAL\Connection` also has methods control the transaction isolation level as supported by the underlying database. `Connection#setTransactionIsolation($level)` and Connection#getTransactionIsolation() can be used for that purpose. The possible isolation levels are represented by the following constants: diff --git a/manual/en/transactions-and-concurrency.txt b/manual/en/transactions-and-concurrency.txt index ac89ab908..7d046029f 100644 --- a/manual/en/transactions-and-concurrency.txt +++ b/manual/en/transactions-and-concurrency.txt @@ -51,15 +51,35 @@ Explicit transaction demarcation is required when you want to include custom DBA or when you want to make use of some methods of the `EntityManager` API that require an active transaction. Such methods will throw a `TransactionRequiredException` to inform you of that requirement. +A more convenient alternative for explicit transaction demarcation is the use of provided control +abstractions in the form of `Connection#transactional($func)` and `EntityManager#transactional($func)`. +When used, these control abstractions ensure that you never forget to rollback the transaction or +close the `EntityManager`, apart from the obvious code reduction. An example that is functionally +equivalent to the previously shown code looks as follows: -++ Exception Handling + [php] + // $em instanceof EntityManager + $em->transactional(function($em) { + //... do some work + $user = new User; + $user->setName('George'); + $em->persist($user); + }); + +The difference between `Connection#transactional($func)` and `EntityManager#transactional($func)` is +that the latter abstraction flushes the `EntityManager` prior to transaction commit and also closes +the `EntityManager` properly when an exception occurs (in addition to rolling back the transaction). + + ++++ Exception Handling When using implicit transaction demarcation and an exception occurs during `EntityManager#flush()`, the transaction is automatically rolled back and the `EntityManager` closed. When using explicit transaction demarcation and an exception occurs, the transaction should be rolled back immediately and the `EntityManager` closed by invoking `EntityManager#close()` and subsequently discarded, as demonstrated in -the example above. Note that when catching `Exception` you should generally rethrow the exception. If you intend to +the example above. This can be handled elegantly by the control abstractions shown earlier. +Note that when catching `Exception` you should generally rethrow the exception. If you intend to recover from some exceptions, catch them explicitly in earlier catch blocks (but do not forget to rollback the transaction and close the `EntityManager` there as well). All other best practices of exception handling apply similarly (i.e. either log or rethrow, not both, etc.). From 184a402f3983b3c18ed5b36c633c9c7935839266 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 13 May 2010 13:14:37 +0200 Subject: [PATCH 032/430] Fixed typo --- manual/en/dbal.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/dbal.txt b/manual/en/dbal.txt index cb2877b95..017edf56b 100644 --- a/manual/en/dbal.txt +++ b/manual/en/dbal.txt @@ -213,7 +213,7 @@ of an exception. The following code snippet is functionally equivalent to the pr }); -The `Doctrine\DBAL\Connection` also has methods control the transaction isolation level as supported by the underlying database. +The `Doctrine\DBAL\Connection` also has methods to control the transaction isolation level as supported by the underlying database. `Connection#setTransactionIsolation($level)` and Connection#getTransactionIsolation() can be used for that purpose. The possible isolation levels are represented by the following constants: From 5a79963bd22bcde8b8087903fb3ab5e05153ce13 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 14 May 2010 11:12:54 -0400 Subject: [PATCH 033/430] Removing dbal documentation from orm docs --- manual/en.txt | 1 - manual/en/dbal.txt | 388 --------------------------------------------- 2 files changed, 389 deletions(-) delete mode 100644 manual/en/dbal.txt diff --git a/manual/en.txt b/manual/en.txt index 56e3ff339..a95c260f6 100644 --- a/manual/en.txt +++ b/manual/en.txt @@ -19,5 +19,4 @@ + Improving Performance + Tools + Metadata Drivers -+ DBAL + Best Practices \ No newline at end of file diff --git a/manual/en/dbal.txt b/manual/en/dbal.txt deleted file mode 100644 index 017edf56b..000000000 --- a/manual/en/dbal.txt +++ /dev/null @@ -1,388 +0,0 @@ -The Doctrine database abstraction & access layer (DBAL) offers a leightweight and thin runtime layer around a PDO-like API and a lot of additional, horizontal features like database schema introspection and manipulation through an OO API. - -The fact that the Doctrine DBAL abstracts the concrete PDO API away through the use of interfaces that closely resemble the existing PDO API -makes it possible to implement custom drivers that may use existing native or self-made APIs. For example, the DBAL ships with a driver for Oracle databases -that uses the oci8 extension under the hood. - -++ Architecture - -As already said, the DBAL is a thin layer on top of PDO. PDO itself is mainly defined in terms of 2 classes: -`PDO` and `PDOStatement`. The equivalent classes in the DBAL are `Doctrine\DBAL\Connection` and -`Doctrine\DBAL\Statement`. A `Doctrine\DBAL\Connection` wraps a `Doctrine\DBAL\Driver\Connection` -and a `Doctrine\DBAL\Statement` wraps a `Doctrine\DBAL\Driver\Statement`. - -`Doctrine\DBAL\Driver\Connection` and `Doctrine\DBAL\Driver\Statement` are just interfaces. -These interfaces are implemented by concrete drivers. For all PDO based drivers, `PDO` and -`PDOStatement` are the implementations of these interfaces. Thus, for PDO-based drivers, a -`Doctrine\DBAL\Connection` wraps a `PDO` instance and a `Doctrine\DBAL\Statement` wraps a -`PDOStatement` instance. Even more, a `Doctrine\DBAL\Connection` *is a* `Doctrine\DBAL\Driver\Connection` -and a `Doctrine\DBAL\Statement` *is a* `Doctrine\DBAL\Driver\Statement`. - -What does a `Doctrine\DBAL\Connection` or a `Doctrine\DBAL\Statement` add to the underlying -driver implementations? The enhancements include SQL logging, events and control over the -transaction isolation level in a portable manner, among others. - -A DBAL driver is defined to the outside in terms of 3 interfaces: `Doctrine\DBAL\Driver`, -`Doctrine\DBAL\Driver\Connection` and `Doctrine\DBAL\Driver\Statement`. -The latter two resemble (a subset of) the corresponding PDO API. -A concrete driver implementation must provide implementation classes for these 3 interfaces. - - -++ Getting a Connection - -You can get a DBAL Connection through the `Doctrine\DBAL\DriverManager` class. - - [php] - $config = new \Doctrine\DBAL\Configuration(); - //.. - $connectionParams = array( - 'dbname' => 'mydb', - 'user' => 'user', - 'password' => 'secret', - 'host' => 'locahlost', - 'driver' => 'pdo_mysql', - ); - $conn = DriverManager::getConnection($connectionParams); - -The `DriverManager` returns an instance of `Doctrine\DBAL\Connection` which is a wrapper around the underlying driver connection (which is often a PDO instance). - -The following sections describe the available connection parameters in detail. - - -+++ Driver - -The driver specifies the actual implementations of the DBAL interfaces to use. It can be configured in one of three ways: - -* `driver`: The built-in driver implementation to use. The following drivers are currently available: - * `pdo_mysql`: A MySQL driver that uses the pdo_mysql PDO extension. - * `pdo_sqlite`: An SQLite driver that uses the pdo_sqlite PDO extension. - * `pdo_pgsql`: A PostgreSQL driver that uses the pdo_pgsql PDO extension. - * `pdo_oci`: An Oracle driver that uses the pdo_oci PDO extension. **Note that this driver caused problems in our tests. Prefer the oci8 driver if possible.** - * `oci8`:` An Oracle driver that uses the oci8 PHP extension. -* `driverClass`: Specifies a custom driver implementation if no 'driver' is specified. This allows the use of custom drivers that are not part of the Doctrine DBAL itself. -* `pdo`: Specifies an existing PDO instance to use. - - -+++ Wrapper Class - -By default a `Doctrine\DBAL\Connection` is wrapped around a driver `Connection`. -The `wrapperClass` option allows to specify a custom wrapper implementation to use, however, -custom wrapper class must be a subclass of `Doctrine\DBAL\Connection`. - - -+++ Connection Details - -The connection details identify the database to connect to as well as the credentials to use. -The connection details can differ depending on the used driver. The following sections describe -the options recognized by each built-in driver. - -> *NOTE* -> When using an existing PDO instance through the `pdo` option, specifying connection details is obviously not necessary. - -++++ pdo_sqlite - -* `user` (string): Username to use when connecting to the database. -* `password` (string): Password to use when connecting to the database. -* `path` (string): The filesystem path to the database file. Mutually exclusive with `memory`. `path` takes precedence. -* `memory` (boolean): True if the SQLite database should be in-memory (non-persistent). Mutually exclusive with `path`. `path` takes precedence. - -++++ pdo_mysql - -* `user` (string): Username to use when connecting to the database. -* `password` (string): Password to use when connecting to the database. -* `host` (string): Hostname of the database to connect to. -* `port` (integer): Port of the database to connect to. -* `dbname` (string): Name of the database/schema to connect to. -* `unix_socket` (string): Name of the socket used to connect to the database. - -++++ pdo_pgsql - -* `user` (string): Username to use when connecting to the database. -* `password` (string): Password to use when connecting to the database. -* `host` (string): Hostname of the database to connect to. -* `port` (integer): Port of the database to connect to. -* `dbname` (string): Name of the database/schema to connect to. - -++++ pdo_oci / oci8 - -* `user` (string): Username to use when connecting to the database. -* `password` (string): Password to use when connecting to the database. -* `host` (string): Hostname of the database to connect to. -* `port` (integer): Port of the database to connect to. -* `dbname` (string): Name of the database/schema to connect to. -* `charset` (string): The charset used when connecting to the database. - - -+++ Custom Platform - -Each built-in driver uses a default implementation of `Doctrine\DBAL\Platforms\AbstractPlatform`. -If you wish to use a customized or custom implementation, you can pass a precreated instance -in the `platform` option. - - -+++ Custom Driver Options - -The `driverOptions` option allows to pass arbitrary options through to the driver. -This is equivalent to the 4th argument of the [PDO constructor](http://php.net/manual/en/pdo.construct.php). - - -+++ Events - -++++ PostConnect Event - -`Doctrine\DBAL\Events::postConnect` is triggered right after the connection to the database is established. It allows -to specify any relevant connection specific options and gives access to the `Doctrine\DBAL\Connection` instance -that is responsible for the connection management via an instance of `Doctrine\DBAL\Event\ConnectionEventArgs` -event arguments instance. - -Doctrine is already shipped with two implementations for the "PostConnect" event: - -* `Doctrine\DBAL\Event\Listeners\OracleSessionInit` allows to specify any number of Oracle Session related enviroment variables that are set right after the connection is established. -* `Doctrine\DBAL\Event\Listeners\MysqlSessionInit` allows to specify the Charset and Collation of the Client Connection if these options are not configured correctly on the MySQL server side. - -You can register events by subscribing them to the `EventManager` instance passed to the Connection factory: - - [php] - $evm = new EventManager(); - $evm->addEventSubscriber(new MysqlSessionInit('UTF8')); - - $conn = DriverManager::getConnection($connectionParams, null, $evm); - - -++ DBAL API - -+++ DBAL Architecture - -The DBAL is seperated into several different packages that perfectly seperate responsibilities of the different RDBMS layers. - -* **Drivers** abstract a PHP specific database API by enforcing two interfaces `\Doctrine\DBAL\Driver\Driver` and `\Doctrine\DBAL\Driver\Statement` which require exactly the same methods as PDO. -* **Platforms** abstract the generation of queries and which database features a platform supports. The `\Doctrine\DBAL\Platforms\AbstractPlatform` defines the common denominator of what a database platform has to publish to the userland, to be fully supportable by Doctrine. This includes the SchemaTool, Transaction Isolation and many other features. The Database platform for MySQL for example can be used by all 3 mysql extensions, PDO, Mysqli and ext/mysql. -* **Logging** holds the interface and some implementations for debugging of Doctrine SQL query execution during a request. -* **Schema** offers an API for each database platform to execute DDL statements against your platform or retrieve metadata about it. It also holds the Schema Abstraction Layer which is used by the different Schema Management facilities of Doctrine DBAL and ORM. -* **Types** offers an abstraction layer for the converting and generation of types between Databases and PHP. - -+++ Data Retrieval and Manipulation - -The following methods exist for executing queries against your configured database, three very generic methods and some advanced retrievial methods: - -* `prepare($sql)` - Prepare a given sql statement and return the `\Doctrine\DBAL\Driver\Statement` instance. -* `executeUpdate($sql, array $params)` - Executes a prepared statement with the given sql and parameters and returns the affected rows count. -* `execute($sql, array $params)` - Creates a prepared statement for the given sql and passes the parameters to the execute method, then returning the statement. -* `fetchAll($sql, array $params)` - Execute the query and fetch all results into an array. -* `fetchArray($sql, array $params)` - Numeric index retrieval of first result row of the given query. -* `fetchBoth($sql, array $params)` - Both numeric and assoc column name retrieval of the first result row. -* `fetchColumn($sql, array $params, $colnum)` - Retrieve only the given column of the first result row. -* `fetchRow($sql, array $params)` - Retrieve assoc row of the first result row. -* `select($sql, $limit, $offset)` - Modify the given query with a limit clause. - -There are also convenience methods for data manipulation queries: - -* `delete($tableName, array $identifier)` - Delete all rows of a table matching the given identifier, where keys are column names. -* `insert($tableName, array $data)` - Insert a row into the given table name using the key value pairs of data. -* `update($tableName, array $data, array $identifier)` - Update all rows for the matching key value identifiers with the given data. - -By default the Doctrine DBAL does no escaping. Escaping is a very tricky business to do automagically, therefore there is none by default. The ORM internally escapes all your values, because it has lots of metadata available about the current context. When you use the Doctrine DBAL as standalone, you have to take care of this yourself. The following methods help you with it: - -* `quote($input, $type=null)` - Quote a value -* `quoteIdentifier($identifier)`- Quote an identifier according to the platform details. - -+++ Transactions - -A `Doctrine\DBAL\Connection` provides a PDO-like API for transaction management, with the -methods `Connection#beginTransaction()`, `Connection#commit()` and `Connection#rollback()`. - -Transaction demarcation with the Doctrine DBAL looks as follows: - - [php] - $conn->beginTransaction(); - try{ - // do stuff - $conn->commit(); - } catch(Exception $e) { - $conn->rollback(); - throw $e; - } - -Alternatively, the control abstraction `Connection#transactional($func)` can be used to make -the code more concise and to make sure you never forget to rollback the transaction in the case -of an exception. The following code snippet is functionally equivalent to the previous one: - - [php] - $conn->transactional(function($conn) { - // do stuff - }); - - -The `Doctrine\DBAL\Connection` also has methods to control the transaction isolation level as supported by the underlying database. -`Connection#setTransactionIsolation($level)` and Connection#getTransactionIsolation() can be used for that purpose. -The possible isolation levels are represented by the following constants: - - [php] - Connection::TRANSACTION_READ_UNCOMMITTED - Connection::TRANSACTION_READ_COMMITTED - Connection::TRANSACTION_REPEATABLE_READ - Connection::TRANSACTION_SERIALIZABLE - -The default transaction isolation level of a `Doctrine\DBAL\Connection` is chosen by the underlying -platform but it is always at least READ_COMMITTED. - - -+++ Transaction Nesting - -A `Doctrine\DBAL\Connection` also adds support for nesting transactions, or rather propagating transaction control up the call stack. -For that purpose, the `Connection` class keeps an internal counter that represents the nesting level and is increased/decreased as -`beginTransaction()`, `commit()` and `rollback()` are invoked. `beginTransaction()` increases the nesting level whilst `commit()`` -and `rollback()` decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1, -`beginTransaction()` is invoked on the underlying driver connection and whenever the nesting level transitions from 1 to 0, -`commit()` or `rollback()` is invoked on the underlying driver, depending on whether the transition was caused by `Connection#commit()` -or `Connection#rollback()`. - -What this means is that transaction control is basically passed to code higher up in the call stack and -the inner transaction block is ignored, with one important exception that is described further below. -Do not confuse this with "real" nested transactions or savepoints. These are not supported by Doctrine. -There is always only a single, real database transaction. - -To visualize what this means in practice, consider the following example: - - [php] - // $conn instanceof Doctrine\DBAL\Connection - $conn->beginTransaction(); // 0 => 1, "real" transaction started - try { - - ... - - // nested transaction block, this might be in some other API/library code that is - // unaware of the outer transaction. - $conn->beginTransaction(); // 1 => 2 - try { - ... - - $conn->commit(); // 2 => 1 - } catch (Exception $e) { - $conn->rollback(); // 2 => 1, transaction marked for rollback only - throw $e; - } - - ... - - $conn->commit(); // 1 => 0, "real" transaction committed - } catch (Exception $e) { - $conn->rollback(); // 1 => 0, "real" transaction rollback - throw $e; - } - -However, **a rollback in a nested transaction block will always mark the current transaction so that the only possible outcome of the transaction is to be rolled back**. -That means in the above example, the rollback in the inner transaction block marks the whole transaction for rollback only. -Even if the nested transaction block would not rethrow the exception, the transaction is marked for rollback only and the commit of -the outer transaction would trigger an exception, leading to the final rollback. -This also means that you can not successfully commit some changes in an outer transaction if an inner transaction block fails and issues a rollback, -even if this would be the desired behavior (i.e. because the nested operation is "optional" for the purpose of the outer transaction block). -To achieve that, you need to restructure your application logic so as to avoid nesting transaction blocks. If this is not possible -because the nested transaction blocks are in a third-party API you're out of luck. - -All that is guaruanteed to the inner transaction is that it still happens atomically, all or nothing, the transaction just gets a wider scope -and the control is handed to the outer scope. - -> *CAUTION* -> The transaction nesting described here is a debated feature that has it's critics. Form your own opinion. -> We recommend avoiding nesting transaction blocks when possible, and most of the time, it is possible. -> Transaction control should mostly be left to a service layer and not be handled in data access objects or similar. - -- - -> **CAUTION** -> Directly invoking `PDO#beginTransaction()`, `PDO#commit()` or `PDO#rollback()` or the -> corresponding methods on the particular `Doctrine\DBAL\Driver\Connection` instance in -> use bybasses the transparent transaction nesting that is provided by -> `Doctrine\DBAL\Connection` and can therefore corrupt the nesting level, causing errors -> with broken transaction boundaries that may be hard to debug. - - -++ Schema Representation - -Doctrine has a very powerful abstraction of database schemas. It offers an object-oriented representation of a database schema with support for all the details of Tables, Sequences, Indexes and Foreign Keys. These Schema instances generate a representation that is equal for all the supported platforms. Internally this functionality is used by the ORM Schema Tool to offer you create, drop and update database schema methods from your Doctrine ORM Metadata model. Up to very specific functionality of your database system this allows you to generate SQL code that makes your Domain model work. - -You will be pleased to hear, that Schema representation is completly decoupled from the Doctrine ORM though, that is you can also use it in any other project to implement database migrations or for SQL schema generation for any metadata model that your application has. You can easily generate a Schema, as a simple example shows: - - [php] - $schema = new \Doctrine\DBAL\Schema\Schema(); - $myTable = $schema->createTable("my_table"); - $myTable->addColumn("id", "integer", array("unsigned" => true)); - $myTable->addColumn("username", "string", array("length" => 32)); - $myTable->setPrimaryKey(array("id")); - $myTable->addUniqueIndex(array("username")); - $schema->createSequence("my_table_seq"); - - $myForeign = $schema->createTable("my_foreign"); - $myForeign->addColumn("id", "integer"); - $myForeign->addColumn("user_id", "integer"); - $myForeign->addForeignKeyConstraint($myTable, array("user_id"), array("id"), array("onUpdate" => "CASCADE")); - - $queries = $schema->toSql($myPlatform); // get queries to create this schema. - $dropSchema = $schema->toDropSql($myPlatform); // get queries to safely delete this schema. - -Now if you want to compare this schema with another schema, you can use the `Comparator` class to get instances of `SchemaDiff`, `TableDiff` and `ColumnDiff`, aswell as information about other foreign key, sequence and index changes. - - [php] - $comparator = new \Doctrine\DBAL\Schema\Comparator(); - $schemaDiff = $comparator->compare($fromSchema, $toSchema); - - $queries = $schemaDiff->toSql($myPlatform); // queries to get from one to another schema. - $saveQueries = $schemaDiff->toSaveSql($myPlatform); - -The Save Diff mode is a specific mode that prevents the deletion of tables and sequences that might occour when making a diff of your schema. This is often necessary when your target schema is not complete but only describes a subset of your application. - -All methods that generate SQL queries for you make much effort to get the order of generation correct, so that no problems will ever occour with missing links of foreign keys. - -++ Platforms - -Platforms abstract query generation and specifics of the RDBMS featuresets. In most cases you don't need to interact with this package alot, but there might be certain cases when you are programming database independent where you want to access the platform to generate queries for you. - -The platform can be accessed from any `Doctrine\DBAL\Connection` instance by calling the `getDatabasePlatform()` method. - -You can use your own platform by specifying the 'platform' key with an instance of your own platform: - - [php] - $myPlatform = new MyPlatform(); - $options = array( - 'driver' => 'pdo_sqlite', - 'path' => 'database.sqlite', - 'platform' => $myPlatform - ); - -This way you can optimize your schema or generated SQL code with features that might not be portable for instance, -however are required for your special needs. - -++ Schema Manager - -A Schema Manager instance helps you with the abstraction of the generation of SQL assets such as Tables, Sequences, Foreign Keys and Indexes. You can use any of the Schema Asset classes `Table`, `Sequence`, `ForeignKeyConstraint` and `Index` for use with the methods of the style `dropAndCreate(AssetName)($asset)`, `drop(AssetName)($asset)` and `create(AssetName)($asset)`. - -You also have methods to retrieve instances of those types from the current database you are connected to. These methods are: - -* `listDatabases()` -* `listSequences()` -* `listTableColumns($tableName)` -* `listTableDetails($tableName)` -* `listTableForeignKeys($tableName)` -* `listTableIndexes($tableName)` -* `listTables()` -* `listUsers()` -* `listViews()` - -For a complete representation of the current database you can use the `createSchema()` method which returns an instance of Schema, which you can use in conjunction with the SchemaTool or Schema Comparator. - -++ Supporting other Databases - -To support a database which is not currently shipped with Doctrine you have to implement the following interfaces and abstract classes: - -* `\Doctrine\DBAL\Driver\Driver` -* `\Doctrine\DBAL\Driver\Statement` -* `\Doctrine\DBAL\Platforms\AbstractPlatform` -* `\Doctrine\DBAL\Schema\AbstractSchemaManager` - -For an already supported platform but unsupported driver you only need to implement the first two interfaces, since the SQL Generation and Schema Management is already supported by the respective platform and schema instances. You can also make use of several Abstract Unittests in the `\Doctrine\Tests\DBAL` package to check if your platform behaves like all the others which is necessary for SchemaTool support, namely: - -* `\Doctrine\Tests\DBAL\Platforms\AbstractPlatformTestCase` -* `\Doctrine\Tests\DBAL\Functional\Schema\AbstractSchemaManagerTestCase` - -We would be very happy if any support for new databases would be contributed back to Doctrine to make it an even better product. From a3b42392b4189b9dfdd80047ae8fea8c76da666d Mon Sep 17 00:00:00 2001 From: Michael Ridgway Date: Fri, 14 May 2010 11:32:12 -0700 Subject: [PATCH 034/430] Fixed broken reference to DBAL section --- manual/en/configuration.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index adb746373..b0f9bd90f 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -177,7 +177,7 @@ Gets or sets whether proxy classes should be generated automatically at runtime The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The DBAL configuration is explained -in the [DBAL section](./../dbal). +in the [DBAL section](./../../../../../dbal/2.0/docs/reference/configuration/en). ++ Change Tracking Policies From f99a096d01bd0324ba641ae7c4831bfc0142f84b Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 14 May 2010 15:23:51 -0400 Subject: [PATCH 035/430] Adding a little more information to the dql chapter. --- manual/en/dql-doctrine-query-language.txt | 339 +++++++++++++++------- 1 file changed, 240 insertions(+), 99 deletions(-) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index fe96c7d47..233b46577 100755 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -2,17 +2,16 @@ DQL stands for **D**octrine **Q**uery **L**anguage and is an Object Query Language derivate that is very similar to the **H**ibernate **Q**uery **L**anguage (HQL) or the **J**ava **P**ersistence **Q**uery **L**anguage (JPQL). -In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects. +In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects. > **CAUTION** > A common mistake for beginners is to mistake DQL for being just some form of SQL > and therefore trying to use table names and column names or join arbitrary tables > together in a query. You need to think about DQL as a query language for your object -> model, not for your relational schema. +> model, not for your relational schema. DQL is case in-sensitive, except for namespace, class and field names, which are case sensitive. - ++ Types of DQL queries DQL as a query language has SELECT, UPDATE and DELETE constructs that map to their corresponding @@ -21,7 +20,7 @@ have to be introduced into the persistence context through `EntityManager#persis consistency of your object model. DQL SELECT statements are a very powerful way of retrieving parts of your domain model that are -not accessible via assocations. Additionally they allow to retrieve entities and their associations +not accessible via associations. Additionally they allow to retrieve entities and their associations in one single sql select statement which can make a huge difference in performance in contrast to using several queries. @@ -37,9 +36,10 @@ The select clause of a DQL query specifies what appears in the query result. The Here is an example that selects all users with an age > 20: - [sql] - SELECT u FROM MyProject\Model\User u WHERE u.age > 20 - + [php] + $query = $em->createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20'); + $users = $query->getResult(); + Lets examine the query: * `u` is a so called identification variable or alias that refers to the `MyProject\Model\User` class. By placing this alias in the SELECT clause we specify that we want all instances of the User class that are matched by this query appear in the query result. @@ -52,7 +52,7 @@ The SELECT clause allows to specify both class identification variables that sig of a complete entity class or just fields of the entity using the syntax `u.name`. Combinations of both are also allowed and it is possible to wrap both fields and identification values into aggregation and DQL functions. Numerical fields can -be part of computations using mathematical operatos. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn) +be part of computations using mathematical operations. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn) on more information. +++ Joins @@ -69,21 +69,23 @@ Example: Regular join of the address: - [sql] - SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin' + [php] + $query = $em->createQuery("SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); + $users = $query->getResult(); Fetch join of the address: - [sql] - SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin' + [php] + $query = $em->createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); + $users = $query->getResult(); When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the root level of the result array. In the previous example an array of User instances is returned and the address of each user is fetched and hydrated into the `User#address` variable. If you access -the address Doctrine does not need to +the address Doctrine does not need to lazy load the association with another query. > **NOTE** -> Doctrine allows you to walk all the assocations between all the objects in your domain model. +> Doctrine allows you to walk all the associations between all the objects in your domain model. > Objects that were not already loaded from the database are replaced with lazy load proxy instances. > Non-loaded Collections are also replaced by lazy-load instances that fetch all the contained objects upon > first access. However relying on the lazy-load mechanism leads to many small queries executed @@ -99,95 +101,194 @@ Named parameters are specified with ":name1", ":name2" and so on. +++ DQL SELECT Examples -This section contains a large set of DQL queries and some explainations of what is happening. +This section contains a large set of DQL queries and some explanations of what is happening. The actual result also depends on the hydration mode. - [sql] - -- Hydrate all Users - SELECT u FROM MyProject\Model\User u +Hydrate all User entities: - -- Retrieve the IDs of all CmsUsers - SELECT u.id FROM CmsUser u + [php] + $query = $em->createQuery('SELECT u FROM MyProject\Model\User u'); + $users = $query->getResult(); // array of User objects - -- Retrieve the IDs of all users that have written an article - SELECT DISTINCT a.user.id FROM CmsArticle a +Retrieve the IDs of all CmsUsers: - -- Retrieve all articles and sort them by the name of the articles users instance - SELECT a FROM CmsArticle a ORDER BY a.user.name ASC + [php] + $query = $em->createQuery('SELECT u.id FROM CmsUser u'); + $ids = $query->getResult(); // array of CmsUser ids - -- Retrieve the Username and Name of a CmsUser - SELECT u.username, u.name FROM CmsUser u +Retrieve the IDs of all users that have written an article: - -- Retrieve a ForumUser and his single associated entity - SELECT u, a FROM ForumUser u JOIN u.avatar a + [php] + $query = $em->createQuery('SELECT DISTINCT a.user.id FROM CmsArticle a'); + $ids = $query->getResult(); // array of CmsUser ids - -- Retrieve a CmsUser and fetch join all the phonenumbers he has - SELECT u, p FROM CmsUser u JOIN u.phonenumbers p +Retrieve all articles and sort them by the name of the articles users instance: - -- Hydrate a result in Ascending or Descending Order - SELECT u FROM ForumUser u ORDER BY u.id ASC - SELECT u FROM ForumUser u ORDER BY u.id DESC + [php] + $query = $em->createQuery('SELECT a FROM CmsArticle a ORDER BY a.user.name ASC'); + $articles = $query->getResult(); // array of CmsArticle objects - -- Using Aggregate Functions - SELECT COUNT(u.id) FROM CmsUser u GROUP BY u.id +Retrieve the Username and Name of a CmsUser: - -- With WHERE Clause and Positional Parameter - SELECT u FROM ForumUser u WHERE u.id = ?1 + [php] + $query = $em->createQuery('SELECT u.username, u.name FROM CmsUser u'); + $users = $query->getResults(); // array of CmsUser username and id values + echo $users[0]['username']; - -- With WHERE Clause and Named Parameter - SELECT u FROM ForumUser u WHERE u.username = :name +Retrieve a ForumUser and his single associated entity: - -- With Nested Conditions in WHERE Clause - SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id + [php] + $query = $em->createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a'); + $users = $query->getResult(); // array of ForumUser objects with the avatar association loaded + echo get_class($users[0]->getAvatar()); - -- With COUNT DISTINCT - SELECT COUNT(DISTINCT u.name) FROM CmsUser +Retrieve a CmsUser and fetch join all the phonenumbers he has: - -- With Arithmetic Expression in WHERE clause - SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000 + [php] + $query = $em->createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p'); + $users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded + $phonenumbers = $users[0]->getPhonenumbers(); - -- Using multiple classes fetched using a FROM clause (all returned on the root level of the result) - SELECT u, a FROM CmsUser u, CmsArticle a WHERE u.id = a.user.id +Hydrate a result in Ascending: - -- Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids - SELECT u.id, a.id FROM CmsUser u LEFT JOIN u.articles a + [php] + $query = $em->createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC'); + $users = $query->getResult(); // array of ForumUser objects - -- Restricting a JOIN clause by additional conditions - SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%' +Or in Descending Order: - -- Using several Fetch JOINs - SELECT u, a, p, c FROM CmsUser u - JOIN u.articles a - JOIN u.phonenumbers p - JOIN a.comments c + [php] + $query = $em->createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC'); + $users = $query->getResult(); // array of ForumUser objects - -- BETWEEN in WHERE clause - SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2 +Using Aggregate Functions: - -- DQL Functions in WHERE clause - SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone' + [php] + $query = $em->createQuery('SELECT COUNT(u.id) FROM Entities\User u'); + $count = $query->getSingleScalarResult(); - -- IN() Expression - SELECT u.name FROM CmsUser u WHERE u.id IN(46) - SELECT u FROM CmsUser u WHERE u.id IN (1, 2) - SELECT u FROM CmsUser u WHERE u.id NOT IN (1) +With WHERE Clause and Positional Parameter: - -- CONCAT() DQL Function - SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1 - SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1 + [php] + $query = $em->createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1'); + $users = $query->getResult(); // array of ForumUser objects - -- EXISTS in WHERE clause with correlated Subquery - SELECT u.id FROM CmsUser u - WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.phonenumber = u.id) +With WHERE Clause and Named Parameter: - -- Get all users who are members of $group. - SELECT u.id FROM CmsUser u WHERE :param MEMBER OF u.groups + [php] + $query = $em->createQuery('SELECT u FROM ForumUser u WHERE u.username = :name'); + $users = $query->getResult(); // array of ForumUser objects - -- Get all users that have more than 1 phonenumber - SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1 +With Nested Conditions in WHERE Clause: - -- Get all users that have no phonenumber - SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY + [php] + $query = $em->createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id'); + $users = $query->getResult(); // array of ForumUser objects + +With COUNT DISTINCT: + + [php] + $query = $em->createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser + $users = $query->getResult(); // array of ForumUser objects + +With Arithmetic Expression in WHERE clause: + + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); + $users = $query->getResult(); // array of ForumUser objects + +Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids: + + [php] + $query = $em->createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a'); + $results = $query->getResult(); // array of user ids and every article_id for each user + +Restricting a JOIN clause by additional conditions: + + [php] + $query = $em->createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%'"); + $users = $query->getResult(); + +Using several Fetch JOINs: + + [php] + $query = $em->createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c'); + $users = $query->getResult(); + +BETWEEN in WHERE clause: + + [php] + $query = $em->createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2'); + $usernames = $query->getResult(); + +DQL Functions in WHERE clause: + + [php] + $query = $em->createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'"); + $usernames = $query->getResult(); + +IN() Expression: + + [php] + $query = $em->createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)'); + $usernames = $query->getResult(); + + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id IN (1, 2)'); + $users = $query->getResult(); + + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id NOT IN (1)'); + $users = $query->getResult(); + +CONCAT() DQL Function: + + [php] + $query = $em->createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); + $ids = $query->getResult(); + + $query = $em->createQuery('SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1'); + $idUsernames = $query->getResult(); + +EXISTS in WHERE clause with correlated Subquery + + [php] + $query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.phonenumber = u.id)'); + $ids = $query->getResult(); + +Get all users who are members of $group. + + [php] + $query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE :param MEMBER OF u.groups'); + $ids = $query->getResult(); + +Get all users that have more than 1 phonenumber + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1'); + $users = $query->getResult(); + +Get all users that have no phonenumber + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); + $users = $query->getResult(); + +++++ Partial Object Syntax + +By default when you run a DQL query in Doctrine and select only a subset of the +fields for a given entity, you do not receive objects back. Instead, you receive +only arrays as a flat rectangular result set, similar to how you would if you +were just using SQL directly and joining some data. + +If you want to select partial objects you can use the `partial` DQL keyword: + + [php] + $query = $em->createQuery('SELECT partial u.{id, username} FROM CmsUser u'); + $users = $query->getResult(); // array of partially loaded CmsUser objects + +You use the partial syntax when joining as well: + + [php] + $query = $em->createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a'); + $users = $query->getResult(); // array of partially loaded CmsUser objects +++ Using INDEX BY @@ -357,14 +458,13 @@ An instance of the `Doctrine\ORM\Query` class represents a DQL query. You create [php] // $em instanceof EntityManager - + // example1: passing a DQL string $q = $em->createQuery('select u from MyProject\Model\User u'); - + // example2: usin setDql $q = $em->createQuery(); $q->setDql('select u from MyProject\Model\User u'); - +++ Query Result Formats @@ -397,7 +497,7 @@ A pure result usually looks like this: [1] => Object [2] => Object ... - + A mixed result on the other hand has the following general structure: array @@ -431,30 +531,78 @@ Here is how the result could look like: ... And here is how you would access it in PHP code: - + [php] foreach ($results as $row) { echo "Name: " . $row[0]->getName(); echo "Name UPPER: " . $row['nameUpper']; } - + You may have observed that in a mixed result, the object always ends up on index 0 of a result row. -+++ Hydration Mode Asumptions ++++ Hydration Modes -Each of the Hydration Modes makes assumptions about how the result is returned to userland. You should +Each of the Hydration Modes makes assumptions about how the result is returned to user land. You should know about all the details to make best use of the different result formats: +The constants for the different hydration modes are: + +* Query::HYDRATE_OBJECT +* Query::HYDRATE_ARRAY +* Query::HYDRATE_SCALAR +* Query::HYDRATE_SINGLE_SCALAR + ++++ Object Hydration +Object hydration hydrates the result set into the object graph: + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_OBJECT); + ++++ Array Hydration -++++ Scalar Hydration Details +You can run the same query with array hydration and the result set is hydrated into +an array that represents the object graph: + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_ARRAY); + +You can use the `getArrayResult()` shortcut as well: + + [php] + $users = $query->getArrayResult(); + +++++ Scalar Hydration + +If you want to return a flat rectangular result set instead of an object graph +you can use scalar hydration: + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_SCALAR); + echo $users[0]['u_id']; The following assumptions are made about selected fields using Scalar Hydration: 1. Fields from classes are prefixed by the DQL alias in the result. A query of the kind 'SELECT u.name ..' returns a key 'u_name' in the result rows. +++++ Single Scalar Hydration + +If you a query which returns just a single scalar value you can use single scalar +hydration: + + [php] + $query = $em->createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); + $query->setParameter(1, 'jwage'); + $numArticles = $query->getResult(Query::HYDRATE_SCALAR); + +You can use the `getSingleScalarResult()` shortcut as well: + + [php] + $numArticles = $query->getSingleScalarResult(); + +++ Iterating Large Resultsets There are situations when a query you want to execute returns a very large result-set that needs @@ -583,7 +731,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement -+++ Statements ++++ Statements SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] UpdateStatement ::= UpdateClause [WhereClause] @@ -736,12 +884,12 @@ The following context-free grammar, written in an EBNF variant, describes the Do ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression BooleanPrimary | CaseExpression | EntityTypeExpression - CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | + CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression - GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression + GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" - WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression - SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* + WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" CaseOperand ::= StateFieldPathExpression | TypeDiscriminator SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression @@ -761,7 +909,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" - + +++ Other Expressions QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS @@ -793,11 +941,4 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | "LOWER" "(" StringPrimary ")" | - "UPPER" "(" StringPrimary ")" - - - - - - - + "UPPER" "(" StringPrimary ")" \ No newline at end of file From 5c79ca25aabaf574d71d6bc622c13fd6045626d2 Mon Sep 17 00:00:00 2001 From: Christian Heinrich Date: Sat, 15 May 2010 00:40:18 +0200 Subject: [PATCH 036/430] Updated formatting and fixed mistakes --- manual/en/query-builder.txt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/manual/en/query-builder.txt b/manual/en/query-builder.txt index 9c8c95219..b07bf6d79 100644 --- a/manual/en/query-builder.txt +++ b/manual/en/query-builder.txt @@ -76,7 +76,7 @@ This method is the responsable to build every piece of DQL. It takes 3 parameter Doctrine supports dynamic binding of parameters to your query, similar to preparing queries. You can use both strings and numbers as placeholders, although both have a slightly different syntax. Additionally, you must make your choice: Mixing both styles is not allowed. Binding parameters can simply be achieved as follows: - [php] + [php] // $qb instanceof QueryBuilder // example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support @@ -84,12 +84,12 @@ Doctrine supports dynamic binding of parameters to your query, similar to prepar ->add('from', 'User u') ->add('where', 'u.id = ?1') ->add('orderBy', 'u.name ASC'); - ->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100 + ->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100 You are not forced to enumerate your placeholders as the alternative syntax is available: - [php] + [php] // $qb instanceof QueryBuilder // example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support @@ -97,28 +97,28 @@ You are not forced to enumerate your placeholders as the alternative syntax is a ->add('from', 'User u') ->add('where', 'u.id = :identifier') ->add('orderBy', 'u.name ASC'); - ->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100 + ->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100 Note that numeric placeholders start with a ? followed by a number while the named placeholders start with a : followed by a string. If you've got several parameters to bind to your query, you can also use setParameters() instead of setParameter() with the following syntax: [php] - // $qb instanceof QueryBuilder + // $qb instanceof QueryBuilder // Query here... - $qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2')); + $qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2')); Getting already bound parameters is easy - simply use the abovementioned syntax with "getParameter()" or "getParameters()": [php] - // $qb instanceof QueryBuilder + // $qb instanceof QueryBuilder // See example above - $params = qb->getParameters(array(1, 2)); - // Equivalent to - $param = array($qb->getParameter(1), $qb->getParameter(2)); + $params = qb->getParameters(array(1, 2)); + // Equivalent to + $param = array($qb->getParameter(1), $qb->getParameter(2)); Note: If you try to get a parameter that was not bound yet, getParameter() simply returns NULL. @@ -247,8 +247,8 @@ Here it is a complete list of supported helper methods available: public function not($restriction); // Returns Expr\Func instance // Example - $qb->expr()->in('u.id', array(1, 2, 3)) - // Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception. - // Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above) + // Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception. + // Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above) public function in($x, $y); // Returns Expr\Func instance // Example - $qb->expr()->notIn('u.id', '2') From 72c2432586638595b2d741b24e6290ad5f6fdbbe Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 May 2010 11:27:49 +0200 Subject: [PATCH 037/430] DDC-590 - Added docs on Locking Support --- manual/en/transactions-and-concurrency.txt | 123 ++++++++++++++++++++- 1 file changed, 118 insertions(+), 5 deletions(-) diff --git a/manual/en/transactions-and-concurrency.txt b/manual/en/transactions-and-concurrency.txt index 7d046029f..9e85a9cba 100644 --- a/manual/en/transactions-and-concurrency.txt +++ b/manual/en/transactions-and-concurrency.txt @@ -1,8 +1,14 @@ ++ Transaction Demarcation -Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very important because if not done properly it can negatively affect the performance of your application. Many databases and database abstraction layers like PDO by default operate in auto-commit mode, which means that every single SQL statement is wrapped in a small transaction. Without any explicit transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap. +Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very +important because if not done properly it can negatively affect the performance of your application. +Many databases and database abstraction layers like PDO by default operate in auto-commit mode, +which means that every single SQL statement is wrapped in a small transaction. Without any explicit +transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap. -For the most part, Doctrine 2 already takes care of proper transaction demarcation for you: All the write operations (INSERT/UPDATE/DELETE) are queued until `EntityManager#flush()` is invoked which wraps all of these changes in a single transaction. +For the most part, Doctrine 2 already takes care of proper transaction demarcation for you: All the write +operations (INSERT/UPDATE/DELETE) are queued until `EntityManager#flush()` is invoked which wraps all +of these changes in a single transaction. However, Doctrine 2 also allows (and ecourages) you to take over and control transaction demarcation yourself. @@ -92,12 +98,25 @@ accurate. If you intend to start another unit of work after an exception has occured you should do that with a new `EntityManager`. +++ Locking Support -++ Optimistic Locking +Doctrine 2 offers support for Pessimistic- and Optimistic-locking strategies natively. This allows to take +very fine-grained control over what kind of locking is required for your Entities in your application. -Database transactions are fine for concurrency control during a single request. However, a database transaction should not span across requests, the so-called "user think time". Therefore a long-running "business transaction" that spans multiple requests needs to involve several database transactions. Thus, database transactions alone can no longer control concurrency during such a long-running business transaction. Concurrency control becomes the partial responsibility of the application itself. ++++ Optimistic Locking -Doctrine has integrated support for automatic optimistic locking via a version field. In this approach any entity that should be protected against concurrent modifications during long-running business transactions gets a version field that is either a simple number (mapping type: integer) or a timestamp (mapping type: datetime). When changes to such an entity are persisted at the end of a long-running conversation the version of the entity is compared to the version in the database and if they dont match, an `OptimisticLockException` is thrown, indicating that the entity has been modified by someone else already. +Database transactions are fine for concurrency control during a single request. However, a database transaction +should not span across requests, the so-called "user think time". Therefore a long-running "business transaction" +that spans multiple requests needs to involve several database transactions. Thus, database transactions alone +can no longer control concurrency during such a long-running business transaction. Concurrency control becomes +the partial responsibility of the application itself. + +Doctrine has integrated support for automatic optimistic locking via a version field. In this approach any entity +that should be protected against concurrent modifications during long-running business transactions gets a version +field that is either a simple number (mapping type: integer) or a timestamp (mapping type: datetime). When changes +to such an entity are persisted at the end of a long-running conversation the version of the entity is compared to +the version in the database and if they dont match, an `OptimisticLockException` is thrown, indicating that the +entity has been modified by someone else already. You designate a version field in an entity as follows. In this example we'll use an integer. @@ -130,7 +149,101 @@ and the active transaction rolled back (or marked for rollback). This exception Potential responses to an OptimisticLockException are to present the conflict to the user or to refresh or reload objects in a new transaction and then retrying the transaction. +With PHP promoting a share-nothing architecture, the time between showing an update form and actually modifying the entity can in the worst scenario be +as long as your applications session timeout. If changes happen to the entity in that time frame you want to know directly +when retrieving the entity that you will hit an optimistic locking exception: +You can always verify the version of an entity during a request either when calling `EntityManager#find()`: + [php] + use Doctrine\DBAL\LockMode; + use Doctrine\ORM\OptimisticLockException; + $theEntityId = 1; + $expectedVersion = 184; + + try { + $entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion); + + // do the work + + $em->flush(); + } catch(OptimisticLockException $e) { + echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; + } + +Or you can use `EntityManager#lock()` to find out: + + [php] + use Doctrine\DBAL\LockMode; + use Doctrine\ORM\OptimisticLockException; + + $theEntityId = 1; + $expectedVersion = 184; + + $entity = $em->find('User', $theEntityId); + + try { + // assert version + $em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion); + + } catch(OptimisticLockException $e) { + echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; + } + +++++ Important Implementation Notes + +You can easily get the optimistic locking workflow wrong if you compare the wrong versions. +Say you have Alice and Bob accessing a hypothetical bank account: + +* Alice reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request) +* Bob reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request) +* Bob updates the headline to "Bar", upgrading the optimistic lock version to 2 (POST Request of a Form) +* Alice updates the headline to "Baz", ... (POST Request of a Form) + +Now at the last stage of this scenario the blog post has to be read again from the database before +Alice's headline can be applied. At this point you will want to check if the blog post is still at version 1 +(which it is not in this scenario). + +Using optimistic locking correctly, you *have* to add the version as an additional hidden field +(or into the SESSION for more safety). Otherwise you cannot verify the version is still the one being originally read from +the database when Alice performed her GET request for the blog post. If this happens you might +see lost updates you wanted to prevent with Optimistic Locking. + +See the example code, The form (GET Request): + + [php] + $post = $em->find('BlogPost', 123456); + + echo ''; + echo ''; + +And the change headline action (POST Request): + + [php] + $postId = (int)$_GET['id']; + $postVersion = (int)$_GET['version']; + + $post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion); + ++++ Pessimistic Locking + +Doctrine 2 supports Pessimistic Locking at the database level. No attempt is being made to implement pessimistic locking +inside Doctrine, rather vendor-specific and ANSI-SQL commands are used to aquire row-level locks. Every Entity can +be part of a pessimistic lock, there is no special metadata required to use this feature. + +However for Pessimistic Locking to work you have to disable the Auto-Commit Mode of your Database and start a +transaction around your pessimistic lock use-case using the "Approach 2: Explicit Transaction Demarcation" described +above. Doctrine 2 will throw an Exception if you attempt to aquire an pessimistic lock and no transaction is running. + +Doctrine 2 currently supports two pessimistic lock modes: + +* Pessimistic Write (`Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE`), locks the underlying database rows for concurrent Read and Write Operations. +* Pessimistic Read (`Doctrine\DBAL\LockMode::PESSIMISTIC_READ`), locks other concurrent requests that attempt to update or lock rows in write mode. + +You can use pessimistic locks in three different scenarios: + +1. Using `EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)` or `EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)` +2. Using `EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)` or `EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)` +3. Using `Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)` or `Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)` From 9231f54313ac5acd0b7077b752a5b4b94607277d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 16 May 2010 09:50:07 +0200 Subject: [PATCH 038/430] DDC-153 - Added cookbook entry contributed by merk (thanks!) --- cookbook/en.txt | 3 +- cookbook/en/sql-table-prefixes.txt | 51 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 cookbook/en/sql-table-prefixes.txt diff --git a/cookbook/en.txt b/cookbook/en.txt index db1b17496..cb71d12e7 100644 --- a/cookbook/en.txt +++ b/cookbook/en.txt @@ -5,4 +5,5 @@ + Implementing wakeup or clone + Integrating with CodeIgniter + DQL Custom Walkers -+ DQL User Defined Functions \ No newline at end of file ++ DQL User Defined Functions ++ SQL Table Prefixes \ No newline at end of file diff --git a/cookbook/en/sql-table-prefixes.txt b/cookbook/en/sql-table-prefixes.txt new file mode 100644 index 000000000..eb7929959 --- /dev/null +++ b/cookbook/en/sql-table-prefixes.txt @@ -0,0 +1,51 @@ +This recipe is intended as an example of implementing a loadClassMetadata listener to provide a Table Prefix option for your application. The method used below is not a hack, but fully integrates into the Doctrine system, all SQL generated will include the appropriate table prefix. + +In most circumstances it is desirable to separate different applications into individual databases, but in certain cases, it may be beneficial to have a table prefix for your Entities to separate them from other vendor products in the same database. + +++ Implementing the listener + +The listener in this example has been set up with the DoctrineExtensions namespace. You create this file in your library/DoctrineExtensions directory, but will need to set up appropriate autoloaders. + + [php] + _prefix = (string) $prefix; + } + + public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) + { + $classMetadata = $eventArgs->getClassMetadata(); + $classMetadata->setTableName($this->_prefix . $classMetadata->getTableName()); + } + } + +++ Telling the EntityManager about our listener + +A listener of this type must be set up before the EntityManager has been initialised, otherwise an Entity might be created or cached before the prefix has been set. + +> **Note** +> If you set this listener up, be aware that you will need to clear your caches +> and drop then recreate your database schema. + + [php] + addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix); + + $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm); + From 18b9c6cd699184af437e6a5fc134cd6057cda7c7 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Tue, 18 May 2010 11:49:44 -0400 Subject: [PATCH 039/430] Updating documentation for converting mapping information and reverse engineering databases to entities. --- manual/en/tools.txt | 74 ++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/manual/en/tools.txt b/manual/en/tools.txt index 31109b7f6..991b014c0 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -178,71 +178,63 @@ Before using the orm:schema-tool commands, remember to configure your cli-config ++ Convert Mapping Information -Doctrine comes with some special tools for working with the various supported -formats for specifying mapping information. - -You have the ability to convert from a few different sources. - -* An existing database -* A directory of YAML schema files -* A directory of XML schema files -* A directory of PHP scripts which populate `ClassMetadataInfo` instances -* A directory of PHP classes defining Doctrine entities with annotations - -To convert a mapping source you can do everything you need with the `ClassMetadataExporter`. +To convert some mapping information between the various supported formats you can +use the `ClassMetadataExporter` to get exporter instances for the different formats: [php] $cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter(); -Once you have an instance you can start adding mapping sources to convert. +Once you have a instance you can use it to get an exporter. For example, the yml +exporter: [php] - $cme->addMappingSource('/path/to/yml', 'yml'); - $cme->addMappingSource('/path/to/xml', 'xml'); - $cme->addMappingSource('/path/to/php', 'php'); - $cme->addMappingSource('/path/to/annotations', 'annotation'); - -Now to convert the added mapping sources you can do so by using the exporter drivers. - - [php] - $metadatas = $cme->getMetadatasForMappingSources(); - $exporter = $cme->getExporter('yml', '/path/to/export/yml'); - $exporter->setMetadatas($metadatas); + +Now you can export some `ClassMetadata` instances: + + $classes = array( + $em->getClassMetadata('Entities\User'), + $em->getClassMetadata('Entities\Profile') + ); + $exporter->setMetadata($classes); $exporter->export(); -This functionality functionality is also available from the command line to for -example convert some YAML mapping files to XML. +This functionality is also available from the command line to convert your +loaded mapping information to another format. The `orm:convert-mapping` command +accepts two arguments, the type to convert to and the path to generate it: - $ php doctrine orm:convert-mapping /path/to/mapping-path xml /path/to/mapping-path-converted-to-xml - -It is even possible to define more than one path as source: - - $ php doctrine orm:convert-mapping --from /path/to/mapping-path1 --from /path/to/mapping-path2 /path/to/mapping-path3 xml /path/to/mapping-path-converted-to-xml + $ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml ++ Reverse Engineering -You can use the same `ClassMetadataExporter` to reverse engineer a database and -generate YAML, XML, etc. from your existing databases. +You can use the `DatabaseDriver` to reverse engineer a database to an array of +`ClassMetadataInfo` instances and generate YAML, XML, etc. from them. + +First you need to retrieve the metadata instances with the `DatabaseDriver`: [php] - $sm = $em->getConnection()->getSchemaManager(); + $em->getConfiguration()->setMetadataDriverImpl( + new \Doctrine\ORM\Mapping\Driver\DatabaseDriver( + $em->getConnection()->getSchemaManager() + ) + ); - $cme->addMappingSource($sm, 'database'); - $metadatas = $cme->getMetadatasForMappingSources(); + $cmf = new DisconnectedClassMetadataFactory($em); + $metadata = $cmf->getAllMetadata(); + +Now you can get an exporter instance and export the loaded metadata to yml: $exporter = $cme->getExporter('yml', '/path/to/export/yml'); - $exporter->setMetadatas($metadatas); + $exporter->setMetadata($metadatas); $exporter->export(); -From the command line it is very simple to do something like reverse engineer -your existing database to set of YAML mapping files. +You can also reverse engineer a database using the `orm:convert-mapping` command: - $ php doctrine orm:convert-mapping database yml /path/to/mapping-path-converted-to-yml + $ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml > **CAUTION** > Reverse Engineering is not always working perfectly depending on special cases. > It will only detect Many-To-One relations (even if they are One-To-One) and > will try to create entities from Many-To-Many tables. It also has problems > with naming of foreign keys that have multiple column names. Any Reverse Engineered -> Database-Schema needs considerable manual work to become a useful domain model. +> Database-Schema needs considerable manual work to become a useful domain model. \ No newline at end of file From 0698a8c4553c982d153540d8ff20bb97f30b1a04 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Tue, 18 May 2010 11:51:49 -0400 Subject: [PATCH 040/430] Fixing php code --- manual/en/tools.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manual/en/tools.txt b/manual/en/tools.txt index 991b014c0..22321bbe4 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -192,6 +192,7 @@ exporter: Now you can export some `ClassMetadata` instances: + [php] $classes = array( $em->getClassMetadata('Entities\User'), $em->getClassMetadata('Entities\Profile') @@ -224,6 +225,7 @@ First you need to retrieve the metadata instances with the `DatabaseDriver`: Now you can get an exporter instance and export the loaded metadata to yml: + [php] $exporter = $cme->getExporter('yml', '/path/to/export/yml'); $exporter->setMetadata($metadatas); $exporter->export(); From c34b5ad1a7b0d7af90efce328b54c40f1f2d7e5e Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Sat, 22 May 2010 13:52:20 +0200 Subject: [PATCH 041/430] Added strategy cookbook entry to the index. --- cookbook/en.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cookbook/en.txt b/cookbook/en.txt index cb71d12e7..4f059126e 100644 --- a/cookbook/en.txt +++ b/cookbook/en.txt @@ -6,4 +6,5 @@ + Integrating with CodeIgniter + DQL Custom Walkers + DQL User Defined Functions -+ SQL Table Prefixes \ No newline at end of file ++ SQL Table Prefixes ++ Strategy Cookbook Introduction \ No newline at end of file From e127274aa5056c05e99ad780c5ff1294bd968156 Mon Sep 17 00:00:00 2001 From: Rich Bowen Date: Mon, 24 May 2010 15:34:58 -0400 Subject: [PATCH 042/430] Fixes missing quote. --- manual/en/configuration.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index b0f9bd90f..da2714617 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -33,7 +33,7 @@ The following example shows the setup of a `ClassLoader` // test.php require '/path/to/lib/Doctrine/Common/ClassLoader.php'; - $classLoader = new \Doctrine\Common\ClassLoader('Doctrine', '/path/to/Doctrine2/lib); + $classLoader = new \Doctrine\Common\ClassLoader('Doctrine', '/path/to/Doctrine2/lib'); $classLoader->register(); // register on SPL autoload stack For best class loading performance it is recommended that you keep your include_path short, ideally it should only contain the path to the PEAR libraries, and any other class libraries should be registered with their full base path. @@ -410,4 +410,4 @@ based on namespaces: Based on the namespace of the entity the loading of entities is delegated to the appropriate driver. The chain semantics come from the fact that the driver loops through all namespaces and matches the entity class name against the namespace using a `strpos() === 0` call. This means you need to order the drivers correctly if -sub-namespaces use different metadata driver implementations. \ No newline at end of file +sub-namespaces use different metadata driver implementations. From 30b9cfce3d04bbf1389c6f207932685be547bbac Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Tue, 25 May 2010 13:38:16 -0400 Subject: [PATCH 043/430] [DDC-540] Doc improvement --- manual/en/architecture.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/en/architecture.txt b/manual/en/architecture.txt index 45f10d160..7d2452fde 100644 --- a/manual/en/architecture.txt +++ b/manual/en/architecture.txt @@ -3,8 +3,8 @@ Doctrine 2. It is recommended to read this chapter carefully. ++ Entities -An entity is a lightweight persistent domain object. An entity can be any regular -php class that obeys to the following restrictions: +An entity is a lightweight, persistent domain object. An entity can be any regular +PHP class observing the following restrictions: * An entity class must not be final or contain final methods. * An entity class must not implement `__clone` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). From 37124c4e0aacf3fc898a02be4ff9dcfe83331a47 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 2 Jun 2010 23:41:41 -0400 Subject: [PATCH 044/430] Adding section for custom hydrators. --- manual/en/dql-doctrine-query-language.txt | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index 233b46577..ac0c48c39 100755 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -603,6 +603,34 @@ You can use the `getSingleScalarResult()` shortcut as well: [php] $numArticles = $query->getSingleScalarResult(); +++++ Custom Hydration Modes + +You can easily add your own custom hydration modes by first creating a class which extends `AbstractHydrator`: + + [php] + namespace MyProject\Hydrators; + + use Doctrine\ORM\Internal\Hydration\AbstractHydrator; + + class CustomHydrator extends AbstractHydrator + { + protected function _hydrateAll() + { + return $this->_stmt->fetchAll(PDO::FETCH_ASSOC); + } + } + +Next you just need to add the class to the ORM configuration: + + [php] + $em->getConfiguration()->addHydrator('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); + +Now the hydrator is ready to be used in your queries: + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u'); + $results = $query->getResult('CustomHydrator'); + +++ Iterating Large Resultsets There are situations when a query you want to execute returns a very large result-set that needs From 9d2f7f0686bbf24962697cb563675838af9a0b71 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Thu, 3 Jun 2010 14:13:39 -0400 Subject: [PATCH 045/430] Updating documentation for custom hydrators. --- manual/en/dql-doctrine-query-language.txt | 1942 ++++++++++----------- 1 file changed, 971 insertions(+), 971 deletions(-) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index ac0c48c39..a4b66bf2c 100755 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -1,972 +1,972 @@ -++ DQL Explained - -DQL stands for **D**octrine **Q**uery **L**anguage and is an Object Query Language derivate that is very similar to the **H**ibernate **Q**uery **L**anguage (HQL) or the **J**ava **P**ersistence **Q**uery **L**anguage (JPQL). - -In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects. - -> **CAUTION** -> A common mistake for beginners is to mistake DQL for being just some form of SQL -> and therefore trying to use table names and column names or join arbitrary tables -> together in a query. You need to think about DQL as a query language for your object -> model, not for your relational schema. - -DQL is case in-sensitive, except for namespace, class and field names, which are case sensitive. - -++ Types of DQL queries - -DQL as a query language has SELECT, UPDATE and DELETE constructs that map to their corresponding -SQL statement types. INSERT statements are not allowed in DQL, because entities and their relations -have to be introduced into the persistence context through `EntityManager#persist()` to ensure -consistency of your object model. - -DQL SELECT statements are a very powerful way of retrieving parts of your domain model that are -not accessible via associations. Additionally they allow to retrieve entities and their associations -in one single sql select statement which can make a huge difference in performance in contrast -to using several queries. - -DQL UPDATE and DELETE statements offer a way to execute bulk changes on the entities of your -domain model. This is often necessary when you cannot load all the affected entities of a bulk -update into memory. - -++ SELECT queries - -+++ DQL SELECT clause - -The select clause of a DQL query specifies what appears in the query result. The composition of all the expressions in the select clause also influences the nature of the query result. - -Here is an example that selects all users with an age > 20: - - [php] - $query = $em->createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20'); - $users = $query->getResult(); - -Lets examine the query: - -* `u` is a so called identification variable or alias that refers to the `MyProject\Model\User` class. By placing this alias in the SELECT clause we specify that we want all instances of the User class that are matched by this query appear in the query result. -* The FROM keyword is always followed by a fully-qualified class name which in turn is followed by an identification variable or alias for that class name. This class designates a root of our query from which we can navigate further via joins (explained later) and path expressions. -* The expression `u.age` in the WHERE clause is a path expression. Path expressions in DQL are easily identified by the use of the '.' operator that is used for constructing paths. The path expression `u.age` refers to the `age` field on the User class. - -The result of this query would be a list of User objects where all users are older than 20. - -The SELECT clause allows to specify both class identification variables that signal the hydration -of a complete entity class or just fields of the entity using the syntax `u.name`. -Combinations of both are also allowed and it is possible to wrap both fields and -identification values into aggregation and DQL functions. Numerical fields can -be part of computations using mathematical operations. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn) -on more information. - -+++ Joins - -A SELECT query can contain joins. There are 2 types of JOINs: "Regular" Joins and "Fetch" Joins. - -**Regular Joins**: Used to limit the results and/or compute aggregate values. - -**Fetch Joins**: In addition to the uses of regular joins: Used to fetch related entities and include them in the hydrated result of a query. - -There is no special DQL keyword that distinguishes a regular join from a fetch join. A join (be it an inner or outer join) becomes a "fetch join" as soon as fields of the joined entity appear in the SELECT part of the DQL query outside of an aggregate function. Otherwise its a "regular join". - -Example: - -Regular join of the address: - - [php] - $query = $em->createQuery("SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); - $users = $query->getResult(); - -Fetch join of the address: - - [php] - $query = $em->createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); - $users = $query->getResult(); - -When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the -root level of the result array. In the previous example an array of User instances is returned -and the address of each user is fetched and hydrated into the `User#address` variable. If you access -the address Doctrine does not need to lazy load the association with another query. - -> **NOTE** -> Doctrine allows you to walk all the associations between all the objects in your domain model. -> Objects that were not already loaded from the database are replaced with lazy load proxy instances. -> Non-loaded Collections are also replaced by lazy-load instances that fetch all the contained objects upon -> first access. However relying on the lazy-load mechanism leads to many small queries executed -> against the database, which can significantly affect the performance of your application. -> **Fetch Joins** are the solution to hydrate most or all of the entities that you -> need in a single SELECT query. - -+++ Named and Positional Parameters - -DQL supports both named and positional parameters, however in contrast to many SQL dialects -positional parameters are specified with numbers, for example "?1", "?2" and so on. -Named parameters are specified with ":name1", ":name2" and so on. - -+++ DQL SELECT Examples - -This section contains a large set of DQL queries and some explanations of what is happening. -The actual result also depends on the hydration mode. - -Hydrate all User entities: - - [php] - $query = $em->createQuery('SELECT u FROM MyProject\Model\User u'); - $users = $query->getResult(); // array of User objects - -Retrieve the IDs of all CmsUsers: - - [php] - $query = $em->createQuery('SELECT u.id FROM CmsUser u'); - $ids = $query->getResult(); // array of CmsUser ids - -Retrieve the IDs of all users that have written an article: - - [php] - $query = $em->createQuery('SELECT DISTINCT a.user.id FROM CmsArticle a'); - $ids = $query->getResult(); // array of CmsUser ids - -Retrieve all articles and sort them by the name of the articles users instance: - - [php] - $query = $em->createQuery('SELECT a FROM CmsArticle a ORDER BY a.user.name ASC'); - $articles = $query->getResult(); // array of CmsArticle objects - -Retrieve the Username and Name of a CmsUser: - - [php] - $query = $em->createQuery('SELECT u.username, u.name FROM CmsUser u'); - $users = $query->getResults(); // array of CmsUser username and id values - echo $users[0]['username']; - -Retrieve a ForumUser and his single associated entity: - - [php] - $query = $em->createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a'); - $users = $query->getResult(); // array of ForumUser objects with the avatar association loaded - echo get_class($users[0]->getAvatar()); - -Retrieve a CmsUser and fetch join all the phonenumbers he has: - - [php] - $query = $em->createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p'); - $users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded - $phonenumbers = $users[0]->getPhonenumbers(); - -Hydrate a result in Ascending: - - [php] - $query = $em->createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC'); - $users = $query->getResult(); // array of ForumUser objects - -Or in Descending Order: - - [php] - $query = $em->createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC'); - $users = $query->getResult(); // array of ForumUser objects - -Using Aggregate Functions: - - [php] - $query = $em->createQuery('SELECT COUNT(u.id) FROM Entities\User u'); - $count = $query->getSingleScalarResult(); - -With WHERE Clause and Positional Parameter: - - [php] - $query = $em->createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1'); - $users = $query->getResult(); // array of ForumUser objects - -With WHERE Clause and Named Parameter: - - [php] - $query = $em->createQuery('SELECT u FROM ForumUser u WHERE u.username = :name'); - $users = $query->getResult(); // array of ForumUser objects - -With Nested Conditions in WHERE Clause: - - [php] - $query = $em->createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id'); - $users = $query->getResult(); // array of ForumUser objects - -With COUNT DISTINCT: - - [php] - $query = $em->createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser - $users = $query->getResult(); // array of ForumUser objects - -With Arithmetic Expression in WHERE clause: - - $query = $em->createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); - $users = $query->getResult(); // array of ForumUser objects - -Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids: - - [php] - $query = $em->createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a'); - $results = $query->getResult(); // array of user ids and every article_id for each user - -Restricting a JOIN clause by additional conditions: - - [php] - $query = $em->createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%'"); - $users = $query->getResult(); - -Using several Fetch JOINs: - - [php] - $query = $em->createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c'); - $users = $query->getResult(); - -BETWEEN in WHERE clause: - - [php] - $query = $em->createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2'); - $usernames = $query->getResult(); - -DQL Functions in WHERE clause: - - [php] - $query = $em->createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'"); - $usernames = $query->getResult(); - -IN() Expression: - - [php] - $query = $em->createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)'); - $usernames = $query->getResult(); - - $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id IN (1, 2)'); - $users = $query->getResult(); - - $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id NOT IN (1)'); - $users = $query->getResult(); - -CONCAT() DQL Function: - - [php] - $query = $em->createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); - $ids = $query->getResult(); - - $query = $em->createQuery('SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1'); - $idUsernames = $query->getResult(); - -EXISTS in WHERE clause with correlated Subquery - - [php] - $query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.phonenumber = u.id)'); - $ids = $query->getResult(); - -Get all users who are members of $group. - - [php] - $query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE :param MEMBER OF u.groups'); - $ids = $query->getResult(); - -Get all users that have more than 1 phonenumber - - [php] - $query = $em->createQuery('SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1'); - $users = $query->getResult(); - -Get all users that have no phonenumber - - [php] - $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); - $users = $query->getResult(); - -++++ Partial Object Syntax - -By default when you run a DQL query in Doctrine and select only a subset of the -fields for a given entity, you do not receive objects back. Instead, you receive -only arrays as a flat rectangular result set, similar to how you would if you -were just using SQL directly and joining some data. - -If you want to select partial objects you can use the `partial` DQL keyword: - - [php] - $query = $em->createQuery('SELECT partial u.{id, username} FROM CmsUser u'); - $users = $query->getResult(); // array of partially loaded CmsUser objects - -You use the partial syntax when joining as well: - - [php] - $query = $em->createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a'); - $users = $query->getResult(); // array of partially loaded CmsUser objects - -+++ Using INDEX BY - -The INDEX BY construct is nothing that directly translates into SQL but that affects -object and array hydration. After each FROM and JOIN clause you specify by which field -this class should be indexed in the result. By default a result is incremented -by numerical keys starting with 0. However with INDEX BY you can specifiy any -other column to be the key of your result, it really only makes sense with primary -or unique fields though: - - [sql] - SELECT u.id, u.status, upper(u.name) nameUpper FROM User u INDEX BY u.id - JOIN u.phonenumbers p INDEX BY p.phonenumber - -Returns an array of the following kind, indexed by both user-id then phonenumber-id: - - array - 0 => - array - 1 => - object(stdClass)[299] - public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) - public 'id' => int 1 - .. - 'nameUpper' => string 'ROMANB' (length=6) - 1 => - array - 2 => - object(stdClass)[298] - public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) - public 'id' => int 2 - ... - 'nameUpper' => string 'JWAGE' (length=5) - -++ UPDATE queries - -DQL not only allows to select your Entities using field names, you can also execute bulk updates on a set -of entities using an DQL-UPDATE query. The Syntax of an UPDATE query works as expected, as the following -example shows: - - UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) - -References to related entities are only possible in the WHERE clause and using subselects. - -> **CAUTION** -> DQL UPDATE statements are ported directly into a Database UPDATE statement and therefore bypass -> any locking scheme and do not increment the version column. Entities that are already -> loaded into the persistence context will *NOT* be synced with the updated database state. It -> is recommended to call `EntityManager#clear()` and retrieve new instances of any affected entity. - -++ DELETE queries - -DELETE queries can also be specified using DQL and their syntax is as simple as the UPDATE syntax: - - DELETE MyProject\Model\User u WHERE u.id = 4 - -The same restrictions apply for the reference of related entities. - -> **CAUTION** -> DQL DELETE statements are ported directly into a Database DELETE statement and therefore -> bypass any checks for the version column if they are not explicitly added to the WHERE -> clause of the query. Additionally Deletes of specifies entities are *NOT* cascaded -> to related entities even if specified in the metadata. - -++ Functions, Operators, Aggregates - - -+++ DQL Functions - -The following functions are supported in SELECT, WHERE and HAVING clauses: - -* ABS(arithmetic_expression) -* CONCAT(str1, str2) -* CURRENT_DATE() - Return the current date -* CURRENT_TIME() - Returns the current time -* CURRENT_TIMESTAMP() - Returns a timestamp of the current date and time. -* LENGTH(str) - Returns the length of the given string -* LOCATE(needle, haystack [, offset]) - Locate the first occurance of the substring in the string. -* LOWER(str) - returns the string lowercased. -* MOD(a, b) - Return a MOD b. -* SIZE(collection) - Return the number of elements in the specified collection -* SQRT(q) - Return the squareroot of q. -* SUBSTRING(str, start [, length]) - Return substring of given string. -* TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str) - Trim the string by the given trim char, defaults to whitespaces. -* UPPER(str) - Return the upper-case of the given string. - -+++ Arithmetic operators - -You can do math in DQL using numeric values, for example: - - SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 - -+++ Aggregate Functions - -The following aggregate functions are allowed in SELECT and GROUP BY clauses: AVG, COUNT, MIN, MAX, SUM - -+++ Other Expressions - -DQL offers a wide-range of additional expressions that are known from SQL, here is a list of -all the supported constructs: - -* `ALL/ANY/SOME` - Used in a WHERE clause followed by a subselect this works like the equivalent constructs in SQL. -* `BETWEEN a AND b` and `NOT BETWEEN a AND b` can be used to match ranges of arithmetic values. -* `IN (x1, x2, ...)` and `NOT IN (x1, x2, ..)` can be used to match a set of given values. -* `LIKE ..` and `NOT LIKE ..` match parts of a string or text using % as a wildcard. -* `IS NULL` and `IS NOT NULL` to check for null values -* `EXISTS` and `NOT EXISTS` in combination with a subselect - -+++ Adding your own functions to the DQL language - -By default DQL comes with functions that are part of a large basis of underlying databases. However you will most likely -choose a database platform at the beginning of your project and most likely never change it. For this cases you can -easily extend the DQL parser with own specialized platform functions. - -You can register custom DQL functions in your ORM Configuration: - - [php] - $config = new \Doctrine\ORM\Configuration(); - $config->addCustomStringFunction($name, $class); - $config->addCustomNumericFunction($name, $class); - $config->addCustomDatetimeFunction($name, $class); - - $em = EntityManager::create($dbParams, $config); - -The functions have to return either a string, numeric or datetime value depending on the registered function type. As an example -we will add a MySQL specific FLOOR() functionality. All the given classes have to implement the base class -\Doctrine\ORM\Query\AST\Functions\FunctionNode: - - [php] - namespace MyProject\Query\AST; - - use \Doctrine\ORM\Query\AST\Functions\FunctionsNode; - - class MysqlFloor extends FunctionNode - { - public $simpleArithmeticExpression; - - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) - { - return 'FLOOR(' . $sqlWalker->walkSimpleArithmeticExpression( - $this->simpleArithmeticExpression - ) . ')'; - } - - public function parse(\Doctrine\ORM\Query\Parser $parser) - { - $lexer = $parser->getLexer(); - - $parser->match(Lexer::T_ABS); - $parser->match(Lexer::T_OPEN_PARENTHESIS); - - $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); - - $parser->match(Lexer::T_CLOSE_PARENTHESIS); - } - } - -We will register the function by calling and can then use it: - - [php] - \Doctrine\ORM\Query\Parser::registerNumericFunction('FLOOR', 'MyProject\Query\MysqlFloor'); - $dql = "SELECT FLOOR(person.salary * 1.75) FROM CompanyPerson person"; - -++ The Query class - -An instance of the `Doctrine\ORM\Query` class represents a DQL query. You create a Query instance be calling `EntityManager#createQuery($dql)`, passing the DQL query string. Alternatively you can create an empty `Query` instance and invoke `Query#setDql($dql)` afterwards. Here are some examples: - - [php] - // $em instanceof EntityManager - - // example1: passing a DQL string - $q = $em->createQuery('select u from MyProject\Model\User u'); - - // example2: usin setDql - $q = $em->createQuery(); - $q->setDql('select u from MyProject\Model\User u'); - -+++ Query Result Formats - -The format in which the result of a DQL SELECT query is returned can be influenced by a so-called `hydration mode`. A hydration mode specifies a particular way in which an SQL result set is transformed. Each hydration mode has its own dedicated method on the Query class. Here they are: - -* `Query#getResult()`: Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array where the objects are nested in the result rows (mixed). -* `Query#getSingleResult()`: Retrieves a single object. If the result contains more than one object, an exception is thrown. The pure/mixed distinction does not apply. -* `Query#getArrayResult()`: Retrieves an array graph (a nested array) that is largely interchangeable with the object graph generated by `Query#getResultList()` for read-only purposes. - -> **NOTE** -> An array graph can differ from the corresponding object graph in -> certain scenarios due to the difference of the identity semantics between arrays and -> objects. - -* `Query#getScalarResult()`: Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The pure/mixed distinction does not apply. -* `Query#getSingleScalarResult()`: Retrieves a single scalar value from the result returned by the dbms. If the result contains more than a single scalar value, an exception is thrown. The pure/mixed distinction does not apply. - -Instead of using these methods, you can alternatively use the general-purpose method `Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)`. Using this method you can directly supply the hydration mode as the second parameter via one of the Query constants. In fact, the methods mentioned earlier are just convenient shortcuts for the execute method. For example, the method `Query#getResultList()` internally invokes execute, passing in `Query::HYDRATE_OBJECT` as the hydration mode. - -The use of the methods mentioned earlier is generally preferred as it leads to more concise code. - -+++ Pure and Mixed Results - -The nature of a result returned by a DQL SELECT query retrieved through `Query#getResult()` or `Query#getArrayResult()` can be of 2 forms: **pure** and **mixed**. In the previous simple examples, you already saw a "pure" query result, with only objects. By default, the result type is **pure** but **as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. A mixed result has a different structure than a pure result in order to accomodate for the scalar values. - -A pure result usually looks like this: - - array - [0] => Object - [1] => Object - [2] => Object - ... - -A mixed result on the other hand has the following general structure: - - array - array - [0] => Object - [1] => "some scalar string" - ['count'] => 42 - // ... more scalar values, either indexed numerically or with a name - array - [0] => Object - [1] => "some scalar string" - ['count'] => 42 - // ... more scalar values, either indexed numerically or with a name - -To better understand mixed results, consider the following DQL query: - - [sql] - SELECT u, UPPER(u.name) nameUpper FROM MyProject\Model\User u - -This query makes use of the `UPPER` DQL function that returns a scalar value and because there is now a scalar value in the SELECT clause, we get a mixed result. - -Here is how the result could look like: - - array - array - [0] => User (Object) - ['nameUpper'] => "Roman" - array - [0] => User (Object) - ['nameUpper'] => "Jonathan" - ... - -And here is how you would access it in PHP code: - - [php] - foreach ($results as $row) { - echo "Name: " . $row[0]->getName(); - echo "Name UPPER: " . $row['nameUpper']; - } - -You may have observed that in a mixed result, the object always ends up on index 0 of a result row. - -+++ Hydration Modes - -Each of the Hydration Modes makes assumptions about how the result is returned to user land. You should -know about all the details to make best use of the different result formats: - -The constants for the different hydration modes are: - -* Query::HYDRATE_OBJECT -* Query::HYDRATE_ARRAY -* Query::HYDRATE_SCALAR -* Query::HYDRATE_SINGLE_SCALAR - -++++ Object Hydration - -Object hydration hydrates the result set into the object graph: - - [php] - $query = $em->createQuery('SELECT u FROM CmsUser u'); - $users = $query->getResult(Query::HYDRATE_OBJECT); - -++++ Array Hydration - -You can run the same query with array hydration and the result set is hydrated into -an array that represents the object graph: - - [php] - $query = $em->createQuery('SELECT u FROM CmsUser u'); - $users = $query->getResult(Query::HYDRATE_ARRAY); - -You can use the `getArrayResult()` shortcut as well: - - [php] - $users = $query->getArrayResult(); - -++++ Scalar Hydration - -If you want to return a flat rectangular result set instead of an object graph -you can use scalar hydration: - - [php] - $query = $em->createQuery('SELECT u FROM CmsUser u'); - $users = $query->getResult(Query::HYDRATE_SCALAR); - echo $users[0]['u_id']; - -The following assumptions are made about selected fields using Scalar Hydration: - -1. Fields from classes are prefixed by the DQL alias in the result. A query of the kind 'SELECT u.name ..' returns a key 'u_name' in the result rows. - -++++ Single Scalar Hydration - -If you a query which returns just a single scalar value you can use single scalar -hydration: - - [php] - $query = $em->createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); - $query->setParameter(1, 'jwage'); - $numArticles = $query->getResult(Query::HYDRATE_SCALAR); - -You can use the `getSingleScalarResult()` shortcut as well: - - [php] - $numArticles = $query->getSingleScalarResult(); - -++++ Custom Hydration Modes - -You can easily add your own custom hydration modes by first creating a class which extends `AbstractHydrator`: - - [php] - namespace MyProject\Hydrators; - - use Doctrine\ORM\Internal\Hydration\AbstractHydrator; - - class CustomHydrator extends AbstractHydrator - { - protected function _hydrateAll() - { - return $this->_stmt->fetchAll(PDO::FETCH_ASSOC); - } - } - -Next you just need to add the class to the ORM configuration: - - [php] - $em->getConfiguration()->addHydrator('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); - -Now the hydrator is ready to be used in your queries: - - [php] - $query = $em->createQuery('SELECT u FROM CmsUser u'); - $results = $query->getResult('CustomHydrator'); - -+++ Iterating Large Resultsets - -There are situations when a query you want to execute returns a very large result-set that needs -to be processed. All the previously described hydration modes completly load a result-set into -memory which might not be feasible with large result sets. See the [Batch Processing](batch-processing) -section on details how to iterate large result sets. - -+++ Functions - -The following methods exist on the `AbstractQuery` which both `Query` and `NativeQuery` extend from. - -++++ Parameters - -Prepared Statements that use numerical or named wildcards require additional parameters to be executable -against the database. To pass parameters to the query the following methods can be used: - -* `AbstractQuery::setParameter($param, $value)` - Set the numerical or named wildcard to the given value. -* `AbstractQuery::setParameters(array $params)` - Set an array of parameter key-value pairs. -* `AbstractQuery::getParameter($param)` -* `AbstractQuery::getParameters()` - -++++ Cache related API - -You can cache query results based either on all variables that define the result (SQL, Hydration Mode, Parameters and Hints) -or on user-defined cache keys. However by default query results are not cached at all. You have to enable -the result cache on a per query basis. The following example shows a complete workflow using the Result Cache -API: - - [php] - $query = $em->createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); - $query->setParameter(1, 12); - - $query->setResultCacheDriver(new ApcCache()); - - $query->useResultCache(true) - ->setResultCacheLifeTime($seconds = 3600); - - $result = $query->getResult(); // cache miss - - $query->expireResultCache(true); - $result = $query->getResult(); // forced expire, cache miss - - $query->setResultCacheId('my_query_result'); - $result = $query->getResult(); // saved in given result cache id. - - // or call useResultCache() with all parameters: - $query->useResultCache(true, $seconds = 3600, 'my_query_result'); - $result = $query->getResult(); // cache hit! - -> **TIP!** -> You can set the Result Cache Driver globally on the `Doctrine\ORM\Configuration` instance -> so that it is passed to every `Query` and `NativeQuery` instance. - -++++ Query Hints - -You can pass hints to the query parser and hydrators by using the `AbstractQuery::setHint($name, $value)` method. -Currently there exist mostly internal query hints that are not be consumed in userland. However the following few hints -are to be used in userland: - -* Query::HINT_FORCE_PARTIAL_LOAD - Allows to hydrate objects although not all their columns are fetched. This query -hint can be used to handle memory consumption problems with large result-sets that contain char or binary data. -Doctrine has no way of implicitly reloading this data. Partially loaded objects have to be passed to -`EntityManager::refresh()` if they are to be reloaded fully from the database. -* Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland aswell. -If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the -fields of the existing entity will be refreshed. In normal operation a result-set that loads data of an already existing -entity is discarded in favour of the already existing entity. -* Query::HINT_CUSTOM_TREE_WALKERS - An array of additional `Doctrine\ORM\Query\TreeWalker` instances that are attached -to the DQL query parsing process. - -++++ Query Cache (DQL Query Only) - -Parsing a DQL query and converting it into an SQL query against the underlying database platform obviously has some overhead -in contrast to directly executing Native SQL queries. That is why there is a dedicated Query Cache for caching the -DQL parser results. In combination with the use of wildcards you can reduce the number of parsed queries in production -to zero. - -The Query Cache Driver is passed from the `Doctrine\ORM\Configuration` instance to each `Doctrine\ORM\Query` instance -by default and is also enabled by default. This also means you don't regularly need to fiddle with the parameters -of the Query Cache, however if you do there are several methods to interact with it: - -* `Query::setQueryCacheDriver($driver)` - Allows to set a Cache instance -* `Query::setQueryCacheLifeTime($seconds = 3600)` - Set lifetime of the query caching. -* `Query::expireQueryCache($bool)` - Enforce the expiring of the query cache if set to true. -* `Query::getExpireQueryCache()` -* `Query::getQueryCacheDriver()` -* `Query::getQueryCacheLifeTime()` - -++++ First and Max Result Items (DQL Query Only) - -You can limit the number of results returned from a DQL query aswell as specify the starting offset, Doctrine -then uses a strategy of manipulating the select query to return only the requested number of results: - -* `Query::setMaxResults($maxResults)` -* `Query::setFirstResult($offset)` - -> **NOTE** -> If your query contains a fetch-joined collection specifying the result limit methods are not working -> as you would expect. Set Max Results restricts the number of database result rows, however in the -> case of fetch-joined collections one root entity might appear in many rows, effectivly hydrating -> less than the specified number of results. - -++ EBNF - -The following context-free grammar, written in an EBNF variant, describes the Doctrine Query Language. You can consult this grammar whenever you are unsure about what is possible with DQL or what the correct syntax for a particular query should be. - -+++ Document syntax: - -* non-terminals begin with an upper case character -* terminals begin with a lower case character -* parentheses (...) are used for grouping -* square brackets [...] are used for defining an optional part, eg. zero or one time -* curly brackets {...} are used for repetion, eg. zero or more times -* double quotation marks "..." define a terminal string a vertical bar | represents an alternative - -+++ Terminals - -* identifier (name, email, ...) -* string ('foo', 'bar''s house', '%ninja%', ...) -* char ('/', '\\', ' ', ...) -* integer (-1, 0, 1, 34, ...) -* float (-0.23, 0.007, 1.245342E+8, ...) -* boolean (false, true) - -+++ Query Language - - QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement - -+++ Statements - - SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] - UpdateStatement ::= UpdateClause [WhereClause] - DeleteStatement ::= DeleteClause [WhereClause] - -+++ Identifiers - - /* Alias Identification usage (the "u" of "u.name") */ - IdentificationVariable ::= identifier - - /* Alias Identification declaration (the "u" of "FROM User u") */ - AliasIdentificationVariable :: = identifier - - /* identifier that must be a class name (the "User" of "FROM User u") */ - AbstractSchemaName ::= identifier - - /* identifier that must be a field (the "name" of "u.name") */ - /* This is responsable to know if the field exists in Object, no matter if it's a relation or a simple field */ - FieldIdentificationVariable ::= identifier - - /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ - CollectionValuedAssociationField ::= FieldIdentificationVariable - - /* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */ - SingleValuedAssociationField ::= FieldIdentificationVariable - - /* identifier that must be an embedded class state field (for the future) */ - EmbeddedClassStateField ::= FieldIdentificationVariable - - /* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ - /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ - SimpleStateField ::= FieldIdentificationVariable - - /* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */ - AliasResultVariable = identifier - - /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ - ResultVariable = identifier - -+++ Path Expressions - - /* "u.Group" or "u.Phonenumbers" declarations */ - JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) - - /* "u.Group" or "u.Phonenumbers" usages */ - AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression - - /* "u.name" or "u.Group" */ - SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression - - /* "u.name" or "u.Group.name" */ - StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField - - /* "u.Group" */ - SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField - - /* "u.Group.Permissions" */ - CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField - - /* "name" */ - StateField ::= {EmbeddedClassStateField "."}* SimpleStateField - - /* "u.name" or "u.address.zip" (address = EmbeddedClassStateField) */ - SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField - - -+++ Clauses - - SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* - SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression - UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* - DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable - FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* - SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* - WhereClause ::= "WHERE" ConditionalExpression - HavingClause ::= "HAVING" ConditionalExpression - GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* - OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* - Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] - - -+++ Items - - UpdateItem ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue - OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] - GroupByItem ::= IdentificationVariable | SingleValuedPathExpression - NewValue ::= ScalarExpression | SimpleEntityExpression | "NULL" - - -+++ From, Join and Index by - - IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* - SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) - JoinVariableDeclaration ::= Join [IndexBy] - RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable - Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression - ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] - IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression - - -+++ Select Expressions - - SelectExpression ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")" | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable] - SimpleSelectExpression ::= ScalarExpression | IdentificationVariable | - (AggregateExpression [["AS"] AliasResultVariable]) - PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet - PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" - - -+++ Conditional Expressions - - ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* - ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* - ConditionalFactor ::= ["NOT"] ConditionalPrimary - ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" - SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression | - InExpression | NullComparisonExpression | ExistsExpression | - EmptyCollectionComparisonExpression | CollectionMemberExpression - - -+++ Collection Expressions - - EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" - CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression - -+++ Literal Values - - Literal ::= string | char | integer | float | boolean - InParameter ::= Literal | InputParameter - -+++ Input Parameter - - InputParameter ::= PositionalParameter | NamedParameter - PositionalParameter ::= "?" integer - NamedParameter ::= ":" string - - -+++ Arithmetic Expressions - - ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" - SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* - ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* - ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary - ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" - | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings - | FunctionsReturningDatetime | IdentificationVariable | InputParameter - - -+++ Scalar and Type Expressions - - ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression - BooleanPrimary | CaseExpression | EntityTypeExpression - CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | - CoalesceExpression | NullifExpression - GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression - "END" - WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression - SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* - "ELSE" ScalarExpression "END" - CaseOperand ::= StateFieldPathExpression | TypeDiscriminator - SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression - CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" - NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" - StringExpression ::= StringPrimary | "(" Subselect ")" - StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression - BooleanExpression ::= BooleanPrimary | "(" Subselect ")" - BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter - EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression - SimpleEntityExpression ::= IdentificationVariable | InputParameter - DatetimeExpression ::= DatetimePrimary | "(" Subselect ")" - DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression - - -+++ Aggregate Expressions - - AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | - "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" - -+++ Other Expressions - -QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS - - QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" - BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression - ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) - InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" - LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] - NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" - ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" - ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" - -+++ Functions - - FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime - - FunctionsReturningNumerics ::= - "LENGTH" "(" StringPrimary ")" | - "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | - "ABS" "(" SimpleArithmeticExpression ")" | "SQRT" "(" SimpleArithmeticExpression ")" | - "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | - "SIZE" "(" CollectionValuedPathExpression ")" - - FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" - - FunctionsReturningStrings ::= - "CONCAT" "(" StringPrimary "," StringPrimary ")" | - "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | - "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | - "LOWER" "(" StringPrimary ")" | +++ DQL Explained + +DQL stands for **D**octrine **Q**uery **L**anguage and is an Object Query Language derivate that is very similar to the **H**ibernate **Q**uery **L**anguage (HQL) or the **J**ava **P**ersistence **Q**uery **L**anguage (JPQL). + +In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects. + +> **CAUTION** +> A common mistake for beginners is to mistake DQL for being just some form of SQL +> and therefore trying to use table names and column names or join arbitrary tables +> together in a query. You need to think about DQL as a query language for your object +> model, not for your relational schema. + +DQL is case in-sensitive, except for namespace, class and field names, which are case sensitive. + +++ Types of DQL queries + +DQL as a query language has SELECT, UPDATE and DELETE constructs that map to their corresponding +SQL statement types. INSERT statements are not allowed in DQL, because entities and their relations +have to be introduced into the persistence context through `EntityManager#persist()` to ensure +consistency of your object model. + +DQL SELECT statements are a very powerful way of retrieving parts of your domain model that are +not accessible via associations. Additionally they allow to retrieve entities and their associations +in one single sql select statement which can make a huge difference in performance in contrast +to using several queries. + +DQL UPDATE and DELETE statements offer a way to execute bulk changes on the entities of your +domain model. This is often necessary when you cannot load all the affected entities of a bulk +update into memory. + +++ SELECT queries + ++++ DQL SELECT clause + +The select clause of a DQL query specifies what appears in the query result. The composition of all the expressions in the select clause also influences the nature of the query result. + +Here is an example that selects all users with an age > 20: + + [php] + $query = $em->createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20'); + $users = $query->getResult(); + +Lets examine the query: + +* `u` is a so called identification variable or alias that refers to the `MyProject\Model\User` class. By placing this alias in the SELECT clause we specify that we want all instances of the User class that are matched by this query appear in the query result. +* The FROM keyword is always followed by a fully-qualified class name which in turn is followed by an identification variable or alias for that class name. This class designates a root of our query from which we can navigate further via joins (explained later) and path expressions. +* The expression `u.age` in the WHERE clause is a path expression. Path expressions in DQL are easily identified by the use of the '.' operator that is used for constructing paths. The path expression `u.age` refers to the `age` field on the User class. + +The result of this query would be a list of User objects where all users are older than 20. + +The SELECT clause allows to specify both class identification variables that signal the hydration +of a complete entity class or just fields of the entity using the syntax `u.name`. +Combinations of both are also allowed and it is possible to wrap both fields and +identification values into aggregation and DQL functions. Numerical fields can +be part of computations using mathematical operations. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn) +on more information. + ++++ Joins + +A SELECT query can contain joins. There are 2 types of JOINs: "Regular" Joins and "Fetch" Joins. + +**Regular Joins**: Used to limit the results and/or compute aggregate values. + +**Fetch Joins**: In addition to the uses of regular joins: Used to fetch related entities and include them in the hydrated result of a query. + +There is no special DQL keyword that distinguishes a regular join from a fetch join. A join (be it an inner or outer join) becomes a "fetch join" as soon as fields of the joined entity appear in the SELECT part of the DQL query outside of an aggregate function. Otherwise its a "regular join". + +Example: + +Regular join of the address: + + [php] + $query = $em->createQuery("SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); + $users = $query->getResult(); + +Fetch join of the address: + + [php] + $query = $em->createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); + $users = $query->getResult(); + +When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the +root level of the result array. In the previous example an array of User instances is returned +and the address of each user is fetched and hydrated into the `User#address` variable. If you access +the address Doctrine does not need to lazy load the association with another query. + +> **NOTE** +> Doctrine allows you to walk all the associations between all the objects in your domain model. +> Objects that were not already loaded from the database are replaced with lazy load proxy instances. +> Non-loaded Collections are also replaced by lazy-load instances that fetch all the contained objects upon +> first access. However relying on the lazy-load mechanism leads to many small queries executed +> against the database, which can significantly affect the performance of your application. +> **Fetch Joins** are the solution to hydrate most or all of the entities that you +> need in a single SELECT query. + ++++ Named and Positional Parameters + +DQL supports both named and positional parameters, however in contrast to many SQL dialects +positional parameters are specified with numbers, for example "?1", "?2" and so on. +Named parameters are specified with ":name1", ":name2" and so on. + ++++ DQL SELECT Examples + +This section contains a large set of DQL queries and some explanations of what is happening. +The actual result also depends on the hydration mode. + +Hydrate all User entities: + + [php] + $query = $em->createQuery('SELECT u FROM MyProject\Model\User u'); + $users = $query->getResult(); // array of User objects + +Retrieve the IDs of all CmsUsers: + + [php] + $query = $em->createQuery('SELECT u.id FROM CmsUser u'); + $ids = $query->getResult(); // array of CmsUser ids + +Retrieve the IDs of all users that have written an article: + + [php] + $query = $em->createQuery('SELECT DISTINCT a.user.id FROM CmsArticle a'); + $ids = $query->getResult(); // array of CmsUser ids + +Retrieve all articles and sort them by the name of the articles users instance: + + [php] + $query = $em->createQuery('SELECT a FROM CmsArticle a ORDER BY a.user.name ASC'); + $articles = $query->getResult(); // array of CmsArticle objects + +Retrieve the Username and Name of a CmsUser: + + [php] + $query = $em->createQuery('SELECT u.username, u.name FROM CmsUser u'); + $users = $query->getResults(); // array of CmsUser username and id values + echo $users[0]['username']; + +Retrieve a ForumUser and his single associated entity: + + [php] + $query = $em->createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a'); + $users = $query->getResult(); // array of ForumUser objects with the avatar association loaded + echo get_class($users[0]->getAvatar()); + +Retrieve a CmsUser and fetch join all the phonenumbers he has: + + [php] + $query = $em->createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p'); + $users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded + $phonenumbers = $users[0]->getPhonenumbers(); + +Hydrate a result in Ascending: + + [php] + $query = $em->createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC'); + $users = $query->getResult(); // array of ForumUser objects + +Or in Descending Order: + + [php] + $query = $em->createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC'); + $users = $query->getResult(); // array of ForumUser objects + +Using Aggregate Functions: + + [php] + $query = $em->createQuery('SELECT COUNT(u.id) FROM Entities\User u'); + $count = $query->getSingleScalarResult(); + +With WHERE Clause and Positional Parameter: + + [php] + $query = $em->createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1'); + $users = $query->getResult(); // array of ForumUser objects + +With WHERE Clause and Named Parameter: + + [php] + $query = $em->createQuery('SELECT u FROM ForumUser u WHERE u.username = :name'); + $users = $query->getResult(); // array of ForumUser objects + +With Nested Conditions in WHERE Clause: + + [php] + $query = $em->createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id'); + $users = $query->getResult(); // array of ForumUser objects + +With COUNT DISTINCT: + + [php] + $query = $em->createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser + $users = $query->getResult(); // array of ForumUser objects + +With Arithmetic Expression in WHERE clause: + + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); + $users = $query->getResult(); // array of ForumUser objects + +Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids: + + [php] + $query = $em->createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a'); + $results = $query->getResult(); // array of user ids and every article_id for each user + +Restricting a JOIN clause by additional conditions: + + [php] + $query = $em->createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%'"); + $users = $query->getResult(); + +Using several Fetch JOINs: + + [php] + $query = $em->createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c'); + $users = $query->getResult(); + +BETWEEN in WHERE clause: + + [php] + $query = $em->createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2'); + $usernames = $query->getResult(); + +DQL Functions in WHERE clause: + + [php] + $query = $em->createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'"); + $usernames = $query->getResult(); + +IN() Expression: + + [php] + $query = $em->createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)'); + $usernames = $query->getResult(); + + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id IN (1, 2)'); + $users = $query->getResult(); + + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id NOT IN (1)'); + $users = $query->getResult(); + +CONCAT() DQL Function: + + [php] + $query = $em->createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); + $ids = $query->getResult(); + + $query = $em->createQuery('SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1'); + $idUsernames = $query->getResult(); + +EXISTS in WHERE clause with correlated Subquery + + [php] + $query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.phonenumber = u.id)'); + $ids = $query->getResult(); + +Get all users who are members of $group. + + [php] + $query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE :param MEMBER OF u.groups'); + $ids = $query->getResult(); + +Get all users that have more than 1 phonenumber + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1'); + $users = $query->getResult(); + +Get all users that have no phonenumber + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); + $users = $query->getResult(); + +++++ Partial Object Syntax + +By default when you run a DQL query in Doctrine and select only a subset of the +fields for a given entity, you do not receive objects back. Instead, you receive +only arrays as a flat rectangular result set, similar to how you would if you +were just using SQL directly and joining some data. + +If you want to select partial objects you can use the `partial` DQL keyword: + + [php] + $query = $em->createQuery('SELECT partial u.{id, username} FROM CmsUser u'); + $users = $query->getResult(); // array of partially loaded CmsUser objects + +You use the partial syntax when joining as well: + + [php] + $query = $em->createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a'); + $users = $query->getResult(); // array of partially loaded CmsUser objects + ++++ Using INDEX BY + +The INDEX BY construct is nothing that directly translates into SQL but that affects +object and array hydration. After each FROM and JOIN clause you specify by which field +this class should be indexed in the result. By default a result is incremented +by numerical keys starting with 0. However with INDEX BY you can specifiy any +other column to be the key of your result, it really only makes sense with primary +or unique fields though: + + [sql] + SELECT u.id, u.status, upper(u.name) nameUpper FROM User u INDEX BY u.id + JOIN u.phonenumbers p INDEX BY p.phonenumber + +Returns an array of the following kind, indexed by both user-id then phonenumber-id: + + array + 0 => + array + 1 => + object(stdClass)[299] + public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) + public 'id' => int 1 + .. + 'nameUpper' => string 'ROMANB' (length=6) + 1 => + array + 2 => + object(stdClass)[298] + public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) + public 'id' => int 2 + ... + 'nameUpper' => string 'JWAGE' (length=5) + +++ UPDATE queries + +DQL not only allows to select your Entities using field names, you can also execute bulk updates on a set +of entities using an DQL-UPDATE query. The Syntax of an UPDATE query works as expected, as the following +example shows: + + UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) + +References to related entities are only possible in the WHERE clause and using subselects. + +> **CAUTION** +> DQL UPDATE statements are ported directly into a Database UPDATE statement and therefore bypass +> any locking scheme and do not increment the version column. Entities that are already +> loaded into the persistence context will *NOT* be synced with the updated database state. It +> is recommended to call `EntityManager#clear()` and retrieve new instances of any affected entity. + +++ DELETE queries + +DELETE queries can also be specified using DQL and their syntax is as simple as the UPDATE syntax: + + DELETE MyProject\Model\User u WHERE u.id = 4 + +The same restrictions apply for the reference of related entities. + +> **CAUTION** +> DQL DELETE statements are ported directly into a Database DELETE statement and therefore +> bypass any checks for the version column if they are not explicitly added to the WHERE +> clause of the query. Additionally Deletes of specifies entities are *NOT* cascaded +> to related entities even if specified in the metadata. + +++ Functions, Operators, Aggregates + + ++++ DQL Functions + +The following functions are supported in SELECT, WHERE and HAVING clauses: + +* ABS(arithmetic_expression) +* CONCAT(str1, str2) +* CURRENT_DATE() - Return the current date +* CURRENT_TIME() - Returns the current time +* CURRENT_TIMESTAMP() - Returns a timestamp of the current date and time. +* LENGTH(str) - Returns the length of the given string +* LOCATE(needle, haystack [, offset]) - Locate the first occurance of the substring in the string. +* LOWER(str) - returns the string lowercased. +* MOD(a, b) - Return a MOD b. +* SIZE(collection) - Return the number of elements in the specified collection +* SQRT(q) - Return the squareroot of q. +* SUBSTRING(str, start [, length]) - Return substring of given string. +* TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str) - Trim the string by the given trim char, defaults to whitespaces. +* UPPER(str) - Return the upper-case of the given string. + ++++ Arithmetic operators + +You can do math in DQL using numeric values, for example: + + SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 + ++++ Aggregate Functions + +The following aggregate functions are allowed in SELECT and GROUP BY clauses: AVG, COUNT, MIN, MAX, SUM + ++++ Other Expressions + +DQL offers a wide-range of additional expressions that are known from SQL, here is a list of +all the supported constructs: + +* `ALL/ANY/SOME` - Used in a WHERE clause followed by a subselect this works like the equivalent constructs in SQL. +* `BETWEEN a AND b` and `NOT BETWEEN a AND b` can be used to match ranges of arithmetic values. +* `IN (x1, x2, ...)` and `NOT IN (x1, x2, ..)` can be used to match a set of given values. +* `LIKE ..` and `NOT LIKE ..` match parts of a string or text using % as a wildcard. +* `IS NULL` and `IS NOT NULL` to check for null values +* `EXISTS` and `NOT EXISTS` in combination with a subselect + ++++ Adding your own functions to the DQL language + +By default DQL comes with functions that are part of a large basis of underlying databases. However you will most likely +choose a database platform at the beginning of your project and most likely never change it. For this cases you can +easily extend the DQL parser with own specialized platform functions. + +You can register custom DQL functions in your ORM Configuration: + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->addCustomStringFunction($name, $class); + $config->addCustomNumericFunction($name, $class); + $config->addCustomDatetimeFunction($name, $class); + + $em = EntityManager::create($dbParams, $config); + +The functions have to return either a string, numeric or datetime value depending on the registered function type. As an example +we will add a MySQL specific FLOOR() functionality. All the given classes have to implement the base class +\Doctrine\ORM\Query\AST\Functions\FunctionNode: + + [php] + namespace MyProject\Query\AST; + + use \Doctrine\ORM\Query\AST\Functions\FunctionsNode; + + class MysqlFloor extends FunctionNode + { + public $simpleArithmeticExpression; + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'FLOOR(' . $sqlWalker->walkSimpleArithmeticExpression( + $this->simpleArithmeticExpression + ) . ')'; + } + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $lexer = $parser->getLexer(); + + $parser->match(Lexer::T_ABS); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + } + +We will register the function by calling and can then use it: + + [php] + \Doctrine\ORM\Query\Parser::registerNumericFunction('FLOOR', 'MyProject\Query\MysqlFloor'); + $dql = "SELECT FLOOR(person.salary * 1.75) FROM CompanyPerson person"; + +++ The Query class + +An instance of the `Doctrine\ORM\Query` class represents a DQL query. You create a Query instance be calling `EntityManager#createQuery($dql)`, passing the DQL query string. Alternatively you can create an empty `Query` instance and invoke `Query#setDql($dql)` afterwards. Here are some examples: + + [php] + // $em instanceof EntityManager + + // example1: passing a DQL string + $q = $em->createQuery('select u from MyProject\Model\User u'); + + // example2: usin setDql + $q = $em->createQuery(); + $q->setDql('select u from MyProject\Model\User u'); + ++++ Query Result Formats + +The format in which the result of a DQL SELECT query is returned can be influenced by a so-called `hydration mode`. A hydration mode specifies a particular way in which an SQL result set is transformed. Each hydration mode has its own dedicated method on the Query class. Here they are: + +* `Query#getResult()`: Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array where the objects are nested in the result rows (mixed). +* `Query#getSingleResult()`: Retrieves a single object. If the result contains more than one object, an exception is thrown. The pure/mixed distinction does not apply. +* `Query#getArrayResult()`: Retrieves an array graph (a nested array) that is largely interchangeable with the object graph generated by `Query#getResultList()` for read-only purposes. + +> **NOTE** +> An array graph can differ from the corresponding object graph in +> certain scenarios due to the difference of the identity semantics between arrays and +> objects. + +* `Query#getScalarResult()`: Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The pure/mixed distinction does not apply. +* `Query#getSingleScalarResult()`: Retrieves a single scalar value from the result returned by the dbms. If the result contains more than a single scalar value, an exception is thrown. The pure/mixed distinction does not apply. + +Instead of using these methods, you can alternatively use the general-purpose method `Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)`. Using this method you can directly supply the hydration mode as the second parameter via one of the Query constants. In fact, the methods mentioned earlier are just convenient shortcuts for the execute method. For example, the method `Query#getResultList()` internally invokes execute, passing in `Query::HYDRATE_OBJECT` as the hydration mode. + +The use of the methods mentioned earlier is generally preferred as it leads to more concise code. + ++++ Pure and Mixed Results + +The nature of a result returned by a DQL SELECT query retrieved through `Query#getResult()` or `Query#getArrayResult()` can be of 2 forms: **pure** and **mixed**. In the previous simple examples, you already saw a "pure" query result, with only objects. By default, the result type is **pure** but **as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. A mixed result has a different structure than a pure result in order to accomodate for the scalar values. + +A pure result usually looks like this: + + array + [0] => Object + [1] => Object + [2] => Object + ... + +A mixed result on the other hand has the following general structure: + + array + array + [0] => Object + [1] => "some scalar string" + ['count'] => 42 + // ... more scalar values, either indexed numerically or with a name + array + [0] => Object + [1] => "some scalar string" + ['count'] => 42 + // ... more scalar values, either indexed numerically or with a name + +To better understand mixed results, consider the following DQL query: + + [sql] + SELECT u, UPPER(u.name) nameUpper FROM MyProject\Model\User u + +This query makes use of the `UPPER` DQL function that returns a scalar value and because there is now a scalar value in the SELECT clause, we get a mixed result. + +Here is how the result could look like: + + array + array + [0] => User (Object) + ['nameUpper'] => "Roman" + array + [0] => User (Object) + ['nameUpper'] => "Jonathan" + ... + +And here is how you would access it in PHP code: + + [php] + foreach ($results as $row) { + echo "Name: " . $row[0]->getName(); + echo "Name UPPER: " . $row['nameUpper']; + } + +You may have observed that in a mixed result, the object always ends up on index 0 of a result row. + ++++ Hydration Modes + +Each of the Hydration Modes makes assumptions about how the result is returned to user land. You should +know about all the details to make best use of the different result formats: + +The constants for the different hydration modes are: + +* Query::HYDRATE_OBJECT +* Query::HYDRATE_ARRAY +* Query::HYDRATE_SCALAR +* Query::HYDRATE_SINGLE_SCALAR + +++++ Object Hydration + +Object hydration hydrates the result set into the object graph: + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_OBJECT); + +++++ Array Hydration + +You can run the same query with array hydration and the result set is hydrated into +an array that represents the object graph: + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_ARRAY); + +You can use the `getArrayResult()` shortcut as well: + + [php] + $users = $query->getArrayResult(); + +++++ Scalar Hydration + +If you want to return a flat rectangular result set instead of an object graph +you can use scalar hydration: + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_SCALAR); + echo $users[0]['u_id']; + +The following assumptions are made about selected fields using Scalar Hydration: + +1. Fields from classes are prefixed by the DQL alias in the result. A query of the kind 'SELECT u.name ..' returns a key 'u_name' in the result rows. + +++++ Single Scalar Hydration + +If you a query which returns just a single scalar value you can use single scalar +hydration: + + [php] + $query = $em->createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); + $query->setParameter(1, 'jwage'); + $numArticles = $query->getResult(Query::HYDRATE_SCALAR); + +You can use the `getSingleScalarResult()` shortcut as well: + + [php] + $numArticles = $query->getSingleScalarResult(); + +++++ Custom Hydration Modes + +You can easily add your own custom hydration modes by first creating a class which extends `AbstractHydrator`: + + [php] + namespace MyProject\Hydrators; + + use Doctrine\ORM\Internal\Hydration\AbstractHydrator; + + class CustomHydrator extends AbstractHydrator + { + protected function _hydrateAll() + { + return $this->_stmt->fetchAll(PDO::FETCH_ASSOC); + } + } + +Next you just need to add the class to the ORM configuration: + + [php] + $em->getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); + +Now the hydrator is ready to be used in your queries: + + [php] + $query = $em->createQuery('SELECT u FROM CmsUser u'); + $results = $query->getResult('CustomHydrator'); + ++++ Iterating Large Resultsets + +There are situations when a query you want to execute returns a very large result-set that needs +to be processed. All the previously described hydration modes completly load a result-set into +memory which might not be feasible with large result sets. See the [Batch Processing](batch-processing) +section on details how to iterate large result sets. + ++++ Functions + +The following methods exist on the `AbstractQuery` which both `Query` and `NativeQuery` extend from. + +++++ Parameters + +Prepared Statements that use numerical or named wildcards require additional parameters to be executable +against the database. To pass parameters to the query the following methods can be used: + +* `AbstractQuery::setParameter($param, $value)` - Set the numerical or named wildcard to the given value. +* `AbstractQuery::setParameters(array $params)` - Set an array of parameter key-value pairs. +* `AbstractQuery::getParameter($param)` +* `AbstractQuery::getParameters()` + +++++ Cache related API + +You can cache query results based either on all variables that define the result (SQL, Hydration Mode, Parameters and Hints) +or on user-defined cache keys. However by default query results are not cached at all. You have to enable +the result cache on a per query basis. The following example shows a complete workflow using the Result Cache +API: + + [php] + $query = $em->createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); + $query->setParameter(1, 12); + + $query->setResultCacheDriver(new ApcCache()); + + $query->useResultCache(true) + ->setResultCacheLifeTime($seconds = 3600); + + $result = $query->getResult(); // cache miss + + $query->expireResultCache(true); + $result = $query->getResult(); // forced expire, cache miss + + $query->setResultCacheId('my_query_result'); + $result = $query->getResult(); // saved in given result cache id. + + // or call useResultCache() with all parameters: + $query->useResultCache(true, $seconds = 3600, 'my_query_result'); + $result = $query->getResult(); // cache hit! + +> **TIP!** +> You can set the Result Cache Driver globally on the `Doctrine\ORM\Configuration` instance +> so that it is passed to every `Query` and `NativeQuery` instance. + +++++ Query Hints + +You can pass hints to the query parser and hydrators by using the `AbstractQuery::setHint($name, $value)` method. +Currently there exist mostly internal query hints that are not be consumed in userland. However the following few hints +are to be used in userland: + +* Query::HINT_FORCE_PARTIAL_LOAD - Allows to hydrate objects although not all their columns are fetched. This query +hint can be used to handle memory consumption problems with large result-sets that contain char or binary data. +Doctrine has no way of implicitly reloading this data. Partially loaded objects have to be passed to +`EntityManager::refresh()` if they are to be reloaded fully from the database. +* Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland aswell. +If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the +fields of the existing entity will be refreshed. In normal operation a result-set that loads data of an already existing +entity is discarded in favour of the already existing entity. +* Query::HINT_CUSTOM_TREE_WALKERS - An array of additional `Doctrine\ORM\Query\TreeWalker` instances that are attached +to the DQL query parsing process. + +++++ Query Cache (DQL Query Only) + +Parsing a DQL query and converting it into an SQL query against the underlying database platform obviously has some overhead +in contrast to directly executing Native SQL queries. That is why there is a dedicated Query Cache for caching the +DQL parser results. In combination with the use of wildcards you can reduce the number of parsed queries in production +to zero. + +The Query Cache Driver is passed from the `Doctrine\ORM\Configuration` instance to each `Doctrine\ORM\Query` instance +by default and is also enabled by default. This also means you don't regularly need to fiddle with the parameters +of the Query Cache, however if you do there are several methods to interact with it: + +* `Query::setQueryCacheDriver($driver)` - Allows to set a Cache instance +* `Query::setQueryCacheLifeTime($seconds = 3600)` - Set lifetime of the query caching. +* `Query::expireQueryCache($bool)` - Enforce the expiring of the query cache if set to true. +* `Query::getExpireQueryCache()` +* `Query::getQueryCacheDriver()` +* `Query::getQueryCacheLifeTime()` + +++++ First and Max Result Items (DQL Query Only) + +You can limit the number of results returned from a DQL query aswell as specify the starting offset, Doctrine +then uses a strategy of manipulating the select query to return only the requested number of results: + +* `Query::setMaxResults($maxResults)` +* `Query::setFirstResult($offset)` + +> **NOTE** +> If your query contains a fetch-joined collection specifying the result limit methods are not working +> as you would expect. Set Max Results restricts the number of database result rows, however in the +> case of fetch-joined collections one root entity might appear in many rows, effectivly hydrating +> less than the specified number of results. + +++ EBNF + +The following context-free grammar, written in an EBNF variant, describes the Doctrine Query Language. You can consult this grammar whenever you are unsure about what is possible with DQL or what the correct syntax for a particular query should be. + ++++ Document syntax: + +* non-terminals begin with an upper case character +* terminals begin with a lower case character +* parentheses (...) are used for grouping +* square brackets [...] are used for defining an optional part, eg. zero or one time +* curly brackets {...} are used for repetion, eg. zero or more times +* double quotation marks "..." define a terminal string a vertical bar | represents an alternative + ++++ Terminals + +* identifier (name, email, ...) +* string ('foo', 'bar''s house', '%ninja%', ...) +* char ('/', '\\', ' ', ...) +* integer (-1, 0, 1, 34, ...) +* float (-0.23, 0.007, 1.245342E+8, ...) +* boolean (false, true) + ++++ Query Language + + QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement + ++++ Statements + + SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + UpdateStatement ::= UpdateClause [WhereClause] + DeleteStatement ::= DeleteClause [WhereClause] + ++++ Identifiers + + /* Alias Identification usage (the "u" of "u.name") */ + IdentificationVariable ::= identifier + + /* Alias Identification declaration (the "u" of "FROM User u") */ + AliasIdentificationVariable :: = identifier + + /* identifier that must be a class name (the "User" of "FROM User u") */ + AbstractSchemaName ::= identifier + + /* identifier that must be a field (the "name" of "u.name") */ + /* This is responsable to know if the field exists in Object, no matter if it's a relation or a simple field */ + FieldIdentificationVariable ::= identifier + + /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ + CollectionValuedAssociationField ::= FieldIdentificationVariable + + /* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */ + SingleValuedAssociationField ::= FieldIdentificationVariable + + /* identifier that must be an embedded class state field (for the future) */ + EmbeddedClassStateField ::= FieldIdentificationVariable + + /* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ + /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ + SimpleStateField ::= FieldIdentificationVariable + + /* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */ + AliasResultVariable = identifier + + /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ + ResultVariable = identifier + ++++ Path Expressions + + /* "u.Group" or "u.Phonenumbers" declarations */ + JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) + + /* "u.Group" or "u.Phonenumbers" usages */ + AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression + + /* "u.name" or "u.Group" */ + SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression + + /* "u.name" or "u.Group.name" */ + StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField + + /* "u.Group" */ + SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField + + /* "u.Group.Permissions" */ + CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField + + /* "name" */ + StateField ::= {EmbeddedClassStateField "."}* SimpleStateField + + /* "u.name" or "u.address.zip" (address = EmbeddedClassStateField) */ + SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField + + ++++ Clauses + + SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* + SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression + UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* + DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable + FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* + SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* + WhereClause ::= "WHERE" ConditionalExpression + HavingClause ::= "HAVING" ConditionalExpression + GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* + OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* + Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + + ++++ Items + + UpdateItem ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue + OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] + GroupByItem ::= IdentificationVariable | SingleValuedPathExpression + NewValue ::= ScalarExpression | SimpleEntityExpression | "NULL" + + ++++ From, Join and Index by + + IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* + SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) + JoinVariableDeclaration ::= Join [IndexBy] + RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable + Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression + ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] + IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression + + ++++ Select Expressions + + SelectExpression ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")" | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable] + SimpleSelectExpression ::= ScalarExpression | IdentificationVariable | + (AggregateExpression [["AS"] AliasResultVariable]) + PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet + PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" + + ++++ Conditional Expressions + + ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* + ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* + ConditionalFactor ::= ["NOT"] ConditionalPrimary + ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" + SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression | + InExpression | NullComparisonExpression | ExistsExpression | + EmptyCollectionComparisonExpression | CollectionMemberExpression + + ++++ Collection Expressions + + EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" + CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression + ++++ Literal Values + + Literal ::= string | char | integer | float | boolean + InParameter ::= Literal | InputParameter + ++++ Input Parameter + + InputParameter ::= PositionalParameter | NamedParameter + PositionalParameter ::= "?" integer + NamedParameter ::= ":" string + + ++++ Arithmetic Expressions + + ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" + SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* + ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* + ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary + ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" + | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings + | FunctionsReturningDatetime | IdentificationVariable | InputParameter + + ++++ Scalar and Type Expressions + + ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression + BooleanPrimary | CaseExpression | EntityTypeExpression + CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | + CoalesceExpression | NullifExpression + GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression + "END" + WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* + "ELSE" ScalarExpression "END" + CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" + NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" + StringExpression ::= StringPrimary | "(" Subselect ")" + StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression + BooleanExpression ::= BooleanPrimary | "(" Subselect ")" + BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter + EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression + SimpleEntityExpression ::= IdentificationVariable | InputParameter + DatetimeExpression ::= DatetimePrimary | "(" Subselect ")" + DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression + + ++++ Aggregate Expressions + + AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | + "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" + ++++ Other Expressions + +QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS + + QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" + BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression + ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) + InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" + LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] + NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" + ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" + ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" + ++++ Functions + + FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime + + FunctionsReturningNumerics ::= + "LENGTH" "(" StringPrimary ")" | + "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | + "ABS" "(" SimpleArithmeticExpression ")" | "SQRT" "(" SimpleArithmeticExpression ")" | + "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + "SIZE" "(" CollectionValuedPathExpression ")" + + FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" + + FunctionsReturningStrings ::= + "CONCAT" "(" StringPrimary "," StringPrimary ")" | + "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | + "LOWER" "(" StringPrimary ")" | "UPPER" "(" StringPrimary ")" \ No newline at end of file From 854a966a22dc31ee5ec0d21826a2f5487839ea1d Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Mon, 7 Jun 2010 16:54:22 -0700 Subject: [PATCH 046/430] Fix Query::HYDRATE_SINGLE_SCALAR in single scalar result example --- manual/en/dql-doctrine-query-language.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 manual/en/dql-doctrine-query-language.txt diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt old mode 100755 new mode 100644 index a4b66bf2c..ab77e1476 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -596,7 +596,7 @@ hydration: [php] $query = $em->createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); $query->setParameter(1, 'jwage'); - $numArticles = $query->getResult(Query::HYDRATE_SCALAR); + $numArticles = $query->getResult(Query::HYDRATE_SINGLE_SCALAR); You can use the `getSingleScalarResult()` shortcut as well: From 51729cbaa71d8aacf5765b51b58a2e738d5fb9a2 Mon Sep 17 00:00:00 2001 From: beberlei Date: Thu, 10 Jun 2010 21:06:23 +0200 Subject: [PATCH 047/430] Enhanced Description of how the different events work and what restrictions apply to them. --- manual/en/events.txt | 151 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/manual/en/events.txt b/manual/en/events.txt index 254a43427..951ed153d 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -267,16 +267,167 @@ Although you get passed the EntityManager in all of these events, you have to fo carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and lost updates/persists/removes. +For the described events that are also lifecycle callback events the restrictions +apply aswell, with the additional restriction that you do not have access to the EntityManager +or UnitOfWork APIs inside these events. + +++ prePersist +There are two ways for the `prePersist` event to be triggered. One is obviously +when you call `EntityManager#persist()`. The event is also called for all +cascaded associations. + +There is another way for `prePersist` to be called, inside the `flush()` +method when changes to associations are computed and this association +is marked as cascade persist. Any new entity found during this operation +is also persisted and `prePersist` called on it. This is called "persistence by reachability". + +In both cases you get passed a `LifecycleEventArgs` +instance which has access to the entity and the entity manager. + +The following restrictions apply to `prePersist`: + +* If you are using a PrePersist Identity Generator such as sequences the ID value + will *NOT* be available within any PrePersist events. +* Doctrine will not recognize changes made to relations in a pre persist event + called by "reachibility" through a cascade persist unless you use the internal + `UnitOfWork` API. We do not recommend such operations in the persistence by + reachability context, so do this at your own risk and possibly supported by unit-tests. + +++ preRemove +The `preRemove` event is called on every entity when its passed to +the `EntityManager#remove()` method. It is cascaded for all +associations that are marked as cascade delete. + +There are no restrictions to what methods can be called inside +the `preRemove` event, except when the remove method itself was +called during a flush operation. + +++ onFlush +OnFlush is a very powerful event. It is called inside `EntityManager#flush()` +after the changes to all the managed entities and their associations have +been computed. This means, the `onFlush` event has access to the sets of: + +* Entities scheduled for insert +* Entities scheduled for update +* Entities scheduled for removal +* Collections scheduled for update +* Collections scheduled for removal + +To make use of the onFlush event you have to be familiar with interal UnitOfWork API, +which grants you access to the previously mentioned sets. See this example: + + [php] + class FlushExampleListener + { + public function onFlush(OnFlushEventArgs $eventArgs) + { + $em = $eventArgs->getEntityManager(); + $uow = $em->getUnitOfWork(); + + foreach ($uow->getScheduledEntityInsertions() AS $entity) { + + } + + foreach ($uow->getScheduledEntityUpdates() AS $entity) { + + } + + foreach ($uow->getScheduledEntityDeletions() AS $entity) { + + } + + foreach ($uow->getScheduledCollectionDeletions() AS $col) { + + } + + foreach ($uow->getScheduledCollectionUpdates() AS $col) { + + } + } + } + +The following restrictions apply to the onFlush event: + +* Calling `EntityManager#persist()` does not suffice to trigger a persist on an entity. + You have to execute an additional call to `$unitOfWork->computeChangeSet($classMetadata, $entity)`. +* Changing primitive fields or associations requires you to explicitly trigger + a re-computation of the changeset of the affected entity. This can be done + by either calling `$unitOfWork->computeChangeSet($classMetadata, $entity)` + or `$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)`. The second + method has lower overhead, but only re-computes primitive fields, never associations. + +++ preUpdate +PreUpdate is the most restrictive to use event, since it is called right before +an update statement is called for an entity inside the `EntityManager#flush()` +method. + +Changes to associations of the updated entity are never allowed in this event, since Doctrine cannot guarantee to +correctly handle referential integrity at this point of the flush operation. This +event has a powerful feature however, it is executed with a `PreUpdateEventArgs` +instance, which contains a reference to the computed change-set of this entity. + +This means you have access to all the fields that have changed for this entity +with their old and new value. The following methods are available on the `PreUpdateEventArgs`: + +* `getEntity()` to get access to the actual entity. +* `getEntityChangeSet()` to get a copy of the changeset array. Changes to this returned array do not affect updating. +* `hasChangedField($fieldName)` to check if the given field name of the current entity changed. +* `getOldValue($fieldName)` and `getNewValue($fieldName)` to access the values of a field. +* `setNewValue($fieldName, $value)` to change the value of a field to be updated. + +A simple example for this event looks like: + + [php] + class NeverAliceOnlyBobListener + { + public function preUpdate(PreUpdateEventArgs $eventArgs) + { + if ($eventArgs->getEntity() instanceof User) { + if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue() == 'Alice') { + $eventArgs->setNewValue('name', 'Bob'); + } + } + } + } + +You could also use this listener to implement validation of all the fields that have changed. +This is more efficient than using a lifecycle callback when there are expensive validations +to call: + + [php] + class ValidCreditCardListener + { + public function preUpdate(PreUpdateEventArgs $eventArgs) + { + if ($eventArgs->getEntity() instanceof Account) { + if ($eventArgs->hasChangedField('creditCard')) { + $this->validateCreditCard($eventArgs->getNewValue('creditCard')); + } + } + } + + private function validateCreditCard($no) + { + // throw an exception to interupt flush event. Transaction will be rolled back. + } + } + +Restrictions for this event: + +* Changes to associations of the passed entities are not recognized by the flush operation anymore. +* Changes to fields of the passed entities are not recognized by the flush operation anymore, use the computed change-set passed to the event to modify primitive field values. +* Any calls to `EntityManager#persist()` or `EntityManager#remove()`, even in combination with the UnitOfWork API are strongly discouraged and don't work as expected outside the flush operation. + +++ postUpdate, postRemove, postPersist +The three post events are called inside `EntityManager#flush()`. Changes in here +are not relevant to the persistence in the database, but you can use this events +to + ++ Load ClassMetadata Event When the mapping information for an entity is read, it is populated in to a From d872d7f0b194fa01fd6ea1365b844ff72e79e41e Mon Sep 17 00:00:00 2001 From: beberlei Date: Thu, 10 Jun 2010 21:38:22 +0200 Subject: [PATCH 048/430] Updated Cookbook Getting Started XML Edition Tutorial with Array and Scalar Hydration examples, tweaked several passages a little bit --- cookbook/en/getting-started-xml-edition.txt | 84 ++++++++++++++++++--- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/cookbook/en/getting-started-xml-edition.txt b/cookbook/en/getting-started-xml-edition.txt index 8669d0ba6..93851cc13 100644 --- a/cookbook/en/getting-started-xml-edition.txt +++ b/cookbook/en/getting-started-xml-edition.txt @@ -17,7 +17,7 @@ that contains the data which is persisted and retrieved by Doctrine's data mappi ## An Example Model: Bug Tracker For this Getting Started Guide for Doctrine we will implement the Bug Tracker domain model from the [Zend_Db_Table](http://framework.zend.com/manual/en/zend.db.table.html) -documentation. Reading that documentat we can extract the requirements to be: +documentation. Reading their documentation we can extract the requirements to be: * A Bugs has a description, creation date, status, reporter and engineer * A bug can occour on different products (platforms) @@ -491,7 +491,7 @@ Having created the schema we can now start and save entities in the database. Fo $entityManager->persist($user); $entityManager->flush(); -Having a user, he can create products: +Products can also be created: [php] $newProductName = "My Product"; @@ -509,12 +509,16 @@ You have to explicitly call `flush()` to have the EntityManager write those two You might wonder why does this distinction between persist notification and flush exist? Doctrine 2 uses the UnitOfWork pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one single fast transaction, which -is executed when flushing. This way the write-performance is unbelievable fast. Also in more complex scenarios -than those two before you can request updates on many different entities and all flush them at once. +is executed when flush is called. Using this approach the write-performance is significantly faster than +in a scenario where updates are done for each entity in isolation. In more complex scenarios than the +previous two, you are free to request updates on many different entities and all flush them at once. -Doctrine's UnitOfWork even detects entities that have been retrieved from the database and changed when calling -flush, so that you only have to keep track of those entities that are new or to be removed and pass them to -`EntityManager#persist()` and `EntityManager#remove()` respectively. +Doctrine's UnitOfWork detects entities that have changed after retrieval from the database automatically when +the flush operation is called, so that you only have to keep track of those entities that are new or to be removed and pass them to +`EntityManager#persist()` and `EntityManager#remove()` respectively. This comparison to find dirty +entities that need updating is using a very efficient algorithm that has almost no additional +memory overhead and can even save you computing power by only updating those database columns +that really changed. We are now getting to the "Create a New Bug" requirement and the code for this scenario may look like this: @@ -550,6 +554,8 @@ is called and relate them in the database appropriately. ## Queries for Application Use-Cases +### List of Bugs + Using the previous examples we can fill up the database quite a bit, however we now need to discuss how to query the underlying mapper for the required view representations. When opening the application, bugs can be paginated through a list-view, which is the first read-only use-case: @@ -605,6 +611,38 @@ in one single SQL statement. The console output of this script is then: > Using Native SQL you could even use stored procedures for data retrieval, or make use of advanced non-portable > database queries like PostgreSql's recursive queries. +### Array Hydration of the Bug List + +In the previous use-case we retrieved the result as their respective object instances. +We are not limitied to retrieving objects only from Doctrine however. For a simple list view +like the previous one we only need read access to our entities and can switch the hydration +from objects to simple PHP arrays instead. This can obviously yiel considerable performance benefits for read-only requests. + +Implementing the same list view as before using array hydration we can rewrite our code: + + [php] + $dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ". + "JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC"; + $query = $em->createQuery($dql); + $bugs = $query->getArrayResult(); + + foreach ($bugs AS $bug) { + echo $bug['description'] . " - " . $bug['created']->format('d.m.Y')."\n"; + echo " Reported by: ".$bug['reporter']['name']."\n"; + echo " Assigned to: ".$bug['engineer']['name']."\n"; + foreach($bug['products'] AS $product) { + echo " Platform: ".$product['name']."\n"; + } + echo "\n"; + } + +There is one significant difference in the DQL query however, we have +to add an additional fetch-join for the products connected to a bug. The resulting +SQL query for this single select statement is pretty large, however still +more efficient to retrieve compared to hydrating objects. + +### Find by Primary Key + The next Use-Case is displaying a Bug by primary key. This could be done using DQL as in the previous example with a where clause, however there is a convenience method on the Entity Manager that handles loading by primary key, which we have already seen in the write scenarios: @@ -619,8 +657,10 @@ However we will soon see another problem with our entities using this approach. echo "Engineer: ".$bug->getEngineer()->name."\n"; It will be null! What is happening? It worked in the previous example, so it can't be a problem with the persistance -code of Doctrine. You walked in the public property trap. Since we only retrieved the bug by primary key both the -engineer and reporter are not immediately loaded from the database but are replaced by LazyLoading proxies. Sample +code of Doctrine. What is it then? You walked in the public property trap. + +Since we only retrieved the bug by primary key both the engineer and reporter are not immediately loaded +from the database but are replaced by LazyLoading proxies. Sample code of this proxy generated code can be found in the specified Proxy Directory, it looks like: [php] @@ -647,8 +687,8 @@ code of this proxy generated code can be found in the specified Proxy Directory, } See how upon each method call the proxy is lazily loaded from the database? Using public properties however -we never call a method and Doctrine has no way to hook into the PHP Engine to detect this access and trigger -the lazy load. We need to revise our entities, make all the properties private or protected and add getters +we never call a method and Doctrine has no way to hook into the PHP Engine to detect a direct access to a public property +and trigger the lazy load. We need to rewrite our entities, make all the properties private or protected and add getters and setters to get a working example: [php] @@ -660,6 +700,11 @@ and setters to get a working example: Engineer: beberlei */ +Being required to use private or protected properties Doctrine 2 actually enforces you to encapsulate +your objects according to object-oriented best-practices. + +## Dashboard of the User + For the next use-case we want to retrieve the dashboard view, a list of all open bugs the user reported or was assigned to. This will be achieved using DQL again, this time with some WHERE clauses and usage of bound parameters: @@ -679,6 +724,23 @@ was assigned to. This will be achieved using DQL again, this time with some WHER That is it for the read-scenarios of this example, we will continue with the last missing bit, engineers being able to close a bug. +## Number of Bugs + +Until now we only retrieved entities or their array representation. Doctrine also supports the retrieval +of non-entities through DQL. These values are called "scalar result values" and may even be aggregate +values using COUNT, SUM, MIN, MAX or AVG functions. + +We will need this knowledge to retrieve the number of open bugs grouped by product: + + [php] + $dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ". + "JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id"; + $productBugs = $em->createQuery($dql)->getScalarResult(); + + foreach($productBugs as $productBug) { + echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n"; + } + ## Updating Entities There is a single use-case missing from the requirements, Engineers should be able to close a bug. This looks like: From 88e5babb56d44acb4f39c929cb0990788c84532d Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Thu, 17 Jun 2010 10:01:49 -0400 Subject: [PATCH 049/430] [DDC-572] Updating documentation for inversedBy and mappedBy --- manual/en/annotations-reference.txt | 38 +++++++++++++++++++++++++++-- manual/en/association-mapping.txt | 3 +-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/manual/en/annotations-reference.txt b/manual/en/annotations-reference.txt index cf5eece3b..f11983daa 100644 --- a/manual/en/annotations-reference.txt +++ b/manual/en/annotations-reference.txt @@ -334,6 +334,7 @@ Optional attributes: * cascade - Cascade Option * fetch - One of LAZY or EAGER +* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. Example: @@ -356,15 +357,21 @@ Required attributes: Optional attributes: * mappedBy - This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship. +* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. * cascade - Cascade Option * fetch - One of LAZY or EAGER +> **NOTE** +> For ManyToMany bidirectional relationships either side may be the owning side (the side +> that defines the @JoinTable and/or does not make use of the mappedBy attribute, thus +> using a default join table). + Example: /** * Owning Side * - * @ManyToMany(targetEntity="Group") + * @ManyToMany(targetEntity="Group", inversedBy="features") * @JoinTable(name="user_groups", * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} @@ -390,7 +397,7 @@ The @MappedSuperclass annotation cannot be used in conjunction with @Entity. See section for [more details on the restrictions of mapped superclasses](/../inheritance-mapping#mapped-superclasses). -+++ @OnetoOne ++++ @OneToOne The @OneToOne annotation works almost exactly as the [@ManyToOne](#ann_manytoone) with one additional option that can be specified. The configuration defaults for [@JoinColumn](#ann_joincolumn) using the target entity table and primary key column names @@ -406,10 +413,37 @@ Optional attributes: * fetch - One of LAZY or EAGER * orphanRemoval - Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any owning instance, should be removed by Doctrine. Defaults to false. +* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. + +Example: + + /** + * @OneToOne(targetEntity="Customer") + * @JoinColumn(name="customer_id", referencedColumnName="id") + */ + private $customer; +++ @OneToMany +Required attributes: + +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. + +Optional attributes: + +* cascade - Cascade Option +* orphanRemoval - Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any +owning instance, should be removed by Doctrine. Defaults to false. +* mappedBy - This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship. + +Example: + + /** + * @OneToMany(targetEntity="Phonenumber", mappedBy="user", cascade={"persist", "remove", "merge"}, orphanRemoval=true) + */ + public $phonenumbers; + +++ @OrderBy diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index d89756220..a7df2904e 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -9,7 +9,6 @@ When mapping bidirectional associations it is important to understand the concep * A unidirectional relationship only has an owning side. * The owning side of a relationship determines the updates to the relationship in the database. - The following rules apply to *bidirectional* associations: * The inverse side of a bidirectional relationship must refer to its owning side by use of the mappedBy attribute of the OneToOne, OneToMany, or ManyToMany mapping declaration. The mappedBy attribute designates the field in the entity that is the owner of the relationship. @@ -27,7 +26,7 @@ in the object world. There are 2 references on each side of the association and these 2 references both represent the same association but can change independently of one another. Of course, in a correct application the semantics of the bidirectional association are properly maintained by the application -developer (that's his responsiblity). Doctrine needs to know which of +developer (that's his responsibility). Doctrine needs to know which of these two in-memory references is the one that should be persisted and which not. This is what the owning/inverse concept is mainly used for. From cc2d84c992f87d219959d5ca76b854d58c0ff48c Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Thu, 17 Jun 2010 10:07:18 -0400 Subject: [PATCH 050/430] Adding php syntax highlighting --- manual/en/annotations-reference.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/manual/en/annotations-reference.txt b/manual/en/annotations-reference.txt index f11983daa..fa154687a 100644 --- a/manual/en/annotations-reference.txt +++ b/manual/en/annotations-reference.txt @@ -56,6 +56,7 @@ Optional attributes: Examples: + [php] /** * @Column(type="string", length=32, unique=true, nullable=false) */ @@ -85,6 +86,7 @@ can be found in the configuration section. Example: + [php] /** * @Entity * @ChangeTrackingPolicy("DEFERRED_IMPLICIT") @@ -116,6 +118,7 @@ an array as only argument which defines which class should be saved under which are the database value and values are the classes, either as fully- or as unqualified class names depending if the classes are in the namespace or not. + [php] /** * @Entity * @InheritanceType("JOINED") @@ -139,6 +142,7 @@ Optional attributes: Example: + [php] /** * @Entity(repositoryClass="MyProject\UserRepository") */ @@ -161,6 +165,7 @@ Required attributes: Example: + [php] /** * @Id * @Column(type="integer") @@ -177,6 +182,7 @@ callback annotations set on at least one of its methods. Using @PostLoad, @PrePe Example: + [php] /** * @Entity * @HasLifecycleCallbacks @@ -203,6 +209,7 @@ Required attributes: Example: + [php] /** * @Entity * @Table(name="ecommerce_products",indexes={@index(name="search_idx", columns={"name", "email"})}) @@ -220,6 +227,7 @@ identifier columns each column has to be marked with @Id. Example: + [php] /** * @Id * @Column(type="integer") @@ -237,6 +245,7 @@ This annotation has always been used in conjunction with the [@DiscriminatorMap] Examples: + [php] /** * @Entity * @InheritanceType("SINGLE_TABLE") @@ -281,6 +290,7 @@ Optional attributes: Example: + [php] /** * @OneToOne(targetEntity="Customer") * @JoinColumn(name="customer_id", referencedColumnName="id") @@ -312,6 +322,7 @@ Optional attributes: Example: + [php] /** * @ManyToMany(targetEntity="Phonenumber") * @JoinTable(name="users_phonenumbers", @@ -338,6 +349,7 @@ Optional attributes: Example: + [php] /** * @ManyToOne(targetEntity="Cart", cascade="ALL", fetch="EAGER") */ @@ -368,6 +380,7 @@ Optional attributes: Example: + [php] /** * Owning Side * @@ -417,6 +430,7 @@ owning instance, should be removed by Doctrine. Defaults to false. Example: + [php] /** * @OneToOne(targetEntity="Customer") * @JoinColumn(name="customer_id", referencedColumnName="id") @@ -439,6 +453,7 @@ owning instance, should be removed by Doctrine. Defaults to false. Example: + [php] /** * @OneToMany(targetEntity="Phonenumber", mappedBy="user", cascade={"persist", "remove", "merge"}, orphanRemoval=true) */ @@ -455,6 +470,7 @@ This annotation requires a single non-attributed value with an DQL snippet: Example: + [php] /** * @ManyToMany(targetEntity="Group") * @OrderBy({"name" = "ASC"}) @@ -519,6 +535,7 @@ Optional attributes: Example: + [php] /** * @Id * @GeneratedValue(strategy="SEQUENCE") @@ -545,6 +562,7 @@ Optional attributes: Example: + [php] /** * @Entity * @Table(name="user", @@ -568,6 +586,7 @@ Required attributes: Example: + [php] /** * @Entity * @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "email"})}) @@ -584,6 +603,7 @@ It only works on [@Column](#ann_column) annotations that have the type integer o Example: + [php] /** * @column(type="integer") * @version From c7a5a695d367be433cd9aa28060bb352ce3c1bbd Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Thu, 17 Jun 2010 12:03:01 -0400 Subject: [PATCH 051/430] [DDC-246] Added section showing DQL examples when dealing with SINGLE_TABLE and JOINED inheritance since it affects the generated SQL of a DQL query. --- manual/en/dql-doctrine-query-language.txt | 117 ++++++++++++++++++++++ manual/en/inheritance-mapping.txt | 2 +- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index ab77e1476..0ef794994 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -452,6 +452,123 @@ We will register the function by calling and can then use it: \Doctrine\ORM\Query\Parser::registerNumericFunction('FLOOR', 'MyProject\Query\MysqlFloor'); $dql = "SELECT FLOOR(person.salary * 1.75) FROM CompanyPerson person"; +++ Querying Inherited Classes + +This section demonstrates how you can query inherited classes and what type of results +to expect. + ++++ Single Table + +[Single Table Inheritance](http://martinfowler.com/eaaCatalog/singleTableInheritance.html) is an inheritance mapping strategy where all classes of a hierarchy are mapped to a single database table. In order to distinguish which row represents which type in the hierarchy a so-called discriminator column is used. + +First we need to setup an example set of entities to use. In this scenario it is a generic +Person and Employee example: + + [php] + namespace Entities; + + /** + * @Entity + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + protected $id; + + /** + * @Column(type="string", length=50) + */ + protected $name; + + // ... + } + + /** + * @Entity + */ + class Employee extends Person + { + /** + * @Column(type="string", length=50) + */ + private $department; + + // ... + } + +First notice that the generated SQL to create the tables for these entities looks like +the following: + + [sql] + CREATE TABLE Person (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(50) NOT NULL, discr VARCHAR(255) NOT NULL, department VARCHAR(50) NOT NULL) + +Now when persist a new `Employee` instance it will set the discriminator value for us +automatically: + + [php] + $employee = new \Entities\Employee(); + $employee->setName('test'); + $employee->setDepartment('testing'); + $em->persist($employee); + $em->flush(); + +Now lets run a simple query to retrieve the `Employee` we just created: + + [php] + $query = $em->createQuery('select e from Entities\Employee e where e.name = ?1'); + $query->setParameter('1', 'test'); + +If we check the generated SQL you will notice it has some special conditions added to +ensure that we will only get back `Employee` entities: + + [php] + echo $query->getSql(); + + // SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, p0_.discr AS discr3 FROM Person p0_ WHERE (p0_.name = ?) AND p0_.discr IN ('employee') + ++++ Class Table Inheritance + +[Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html) is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. +Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hierarchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance. + +The example for class table inheritance is the same as single table, you just need to +change the inheritance type from `SINGLE_TABLE` to `JOINED`: + + /** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + */ + class Person + { + // ... + } + +Now take a look at the SQL which is generated to create the table, you'll notice some +differences: + + [sql] + CREATE TABLE Person (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(50) NOT NULL, discr VARCHAR(255) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB; + CREATE TABLE Employee (id INT NOT NULL, department VARCHAR(50) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB; + ALTER TABLE Employee ADD FOREIGN KEY (id) REFERENCES Person(id) ON DELETE CASCADE + +* The data is split between two tables +* A foreign key exists between the two tables + +Now if were to insert the same `Employee` as we did in the `SINGLE_TABLE` example and run +the same example query it will generate different SQL joining the `Person` information +automatically for you: + + [sql] + SELECT p0_.id AS id0, p0_.name AS name1, e1_.department AS department2, p0_.discr AS discr3 FROM Employee e1_ INNER JOIN Person p0_ ON e1_.id = p0_.id WHERE p0_.name = ? + ++ The Query class An instance of the `Doctrine\ORM\Query` class represents a DQL query. You create a Query instance be calling `EntityManager#createQuery($dql)`, passing the DQL query string. Alternatively you can create an empty `Query` instance and invoke `Query#setDql($dql)` afterwards. Here are some examples: diff --git a/manual/en/inheritance-mapping.txt b/manual/en/inheritance-mapping.txt index 70a87afa4..b6a4b49ef 100644 --- a/manual/en/inheritance-mapping.txt +++ b/manual/en/inheritance-mapping.txt @@ -97,7 +97,7 @@ This strategy is very efficient for querying across all types in the hierarchy o ++ Class Table Inheritance [Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html) is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. -Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hieararchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance. +Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hierarchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance. Example: From e71f00296ec1181cb9434e6ed53b2e4b5ac45fbd Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Thu, 17 Jun 2010 12:07:43 -0400 Subject: [PATCH 052/430] Fixing php code --- manual/en/dql-doctrine-query-language.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index 0ef794994..1c5b198b1 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -540,6 +540,7 @@ Doctrine 2 implements this strategy through the use of a discriminator column in The example for class table inheritance is the same as single table, you just need to change the inheritance type from `SINGLE_TABLE` to `JOINED`: + [php] /** * @Entity * @InheritanceType("JOINED") From 27233af0d27cc9e29928e6b5f9a7fc5381be6b43 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Thu, 17 Jun 2010 12:09:11 -0400 Subject: [PATCH 053/430] Removing reference to Query object before it is introduced --- manual/en/dql-doctrine-query-language.txt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index 1c5b198b1..9b5210a8f 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -520,17 +520,14 @@ automatically: Now lets run a simple query to retrieve the `Employee` we just created: - [php] - $query = $em->createQuery('select e from Entities\Employee e where e.name = ?1'); - $query->setParameter('1', 'test'); + [sql] + select e from Entities\Employee e where e.name = 'test' If we check the generated SQL you will notice it has some special conditions added to ensure that we will only get back `Employee` entities: - [php] - echo $query->getSql(); - - // SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, p0_.discr AS discr3 FROM Person p0_ WHERE (p0_.name = ?) AND p0_.discr IN ('employee') + [sql] + SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, p0_.discr AS discr3 FROM Person p0_ WHERE (p0_.name = ?) AND p0_.discr IN ('employee') +++ Class Table Inheritance From 823db6071b06da0f09f94f015037237bdc8c7573 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Thu, 17 Jun 2010 13:32:11 -0400 Subject: [PATCH 054/430] Changing case. --- manual/en/dql-doctrine-query-language.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index 9b5210a8f..3e0a5cab5 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -521,7 +521,7 @@ automatically: Now lets run a simple query to retrieve the `Employee` we just created: [sql] - select e from Entities\Employee e where e.name = 'test' + SELECT e FROM Entities\Employee e WHERE e.name = 'test' If we check the generated SQL you will notice it has some special conditions added to ensure that we will only get back `Employee` entities: From 16740b9695846c00220c9450f2b00232fe82121f Mon Sep 17 00:00:00 2001 From: Lars Olesen Date: Wed, 19 May 2010 00:44:47 +0800 Subject: [PATCH 055/430] Fixes typo. --- manual/en/tools.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/tools.txt b/manual/en/tools.txt index 22321bbe4..24c380914 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -227,7 +227,7 @@ Now you can get an exporter instance and export the loaded metadata to yml: [php] $exporter = $cme->getExporter('yml', '/path/to/export/yml'); - $exporter->setMetadata($metadatas); + $exporter->setMetadata($metadata); $exporter->export(); You can also reverse engineer a database using the `orm:convert-mapping` command: From 8e7385ccc208fe1849b875e22a39f818691ecc51 Mon Sep 17 00:00:00 2001 From: Lars Olesen Date: Wed, 19 May 2010 21:49:14 +0800 Subject: [PATCH 056/430] Added the name space in front of Xmldriver, as it is not stated anywhere on this page which namespace is used. --- manual/en/xml-mapping.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/xml-mapping.txt b/manual/en/xml-mapping.txt index 0db537be7..063b2cc89 100644 --- a/manual/en/xml-mapping.txt +++ b/manual/en/xml-mapping.txt @@ -27,7 +27,7 @@ It is recommended to put all XML mapping documents in a single folder but you ca [php] // $config instanceof Doctrine\ORM\Configuration - $driver = new XmlDriver(array('/path/to/files')); + $driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files')); $config->setMetadataDriverImpl($driver); From 80fd691880b17d2d1699591be71120547e43baea Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 3 Jul 2010 16:57:02 +0200 Subject: [PATCH 057/430] Update docs on using own types, aswell as on using Collection::clear() --- manual/en/basic-mapping.txt | 13 +++++++++++++ manual/en/working-with-objects.txt | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 70a098e26..8acf712eb 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -71,6 +71,8 @@ For example, the Doctrine Mapping Type `string` defines the mapping from a PHP s * `time`: Type that maps an SQL TIME to a PHP DateTime object. * `datetime`: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object. * `text`: Type that maps an SQL CLOB to a PHP string. +* `object`: Type that maps a SQL CLOB to a PHP object using `serialize()` and `unserialize()` +* `array`: Type that maps a SQL CLOB to a PHP object using `serialize()` and `unserialize()` > **NOTE** > Doctrine Mapping Types are NOT SQL types and NOT PHP types! They are mapping types @@ -189,6 +191,17 @@ for the mapping type and map that to the corresponding fully qualified class nam private $field; } +To have Schema-Tool convert the underlying database type of your new "mytype" directly into an instance of `MyType` +you have to additionally register this mapping with your database platform: + + [php] + $conn = $em->getConnection(); + $conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype'); + +Now using Schema-Tool, whenever it detects a column having the `db_mytype` it will convert it into a `mytype` +Doctrine Type instance for Schema representation. Keep in mind that you can easily produce clashes this way, +each database type can only map to exactly one Doctrine mapping type. + ++ Identifiers / Primary Keys Every entity class needs an identifier/primary key. You designate the field that serves as the identifier with the `@Id` marker annotation. Here is an example: diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 588082e11..e69adacdb 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -256,6 +256,15 @@ that has O(n) complexity, where n is the size of the map. Since Doctrine always only looks at the owning side of a bidirectional association, it is essentially not necessary that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. +> **NOTE* +> +> You can also clear the contents of a whole collection using the `Collections::clear()` method. You +> should be aware that using this method can lead to a straight and optimized database delete or update call +> during the flush operation that is not aware of entities that have been re-added to the collection. +> +> Say you clear a collection of tags by calling `$post->getTags()->clear();` and then call +> `$post->getTags()->add($tag)`. This will not recognize tag being already added before and issue +> two database calls. ++ Association Management Methods From 8d2da96480cd78313e4a55afd755952e454997fe Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 4 Jul 2010 18:57:51 +0200 Subject: [PATCH 058/430] Fixed error in Sandbox Tutorial --- manual/en/introduction.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index 76206e811..e2545afbc 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -185,7 +185,7 @@ Here is a short overview of the purpose of these folders and files: 1) From within the tools/sandbox folder, run the following command and you should see the same output. - $ php doctrine orm:schema-tool:create ./Entities + $ php doctrine orm:schema-tool:create Creating database schema... Database schema created successfully! From 400be2a3e68b6360ecdba04ec14cfc634bce4571 Mon Sep 17 00:00:00 2001 From: eXtreme Date: Wed, 7 Jul 2010 19:10:48 +0800 Subject: [PATCH 059/430] fixing codeblocks --- manual/en/dql-doctrine-query-language.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index 3e0a5cab5..e2d9e6cdb 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -188,11 +188,12 @@ With Nested Conditions in WHERE Clause: With COUNT DISTINCT: [php] - $query = $em->createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser + $query = $em->createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser'); $users = $query->getResult(); // array of ForumUser objects With Arithmetic Expression in WHERE clause: + [php] $query = $em->createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); $users = $query->getResult(); // array of ForumUser objects From 59aafe100b07cc3d67f5ad12de91a563aeb8b874 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 7 Jul 2010 22:34:51 +0200 Subject: [PATCH 060/430] Added Aggregate Fields documentation --- cookbook/en.txt | 3 +- cookbook/en/aggregate-fields.txt | 320 +++++++++++++++++++++++++++++++ 2 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 cookbook/en/aggregate-fields.txt diff --git a/cookbook/en.txt b/cookbook/en.txt index 4f059126e..7240ec98d 100644 --- a/cookbook/en.txt +++ b/cookbook/en.txt @@ -7,4 +7,5 @@ + DQL Custom Walkers + DQL User Defined Functions + SQL Table Prefixes -+ Strategy Cookbook Introduction \ No newline at end of file ++ Strategy Cookbook Introduction ++ Aggregate Fields \ No newline at end of file diff --git a/cookbook/en/aggregate-fields.txt b/cookbook/en/aggregate-fields.txt new file mode 100644 index 000000000..406003e06 --- /dev/null +++ b/cookbook/en/aggregate-fields.txt @@ -0,0 +1,320 @@ +# Aggregate Fields + +You will is often the requirement to display aggregate values of data that +can be computed by using the MIN, MAX, COUNT or SUM SQL functions. Doctrine 2 +offers several ways to get access to these values and this article will +describe all of them from different perspectives. + +You will see that aggregate fields can become very explicit +features in your domain model and how this potentially complex business rules +can be easily tested. + +## An example model + +Say you want to model a bank account and all their entries. Entries +into the account can either be of positive or negative money values. +Each account has a credit limit and the account is never allowed +to have a balance below that value. + +For simplicity we live in a world were money is composed of integers +only. Also we omit the receiver/sender name, stated reason for transfer +and the execution date. These all would have to be added on the `Entry` +object. + +Our entities look like: + + [php] + namespace Bank\Entities; + + /** + * @Entity + */ + class Account + { + /** @Id @GeneratedValue @Column(type="integer") */ + private $id; + + /** @Column(type="string", unique=true) + private $no; + + /** + * @OneToMany(targetEntity="Entry", mappedBy="entries", cascade={"persist"}) + */ + private $entries; + + /** + * @Column(type="integer") + */ + private $maxCredit = 0; + + public function __construct($no, $maxCredit = 0) + { + $this->no = $no; + $this->maxCredit = $maxCredit; + $this->entries = new \Doctrine\Common\Collections\ArrayCollection(); + } + } + + /** + * @Entity + */ + class Entry + { + /** @Id @GeneratedValue @Column(type="integer") */ + private $id; + + /** + * @ManyToOne(targetEntity="Account", inversedBy="entries") + */ + private $account; + + /** + * @Column(type="integer") + */ + private $amount; + + public function __construct($account, $amount) + { + $this->account = $account; + $this->amount = $amount; + // more stuff here, from/to whom, stated reason, execution date and such + } + + public function getAmount() + { + return $this->amount; + } + } + +## Using DQL + +The Doctrine Query Language allows you to select for aggregate values computed from +fields of your Domain Model. You can select the current balance of your account by +calling: + + [php] + $dql = "SELECT SUM(e.amount) AS balance FROM Bank\Entities\Entry e " . + "WHERE e.account = ?1"; + $balance = $em->createQuery($dql) + ->setParameter(1, $myAccountId) + ->getSingleScalarResult(); + +The `$em` variable in this (and forthcoming) example holds the Doctrine `EntityManager`. +We create a query for the SUM of all amounts (negative amounts are withdraws) and +retrieve them as a single scalar result, essentially return only the first column +of the first row. + +This approach is simple and powerful, however it has a serious drawback. We have +to execute a specific query for the balance whenever we need it. + +To implement a powerful domain model we would rather have access to the balance from +our `Account` entity during all times (even if the Account was not persisted +in the database before!). + +Also an additional requirement is the max credit per `Account` rule. + +We cannot reliably enforce this rule in our `Account` entity with the DQL retrieval +of the balance. There are many different ways to retrieve accounts. We cannot +guarantee that we can execute the aggregation query for all these use-cases, +let alone that a userland programmer checks this balance against newly added +entries. + +## Using your Domain Model + +`Account` and all the `Entry` instances are connected through a collection, +which means we can compute this value at runtime: + + [php] + class Account + { + // .. previous code + public function getBalance() + { + $balance = 0; + foreach ($this->entries AS $entry) { + $balance += $entry->getAmount(); + } + return $balance; + } + } + +Now we can always call `Account::getBalance()` to access the current account balance. + +To enforce the max credit rule we have to implement the "Aggregate Root" pattern as +described in Eric Evans book on Domain Driven Design. Described with one sentence, +an aggregate root controls the instance creation, access and manipulation of its children. + +In our case we want to enforce that new entries can only added to the `Account` by +using a designated method. The `Account` is the aggregate root of this relation. +We can also enforce the correctness of the bi-directional `Account` <-> `Entry` +relation with this method: + + [php] + class Account + { + public function addEntry($amount) + { + $this->assertAcceptEntryAllowed($amount); + + $e = new Entry($e, $amount); + $this->entries[] = $e; + return $e; + } + } + +Now look at the following test-code for our entities: + + [php] + class AccountTest extends \PHPUnit_Framework_TestCase + { + public function testAddEntry() + { + $account = new Account("123456", $maxCredit = 200); + $this->assertEquals(0, $account->getBalance()); + + $account->addEntry(500); + $this->assertEquals(500, $account->getBalance()); + + $account->addEntry(-700); + $this->assertEquals(-200, $account->getBalance()); + } + + public function testExceedMaxLimit() + { + $account = new Account("123456", $maxCredit = 200); + + $this->setExpectedException("Exception"); + $account->addEntriy(-1000); + } + } + +To enforce our rule we can now implement the assertion in `Account::addEntry`: + + [php] + class Account + { + private function assertAcceptEntryAllowed($amount) + { + $futureBalance = $this->getBalance() + $amount; + $allowedMinimalBalance = ($this->maxCredit * -1); + if ($futureBalance < $allowedMinimalBalance) { + throw new Exception("Credit Limit exceeded, entry is not allowed!"); + } + } + } + +We haven't talked to the entity manager for persistence of our account example before. +You can call `EntityManager::persist($account)` and then `EntityManager::flush()` +at any point to save the account to the database. All the nested `Entry` objects +are automatically flushed to the database also. + + [php] + $account = new Account("123456", 200); + $account->addEntry(500); + $account->addEntry(-200); + $em->persist($account); + $em->flush(); + +The current implementation has a considerable drawback. To get the balance, we +have to initialize the complete `Account::$entries` collection, possibly a very +large one. This can considerably hurt the performance of your application. + +## Using an Aggregate Field + +To overcome the previously mentioned issue (initializing the whole entries collection) +we want to add an aggregate field called "balance" on the Account and adjust the +code in `Account::getBalance()` and `Account:addEntry()`: + + [php] + class Account + { + /** + * @Column(type="integer") + */ + private $balance = 0; + + public function getBalance() + { + return $this->balance; + } + + public function addEntry($amount) + { + $this->assertAcceptEntryAllowed($amount); + + $e = new Entry($e, $amount); + $this->entries[] = $e; + $this->balance += $amount; + return $e; + } + } + +This is a very simple change, but all the tests still pass. Our account entities return +the correct balance. Now calling the `Account::getBalance()` method will not occour the +overhead of loading all entries anymore. Adding a new Entry to the `Account::$entities` +will also not initialize the collection internally. + +Adding a new entry is therefore very performant and explictly hooked into the domain model. +It will only update the account with the current balance and insert the new entry into the database. + +## Tackling Race Conditions with Aggregate Fields + +Whenever you denormalize your database schema race-conditions can potentially lead to +inconsistent state. See this example: + + [php] + // The Account $accId has a balance of 0 and a max credit limit of 200: + // request 1 account + $account1 = $em->find('Bank\Entities\Account', $accId); + + // request 2 account + $account2 = $em->find('Bank\Entities\Account', $accId); + + $account1->addEntry(-200); + $account2->addEntry(-200); + + // now request 1 and 2 both flush the changes. + +The aggregate field `Account::$balance` is now -200, however the SUM over all +entries amounts yields -400. A violation of our max credit rule. + +You can use both optimistic or pessimistic locking to save-guard +your aggregate fields against this kind of race-conditions. Reading Eric Evans +DDD carefully he mentions that the "Aggregate Root" (Account in our example) +needs a locking mechanism. + +Optimistic locking is as easy as adding a version column: + + [php] + class Amount + { + /** @Column(type="integer") @Version */ + private $version; + } + +The previous example would then throw an exception in the face of whatever request +saves the entity last (and would create the inconsistent state). + +Pessimmistic locking requires an additional flag set on the `EntityManager::find()` +call, enabling write locking directly in the database using a FOR UPDATE. + + [php] + use Doctrine\DBAL\LockMode; + + $account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ); + +## Keeping Updates and Deletes in Sync + +The example shown in this article does not allow changes to the value in `Entry`, +which considerably simplifies the effort to keep `Account::$balance` in sync. +If your use-case allows fields to be updated or related entities to be removed +you have to encapsulate this logic in your "Aggregate Root" entity and adjust +the aggregate field accordingly. + +## Conclusion + +This article described how to obtain aggregate values using DQL or your domain model. +It showed how you can easily add an aggregate field that offers serious performance +benefits over iterating all the related objects that make up an aggregate value. +Finally I showed how you can ensure that your aggregate fields do not get out +of sync due to race-conditions and concurrent access. \ No newline at end of file From 0428774ec875e5cb34d5cb88a623ca40db318cf2 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 8 Jul 2010 00:26:02 +0200 Subject: [PATCH 061/430] Added more information about flushing semantics. --- manual/en/working-with-objects.txt | 159 ++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 51 deletions(-) diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index e69adacdb..3955abfbb 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -1,4 +1,3 @@ -++ Understanding In this chapter we will help you understand the `EntityManager` and the `UnitOfWork`. A Unit of Work is similar to an object-level transaction. A new Unit of Work is @@ -8,49 +7,7 @@ implicity started when an EntityManager is initially created or after A Unit of Work can be manually closed by calling EntityManager#close(). Any changes to objects within this Unit of Work that have not yet been persisted -are lost. - -+++ The size of a Unit of Work - -The size of a Unit of Work mainly refers to the number of managed entities at -a particular point in time. - -+++ The cost of flush() - -How costly a flush operation is in terms of performance mainly depends on 2 factors: - -* The size of your current Unit of Work -* The configured change tracking policies - -You can get the size of your Unit of Work as follows: - - [php] - $uowSize = $em->getUnitOfWork()->size(); - -The size represents the number of managed entities in the Unit of Work. This -size affects the performance of flush() operations due to change tracking -(see "Change Tracking Policies") and, of course, memory consumption, so you -may want to check it from time to time during development. - -> **CAUTION** -> Do not invoke `flush` after every change to an entity or every single invocation of -> persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the -> performance of your application. Instead, form units of work that operate on your objects -> and call `flush` when you are done. While serving a single HTTP request there should -> be usually no need for invoking `flush` more than 0-2 times. - -+++ Direct access to a Unit of Work - -You can get direct access to the Unit of Work by calling `EntityManager#getUnitOfWork()`. -This will return the UnitOfWork instance the EntityManager is currently using. - - [php] - $uow = $em->getUnitOfWork(); - -> **NOTE** -> Directly manipulating a UnitOfWork is not recommended. When working directly with the -> UnitOfWork API, respect methods marked as INTERNAL by not using them and carefully read -> the API documentation. +are lost. ++ Persisting entities @@ -79,7 +36,7 @@ Example: > **CAUTION** > Generated entity identifiers / primary keys are guaranteed to be available after the > next successful flush operation that involves the entity in question. -> You can not reply on a generated identifier to be available directly after invoking `persist`. +> You can not rely on a generated identifier to be available directly after invoking `persist`. > The inverse is also true. You can not rely on a generated identifier being not available > after a failed flush operation. @@ -88,15 +45,12 @@ The semantics of the persist operation, applied on an entity X, are as follows: * If X is a new entity, it becomes managed. The entity X will be entered into the database as a result of the flush operation. * If X is a preexisting managed entity, it is ignored by the persist operation. However, the persist operation is cascaded to entities referenced by X, if the relationships from X to these other entities are mapped with cascade=PERSIST or cascade=ALL (see "Transitive Persistence"). * If X is a removed entity, it becomes managed. -* If X is a detached entity, the behavior is undefined. - -> **CAUTION** -> Do not pass detached entities to the persist operation. +* If X is a detached entity, an exception will be thrown on flush. ++ Removing entities -An entity can be removed from persistent storage by passing it to the `EntityManager#remove($entity)` method. By applying the `remove` operation on some entity, that entity becomes REMOVED, which means that its persistent state will be deleted once `EntityManager#flush()` is invoked. The in-memory state of an entity is unaffected by the `remove` operation. +An entity can be removed from persistent storage by passing it to the `EntityManager#remove($entity)` method. By applying the `remove` operation on some entity, that entity becomes REMOVED, which means that its persistent state will be deleted once `EntityManager#flush()` is invoked. > **CAUTION** > Just like `persist`, invoking `remove` on an entity does NOT cause an immediate SQL @@ -115,7 +69,9 @@ The semantics of the remove operation, applied to an entity X are as follows: * If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). * If X is a detached entity, an InvalidArgumentException will be thrown. * If X is a removed entity, it is ignored by the remove operation. -* A removed entity X will be removed from the database as a result of the flush operation. +* A removed entity X will be removed from the database as a result of the flush operation. + +After an entity has been removed its in-memory state is the same as before the removal, except for generated identifiers. ++ Detaching entities @@ -340,6 +296,107 @@ Do not blindly apply cascade=all to all associations as it will unnecessarily degrade the performance of your application. +++ Synchronization with the Database + +The state of persistent entities is synchronized with the database on flush of an `EntityManager` +which commits the underlying `UnitOfWork`. The synchronization involves writing any updates to +persistent entities and their relationships to the database. Thereby bidirectional relationships +are persisted based on the references held by the owning side of the relationship as explained +in the Association Mapping chapter. + +The flush operation applies to a managed entity with the following semantics: + +* The entity itself is synchronized to the database, if it has changed. + +For all (initialized) relationships of the entity the following semantics apply to each +associated entity X: + +* If X is new and persist operations are configured to cascade on the relationship, + X will be persisted. +* If X is new and no persist operations are configured to cascade on the relationship, + an exception will be thrown as this indicates a programming error. +* If X is removed and persist operations are configured to cascade on the relationship, + an exception will be thrown as this indicates a programming error (X would be re-persisted by the cascade). +* If X is detached and persist operations are configured to cascade on the relationship, + an exception will be thrown (This is semantically the same as passing X to persist()). + +The flush operation applies to a removed entity by deleting its persistent state from the database. +No cascade options are relevant for removed entities on flush. + ++++ The size of a Unit of Work + +The size of a Unit of Work mainly refers to the number of managed entities at +a particular point in time. + ++++ The cost of flushing + +How costly a flush operation is, mainly depends on two factors: + +* The size of the EntityManager's current UnitOfWork. +* The configured change tracking policies + +You can get the size of a UnitOfWork as follows: + + [php] + $uowSize = $em->getUnitOfWork()->size(); + +The size represents the number of managed entities in the Unit of Work. This +size affects the performance of flush() operations due to change tracking +(see "Change Tracking Policies") and, of course, memory consumption, so you +may want to check it from time to time during development. + +> **CAUTION** +> Do not invoke `flush` after every change to an entity or every single invocation of +> persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the +> performance of your application. Instead, form units of work that operate on your objects +> and call `flush` when you are done. While serving a single HTTP request there should +> be usually no need for invoking `flush` more than 0-2 times. + ++++ Direct access to a Unit of Work + +You can get direct access to the Unit of Work by calling `EntityManager#getUnitOfWork()`. +This will return the UnitOfWork instance the EntityManager is currently using. + + [php] + $uow = $em->getUnitOfWork(); + +> **NOTE** +> Directly manipulating a UnitOfWork is not recommended. When working directly with the +> UnitOfWork API, respect methods marked as INTERNAL by not using them and carefully read +> the API documentation. + ++++ Entity State + +As outlined in the architecture overview an entity can be in one of four possible states: +NEW, MANAGED, REMOVED, DETACHED. If you explicitly need to find out what the current state +of an entity is in the context of a certain `EntityManager` you can ask the underlying +`UnitOfWork`: + + [php] + switch ($em->getUnitOfWork()->getEntityState($entity)) { + case UnitOfWork::MANAGED: + ... + case UnitOfWork::REMOVED: + ... + case UnitOfWork::DETACHED: + ... + case UnitOfWork::NEW: + ... + } + +An entity is in MANAGED state if it is associated with an `EntityManager` and it is not REMOVED. + +An entity is in REMOVED state after it has been passed to `EntityManager#remove()` until the +next flush operation of the same EntityManager. A REMOVED entity is still associated with an +`EntityManager` until the next flush operation. + +An entity is in DETACHED state if it has persistent state and identity but is currently not +associated with an `EntityManager`. + +An entity is in NEW state if has no persistent state and identity and is not associated with an +`EntityManager` (for example those just created via the "new" operator). + + ++ Querying Doctrine 2 provides the following ways, in increasing level of power and flexibility, to query for persistent objects. You should always start with the simplest one that suits your needs. From 1017e2c6c7ceacf73924dbad0ed76505f2ccf6c8 Mon Sep 17 00:00:00 2001 From: beberlei Date: Sat, 10 Jul 2010 08:33:12 +0200 Subject: [PATCH 062/430] Extended details on how to remove entities and their associations --- manual/en/working-with-objects.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 588082e11..29f9cfab5 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -117,6 +117,30 @@ The semantics of the remove operation, applied to an entity X are as follows: * If X is a removed entity, it is ignored by the remove operation. * A removed entity X will be removed from the database as a result of the flush operation. +Removing an entity will also automatically delete any exisiting records in many-to-many +join tables that link this entity. The action taken depends on the value of the `@joinColumn` +mapping attribute "onDelete". Either Doctrine issues a dedicated `DELETE` statement +for records of each join table or it depends on the foreign key semantics of +onDelete="CASCADE". + +Deleting an object with all its associated objects can be achieved in multiple +ways with very different performance impacts. + +1. If an association is marked as `CASCADE=REMOVE` Doctrine 2 will fetch this + association. If its a Single association it will pass this entity to + ´EntityManager#remove()`. If the association is a collection, Doctrine will loop over all + its elements and pass them to `EntityManager#remove()`. In both cases the + cascade remove semantics are applied recursively. For large object graphs + this removal strategy can be very costly. +2. Using a DQL `DELETE` statement allows you to delete multiple entities of a + type with a single command and without hydrating these entities. This + can be very efficient to delete large object graphs from the database. +3. Using foreign key semantics `onDelete="CASCADE"` can force the database + to remove all associated objects internally. This strategy is a bit + tricky to get right but can be very powerful and fast. You should be aware + however that using strategy 1 (`CASCADE=REMOVE`) completly by-passes + any foreign key `onDelete=CASCADE` option, because Doctrine will fetch and remove + all associated entities explicitly nevertheless. ++ Detaching entities From 4c813f60f9d813495678a5e9f63b6cab2a91cd46 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 18 Jul 2010 21:12:51 +0200 Subject: [PATCH 063/430] Updated Aggregate Fields, Added generated SQL for all Association Mappings --- cookbook/en/aggregate-fields.txt | 14 +- manual/en/association-mapping.txt | 289 ++++++++++++++++++++++-------- 2 files changed, 221 insertions(+), 82 deletions(-) diff --git a/cookbook/en/aggregate-fields.txt b/cookbook/en/aggregate-fields.txt index 406003e06..0dc5d18d7 100644 --- a/cookbook/en/aggregate-fields.txt +++ b/cookbook/en/aggregate-fields.txt @@ -1,9 +1,7 @@ # Aggregate Fields -You will is often the requirement to display aggregate values of data that -can be computed by using the MIN, MAX, COUNT or SUM SQL functions. Doctrine 2 -offers several ways to get access to these values and this article will -describe all of them from different perspectives. +You will often come across the requirement to display aggregate values of data that +can be computed by using the MIN, MAX, COUNT or SUM SQL functions. For any ORM this is a tricky issue traditionally. Doctrine 2 offers several ways to get access to these values and this article will describe all of them from different perspectives. You will see that aggregate fields can become very explicit features in your domain model and how this potentially complex business rules @@ -34,7 +32,7 @@ Our entities look like: /** @Id @GeneratedValue @Column(type="integer") */ private $id; - /** @Column(type="string", unique=true) + /** @Column(type="string", unique=true) */ private $no; /** @@ -156,7 +154,7 @@ relation with this method: { $this->assertAcceptEntryAllowed($amount); - $e = new Entry($e, $amount); + $e = new Entry($this, $amount); $this->entries[] = $e; return $e; } @@ -184,7 +182,7 @@ Now look at the following test-code for our entities: $account = new Account("123456", $maxCredit = 200); $this->setExpectedException("Exception"); - $account->addEntriy(-1000); + $account->addEntry(-1000); } } @@ -242,7 +240,7 @@ code in `Account::getBalance()` and `Account:addEntry()`: { $this->assertAcceptEntryAllowed($amount); - $e = new Entry($e, $amount); + $e = new Entry($this, $amount); $this->entries[] = $e; $this->balance += $amount; return $e; diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index a7df2904e..c1cd0edb8 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -81,27 +81,29 @@ This is essentially the same as the following, more verbose, mapping: The @JoinTable definition used for many-to-many mappings has similar defaults. As an example, consider this mapping: [php] - class User { - //... - /** @ManyToMany(targetEntity="Group") */ - private $groups; - //... + class User + { + //... + /** @ManyToMany(targetEntity="Group") */ + private $groups; + //... } This is essentially the same as the following, more verbose, mapping: [php] - class User { - //... - /** - * @ManyToMany(targetEntity="Group") - * @JoinTable(name="User_Group", - * joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")} - * ) - */ - private $groups; - //... + class User + { + //... + /** + * @ManyToMany(targetEntity="Group") + * @JoinTable(name="User_Group", + * joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")} + * ) + */ + private $groups; + //... } In that case, the name of the join table defaults to a combination of the simple, unqualified class names of the participating classes, separated by an underscore character. The names of the join columns default to the simple, unqualified class name of the targeted class followed by "_id". The referencedColumnName always defaults to "id", just as in one-to-one or many-to-one mappings. @@ -135,6 +137,20 @@ A unidirectional one-to-one association is very common. Here is an example of a Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. +Generated MySQL Schema: + + [sql] + CREATE TABLE Product ( + id INT AUTO_INCREMENT NOT NULL, + shipping_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE Shipping ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id); + ++ One-To-One, Bidirectional Here is a one-to-one relationship between a `Customer` and a `Cart`. The `Cart` @@ -170,18 +186,34 @@ has a reference back to the `Customer` so it is bidirectional. Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. +Generated MySQL Schema: + + [sql] + CREATE TABLE Cart ( + id INT AUTO_INCREMENT NOT NULL, + customer_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE Customer ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id); + +See how the foreign key is defined on the owning side of the relation, the table `Cart`. + ++ One-To-One, Self-referencing You can easily have self referencing one-to-one relationships like below. [php] /** @Entity */ - class Customer + class Student { // ... /** - * @OneToOne(targetEntity="Customer") + * @OneToOne(targetEntity="Student") * @JoinColumn(name="mentor_id", referencedColumnName="id") */ private $mentor; @@ -191,6 +223,16 @@ You can easily have self referencing one-to-one relationships like below. Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. +With the generated MySQL Schema: + + [sql] + CREATE TABLE Student ( + id INT AUTO_INCREMENT NOT NULL, + mentor_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Student ADD FOREIGN KEY (mentor_id) REFERENCES Student(id); + ++ One-To-Many, Unidirectional with Join Table A unidirectional one-to-many association can be mapped through a join table. From Doctrine's point of view, it is simply mapped as a unidirectional many-to-many whereby a unique constraint on one of the join columns enforces the one-to-many cardinality. @@ -200,18 +242,18 @@ The following example sets up such a unidirectional one-to-many association: /** @Entity */ class User { - // ... + // ... - /** - * @ManyToMany(targetEntity="Phonenumber") - * @JoinTable(name="users_phonenumbers", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} - * ) - */ - private $phonenumbers; + /** + * @ManyToMany(targetEntity="Phonenumber") + * @JoinTable(name="users_phonenumbers", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} + * ) + */ + private $phonenumbers; - // ... + // ... } /** @Entity */ @@ -223,6 +265,29 @@ The following example sets up such a unidirectional one-to-many association: > **NOTE** > One-To-Many uni-directional relations with join-table only work using the @ManyToMany annotation and a unique-constraint. +Generates the following MySQL Schema: + + [sql] + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + CREATE TABLE users_phonenumbers ( + user_id INT NOT NULL, + phonenumber_id INT NOT NULL, + UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id), + PRIMARY KEY(user_id, phonenumber_id) + ) ENGINE = InnoDB; + + CREATE TABLE Phonenumber ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id); + ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id); + ++ Many-To-One, Unidirectional You can easily implement a many-to-one unidirectional association with the following: @@ -237,7 +302,7 @@ You can easily implement a many-to-one unidirectional association with the follo * @ManyToOne(targetEntity="Address") * @JoinColumn(name="address_id", referencedColumnName="id") */ - private $address + private $address; } /** @Entity */ @@ -250,6 +315,22 @@ You can easily implement a many-to-one unidirectional association with the follo > The above `@JoinColumn` is optional as it would default to `address_id` and `id` > anyways. You can omit it and let it use the defaults. +Generated MySQL Schema: + + [sql] + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + address_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + CREATE TABLE Address ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id); + ++ One-To-Many, Bidirectional Bidirectional one-to-many associations are very common. The following code shows an example with a Product and a Feature class: @@ -280,6 +361,20 @@ Bidirectional one-to-many associations are very common. The following code shows Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. +Generated MySQL Schema: + + [sql] + CREATE TABLE Product ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE Feature ( + id INT AUTO_INCREMENT NOT NULL, + product_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id); + ++ One-To-Many, Self-referencing You can also setup a one-to-many association that is self-referencing. In this example we @@ -306,6 +401,16 @@ This effectively models a hierarchy of categories and from the database perspect Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. +Generated MySQL Schema: + + [sql] + CREATE TABLE Category ( + id INT AUTO_INCREMENT NOT NULL, + parent_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Category ADD FOREIGN KEY (parent_id) REFERENCES Category(id); + ++ Many-To-Many, Unidirectional Real many-to-many associations are less common. The following example shows a unidirectional association between User and Group entities: @@ -314,18 +419,18 @@ Real many-to-many associations are less common. The following example shows a un /** @Entity */ class User { - // ... + // ... - /** - * @ManyToMany(targetEntity="Group") - * @JoinTable(name="users_groups", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} - * ) - */ - private $groups; + /** + * @ManyToMany(targetEntity="Group") + * @JoinTable(name="users_groups", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} + * ) + */ + private $groups; - // ... + // ... } /** @Entity */ @@ -340,6 +445,25 @@ Real many-to-many associations are less common. The following example shows a un > class. Consequently, the direct many-to-many association disappears and is replaced > by one-to-many/many-to-one associations between the 3 participating classes. +Generated MySQL Schema: + + [sql] + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE users_groups ( + user_id INT NOT NULL, + group_id INT NOT NULL, + PRIMARY KEY(user_id, group_id) + ) ENGINE = InnoDB; + CREATE TABLE Group ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE users_groups ADD FOREIGN KEY (user_id) REFERENCES User(id); + ALTER TABLE users_groups ADD FOREIGN KEY (group_id) REFERENCES Group(id); + ++ Many-To-Many, Bidirectional Here is a similar many-to-many relationship as above except this one is bidirectional. @@ -348,18 +472,18 @@ Here is a similar many-to-many relationship as above except this one is bidirect /** @Entity */ class User { - // ... + // ... - /** - * @ManyToMany(targetEntity="Group", inversedBy="users") - * @JoinTable(name="users_groups", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} - * ) - */ - private $groups; + /** + * @ManyToMany(targetEntity="Group", inversedBy="users") + * @JoinTable(name="users_groups", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} + * ) + */ + private $groups; - // ... + // ... } /** @Entity */ @@ -373,6 +497,8 @@ Here is a similar many-to-many relationship as above except this one is bidirect // ... } +The MySQL schema is exactly the same as for the Many-To-Many uni-directional case above. + ++ Many-To-Many, Self-referencing You can even have a self-referencing many-to-many association. A common scenario is where a `User` has friends and the target entity of that relationship is a `User` so it is self referencing. In this example it is bidirectional so `User` has a field named `$friendsWithMe` and `$myFriends`. @@ -381,25 +507,40 @@ You can even have a self-referencing many-to-many association. A common scenario /** @Entity */ class User { - // ... + // ... - /** - * @ManyToMany(targetEntity="User", mappedBy="myFriends") - */ - private $friendsWithMe; + /** + * @ManyToMany(targetEntity="User", mappedBy="myFriends") + */ + private $friendsWithMe; - /** - * @ManyToMany(targetEntity="User", inversedBy="friendsWithMe") - * @JoinTable(name="friends", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")} - * ) - */ - private $myFriends; + /** + * @ManyToMany(targetEntity="User", inversedBy="friendsWithMe") + * @JoinTable(name="friends", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")} + * ) + */ + private $myFriends; - // ... + // ... } +Generated MySQL Schema: + + [sql] + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE friends ( + user_id INT NOT NULL, + friend_user_id INT NOT NULL, + PRIMARY KEY(user_id, friend_user_id) + ) ENGINE = InnoDB; + ALTER TABLE friends ADD FOREIGN KEY (user_id) REFERENCES User(id); + ALTER TABLE friends ADD FOREIGN KEY (friend_user_id) REFERENCES User(id); + ++ Ordering To-Many Collections In many use-cases you will want to sort collections when they are retrieved from the database. @@ -415,13 +556,13 @@ in the following way: /** @Entity */ class User { - // ... + // ... - /** - * @ManyToMany(targetEntity="Group") - * @OrderBy({"name" = "ASC"}) - */ - private $groups; + /** + * @ManyToMany(targetEntity="Group") + * @OrderBy({"name" = "ASC"}) + */ + private $groups; } The DQL Snippet in OrderBy is only allowed to consist of unqualified, @@ -446,19 +587,19 @@ Given our previously defined example, the following would not add ORDER BY, sinc However the following: [sql] - SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ...would internally be rewritten to: [sql] - SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC -You can't reverse the order, an explicit: +You can't reverse the order with an explicit DQL ORDER BY: [sql] - SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC ...is internally rewritten to: [sql] - SELECT u FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC \ No newline at end of file + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC \ No newline at end of file From e7066d6f364030aa532bf29f44bf841889975892 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 18 Jul 2010 22:45:17 +0200 Subject: [PATCH 064/430] Updated Introduction to be more consistent with current Github development model --- manual/en/introduction.txt | 46 ++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index e2545afbc..ffcab215e 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -91,7 +91,7 @@ available on your system. Now when you run the `doctrine` command you will see what you can do with it. $ doctrine - Doctrine Command Line Interface version 2.0-DEV + Doctrine Command Line Interface version 2.0.0BETA3-DEV Usage: [options] command [arguments] @@ -111,9 +111,6 @@ see what you can do with it. :import Import SQL file(s) directly to Database. :run-sql Executes arbitrary SQL directly from the command line. orm - :clear-cache:metadata Clear all metadata cache of the various cache drivers. - :clear-cache:query Clear all query cache of the various cache drivers. - :clear-cache:result Clear result cache of the various cache drivers. :convert-d1-schema Converts Doctrine 1.X schema into a Doctrine 2.X schema. :convert-mapping Convert mapping information between supported formats. :ensure-production-settings Verify that Doctrine is properly configured for a production environment. @@ -121,9 +118,15 @@ see what you can do with it. :generate-proxies Generates proxy classes for entity classes. :generate-repositories Generate repository classes from your mapping information. :run-dql Executes arbitrary DQL directly from the command line. - :schema-tool:create Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output. - :schema-tool:drop Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output. - :schema-tool:update Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output. + :validate-schema Validate that the mapping files. + orm:clear-cache + :metadata Clear all metadata cache of the various cache drivers. + :query Clear all query cache of the various cache drivers. + :result Clear result cache of the various cache drivers. + orm:schema-tool + :create Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output. + :drop Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output. + :update Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output. +++ Package Download @@ -136,18 +139,37 @@ Alternatively you can clone the latest version of Doctrine 2 via GitHub.com: $ git clone git://github.com/doctrine/doctrine2.git doctrine +This downloads all the sources of the ORM package. You need to initialize the Github +submodules for the Common and DBAL package dependencies: + + $ git submodule init + $ git submodule update + +This updates your Git checkout to use the Doctrine\Common and Doctrine\DBAL package +versions that are recommended for the cloned Master version of Doctrine 2. + +++ Subversion +> *NOTE* +> +> Using the SVN Mirror is not recommended. It only allows access to the latest master commit +> and does not automatically fetch the submodules. + If you prefer subversion you can also checkout the code from GitHub.com through the subversion protocol: $ svn co http://svn.github.com/doctrine/doctrine2.git doctrine2 +However this only allows you to check out the current master of Doctrine 2, without +the Common and DBAL dependencies. You have to grab them yourself, but might run +into version incompatibilities between the different master branches of Common, DBAL +and ORM. + ++ Sandbox Quickstart > **NOTE** -> The sandbox is only available via SVN or soon as a separate download on the downloads -> page. +> The sandbox is only available via the Doctrine2 Github Repository or soon as a separate download on the downloads +> page. You will find it in the $root/tools/sandbox folder. The sandbox is a pre-configured environment for evaluating and playing with Doctrine 2. @@ -209,7 +231,7 @@ have been created with the name `database.sqlite`. Open index.php in your browser or execute it on the command line. You should see the output "User saved!". -5) Inspect the SQLite database. Again from within the tools/sandbox folder, +4) Inspect the SQLite database. Again from within the tools/sandbox folder, execute the following command: $ php doctrine dbal:run-sql "select * from users" @@ -228,7 +250,7 @@ You should get the following output: You just saved your first entity with a generated ID in an SQLite database. -6) Replace the contents of index.php with the following: +5) Replace the contents of index.php with the following: [php] //... bootstrap stuff @@ -250,4 +272,4 @@ but we wanted to introduce you to DQL at this point. Can you **find** the easier > schema with the command `doctrine orm:schema-tool --drop` followed by > `doctrine orm:schema-tool --create`. -7) Explore Doctrine 2! \ No newline at end of file +6) Explore Doctrine 2! \ No newline at end of file From a851e08109a63c72b5893d9fc4de6aa084208cf8 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 18 Jul 2010 22:55:36 +0200 Subject: [PATCH 065/430] Updated Introduction to be more consistent with current Github development model --- manual/en/introduction.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index ffcab215e..2cc766801 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -150,7 +150,7 @@ versions that are recommended for the cloned Master version of Doctrine 2. +++ Subversion -> *NOTE* +> **NOTE** > > Using the SVN Mirror is not recommended. It only allows access to the latest master commit > and does not automatically fetch the submodules. From b1f6fe2528f96e75e5c5ddf0b075956406e762f6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 18 Jul 2010 22:57:20 +0200 Subject: [PATCH 066/430] Updated Introduction to be more consistent with current Github development model --- manual/en/introduction.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index 2cc766801..489c41a47 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -148,6 +148,12 @@ submodules for the Common and DBAL package dependencies: This updates your Git checkout to use the Doctrine\Common and Doctrine\DBAL package versions that are recommended for the cloned Master version of Doctrine 2. +> **NOTE** +> +> You should not combine the Doctrine-Common, Doctrine-DBAL and Doctrine-ORM master commits +> with each other in combination. The ORM may not work with the current Common or DBAL master versions. +> Instead the ORM ships with the Git Submodules that are required. + +++ Subversion > **NOTE** From 439cccbc0d03dde8beecd140133081fc8585a868 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 22 Jul 2010 22:55:00 +0200 Subject: [PATCH 067/430] Enhanced Bootstrap Section for more information on Autoloading in different setups (PEAR vs Git) --- manual/en/configuration.txt | 42 ++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index da2714617..39e8b625d 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -23,19 +23,55 @@ and namespace and where there is a common root namespace. > meant to be only used for Doctrine classes, too. It is a generic class loader that can > be used for any classes that follow some basic naming standards as described above. -The following example shows the setup of a `ClassLoader` +The following example shows the setup of a `ClassLoader` for the different types +of Doctrine Installations: > **NOTE** > This assumes you've created some kind of script to test the following code in. > Something like a `test.php` file. +++++ PEAR or Tarball Download + [php] // test.php - require '/path/to/lib/Doctrine/Common/ClassLoader.php'; - $classLoader = new \Doctrine\Common\ClassLoader('Doctrine', '/path/to/Doctrine2/lib'); + require '/path/to/libraries/Doctrine/Common/ClassLoader.php'; + $classLoader = new \Doctrine\Common\ClassLoader('Doctrine', '/path/to/libraries'); $classLoader->register(); // register on SPL autoload stack +++++ Git + +The Git bootstrap assumes that you have fetched the related packages through `git submodule update --init` + + [php] + // test.php + + $lib = '/path/to/doctrine2-orm/lib/'; + require $lib . 'vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php'; + + $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\Common', $lib . 'vendor/doctrine-common/lib'); + $classLoader->register(); + + $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\DBAL', $lib . 'vendor/doctrine-dbal/lib'); + $classLoader->register(); + + $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\ORM', $lib); + $classLoader->register(); + +++++ Additional Symfony Components + +If you don't use Doctrine2 in combination with Symfony2 you have to register an additional namespace to be able to use +the Doctrine-CLI Tool or the YAML Mapping driver: + + [php] + // PEAR or Tarball setup + $classloader = new \Doctrine\Common\ClassLoader('Symfony', '/path/to/libraries/Doctrine'); + $classloader->register(); + + // Git Setup + $classloader = new \Doctrine\Common\ClassLoader('Symfony', $lib . 'vendor/'); + $classloader->register(); + For best class loading performance it is recommended that you keep your include_path short, ideally it should only contain the path to the PEAR libraries, and any other class libraries should be registered with their full base path. +++ Obtaining an EntityManager From d5afa35cb8a56b812267c137a5fe9a6eed8c412d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 22 Jul 2010 23:25:29 +0200 Subject: [PATCH 068/430] Added dev vs production configuration section --- manual/en/configuration.txt | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index 39e8b625d..2a406cd9f 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -170,12 +170,14 @@ The cache implementation must implement the `Doctrine\Common\Cache\Cache` interf Usage of a metadata cache is highly recommended. -The recommended implementations are: +The recommended implementations for production are: * `Doctrine\Common\Cache\ApcCache` * `Doctrine\Common\Cache\MemcacheCache` * `Doctrine\Common\Cache\XcacheCache` +For development you should use the `Doctrine\Common\Cache\ArrayCache` which only caches data on a per-request basis. + +++ Query Cache (***RECOMMENDED***) [php] @@ -186,12 +188,14 @@ Gets or sets the cache implementation to use for caching DQL queries, that is, t Usage of a query cache is highly recommended. -The recommended implementations are: +The recommended implementations for production are: * `Doctrine\Common\Cache\ApcCache` * `Doctrine\Common\Cache\MemcacheCache` * `Doctrine\Common\Cache\XcacheCache` +For development you should use the `Doctrine\Common\Cache\ArrayCache` which only caches data on a per-request basis. + +++ SQL Logger (***Optional***) [php] @@ -208,6 +212,19 @@ Gets or sets the logger to use for logging all SQL statements executed by Doctri Gets or sets whether proxy classes should be generated automatically at runtime by Doctrine. If set to `FALSE`, proxy classes must be generated manually through the doctrine command line task `generate-proxies`. The strongly recommended value for a production environment is `FALSE`. +++ Development vs Production Configuration + +You should code your Doctrine2 bootstrapping with two different runtime models in mind. There are some serious +benefits of using APC or Memcache in production. In development however this will frequently give you fatal +errors, when you change your entities and the cache still keeps the outdated metadata. That is why we recommend +the `ArrayCache` for development. + +Furthermore you should have the Auto-generating Proxy Classes option to true in development and to false +in production. If this option is set to `TRUE` it can seriously hurt your script performance if several proxy +classes are re-generated during script execution. Filesystem calls of that magnitude can even slower than all +the database queries Doctrine issues. Additionally writing a proxy sets an exclusive file lock which can cause +serious performance bottlenecks in systems with regular concurrent requests. + ++ Connection Options The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array From 974e31a3071136bacfc4f80e579168291768f951 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 22 Jul 2010 23:29:21 +0200 Subject: [PATCH 069/430] Use $applicationMode flag in obtaining entity manager example to make sure people understand that different settings for production and development are necessary --- manual/en/configuration.txt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index 2a406cd9f..a76e09db5 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -84,15 +84,27 @@ with the following minimalist configuration: Doctrine\ORM\Configuration; // ... - + + if ($applicationMode == "development") { + $cache = new \Doctrine\Common\Cache\ArrayCache; + } else { + $cache = new \Doctrine\Common\Cache\ApcCache; + } + $config = new Configuration; - $cache = new \Doctrine\Common\Cache\ApcCache; $config->setMetadataCacheImpl($cache); $driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); $config->setMetadataDriverImpl($driverImpl); $config->setQueryCacheImpl($cache); $config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); $config->setProxyNamespace('MyProject\Proxies'); + + if ($applicationMode == "development") { + $config->setAutoGenerateProxyClasses(true); + } else { + $config->setAutoGenerateProxyClasses(true); + } + $connectionOptions = array( 'driver' => 'pdo_sqlite', 'path' => 'database.sqlite' From f8c22abaa6b431f2e51e5124f9ee1c8c373f1892 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 22 Jul 2010 23:30:17 +0200 Subject: [PATCH 070/430] Use $applicationMode flag in obtaining entity manager example to make sure people understand that different settings for production and development are necessary --- manual/en/configuration.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index a76e09db5..e5b2fdf34 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -102,7 +102,7 @@ with the following minimalist configuration: if ($applicationMode == "development") { $config->setAutoGenerateProxyClasses(true); } else { - $config->setAutoGenerateProxyClasses(true); + $config->setAutoGenerateProxyClasses(false); } $connectionOptions = array( From 0a066533de06017a5581a4389bac9ce5c0570299 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 22 Jul 2010 23:38:09 +0200 Subject: [PATCH 071/430] Rephrased Obtaining Entity Manager and moved some sentences around --- manual/en/configuration.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index e5b2fdf34..4431f2124 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -76,8 +76,11 @@ For best class loading performance it is recommended that you keep your include_ +++ Obtaining an EntityManager -Once you have prepared the class loading, you acquire an EntityManager instance -with the following minimalist configuration: +Once you have prepared the class loading, you acquire an *EntityManager* instance. +The EntityManager class is the primary access point to ORM functionality provided by Doctrine. + +A simple configuration of the EntityManager requires a `Doctrine\ORM\Configuration` +instance as well as some database connection parameters: [php] use Doctrine\ORM\EntityManager, @@ -123,8 +126,6 @@ with the following minimalist configuration: > a very fast in-memory cache storage that you can use for the metadata and query > caches as seen in the previous code snippet. -An EntityManager is your central access point to ORM functionality provided by Doctrine. - ++ Configuration Options The following sections describe all the configuration options available on a `Doctrine\ORM\Configuration` instance. From 298773b2f8648fde5c9b3c317c65eb4d747ba5d4 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 23 Jul 2010 21:36:25 +0200 Subject: [PATCH 072/430] Added section on collection field initialization and added constructors with initialization to all examples of toMany collections --- manual/en/association-mapping.txt | 81 ++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index c1cd0edb8..1ca3afd47 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -110,6 +110,55 @@ In that case, the name of the join table defaults to a combination of the simple If you accept these defaults, you can reduce the mapping code to a minimum. +++ Initializing Collections + +You have to be careful when using entity fields that contain a collection of related entities. Say we have a User entity that contains a collection of groups: + + [php] + /** @Entity */ + class User + { + /** @ManyToMany(targetEntity="Group") */ + private $groups; + + public function getGroups() + { + return $this->groups; + } + } + +With this code alone the `$groups` field only contains an instance of `Doctrine\Common\Collections\Collection` if the user is retrieved from +Doctrine, however not after you instantiated a fresh instance of the User. When your user entity is still new `$groups` will obviously be null. + +This is why we recommend to initialize all collection fields to an empty `ArrayCollection` in your entities constructor: + + [php] + use Doctrine\Common\Collections\ArrayCollection; + + /** @Entity */ + class User + { + /** @ManyToMany(targetEntity="Group") */ + private $groups; + + public function __construct() + { + $this->groups = new ArrayCollection(); + } + + public function getGroups() + { + return $this->groups; + } + } + +Now the following code will be working even if the Entity hasn't been associated with an EntityManager yet: + + [php] + $group = $entityManager->find('Group', $groupId); + $user = new User(); + $user->getGroups()->add($group); + ++ One-To-One, Unidirectional A unidirectional one-to-one association is very common. Here is an example of a `Product` that has one `Shipping` object associated to it. The `Shipping` side does not reference back to the `Product` so it is unidirectional. @@ -253,6 +302,10 @@ The following example sets up such a unidirectional one-to-many association: */ private $phonenumbers; + public function __construct() { + $this->phonenumbers = new \Doctrine\Common\Collections\ArrayCollection(); + } + // ... } @@ -345,6 +398,10 @@ Bidirectional one-to-many associations are very common. The following code shows */ private $features; // ... + + public function __construct() { + $this->features = new \Doctrine\Common\Collections\ArrayCollection(); + } } /** @Entity */ @@ -396,7 +453,11 @@ This effectively models a hierarchy of categories and from the database perspect * @JoinColumn(name="parent_id", referencedColumnName="id") */ private $parent; - // ... + // ... + + public function __construct() { + $this->children = new \Doctrine\Common\Collections\ArrayCollection(); + } } Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. @@ -431,6 +492,10 @@ Real many-to-many associations are less common. The following example shows a un private $groups; // ... + + public function __construct() { + $this->groups = new \Doctrine\Common\Collections\ArrayCollection(); + } } /** @Entity */ @@ -483,6 +548,10 @@ Here is a similar many-to-many relationship as above except this one is bidirect */ private $groups; + public function __construct() { + $this->groups = new \Doctrine\Common\Collections\ArrayCollection(); + } + // ... } @@ -494,6 +563,11 @@ Here is a similar many-to-many relationship as above except this one is bidirect * @ManyToMany(targetEntity="User", mappedBy="groups") */ private $users; + + public function __construct() { + $this->users = new \Doctrine\Common\Collections\ArrayCollection(); + } + // ... } @@ -523,6 +597,11 @@ You can even have a self-referencing many-to-many association. A common scenario */ private $myFriends; + public function __construct() { + $this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection(); + $this->myFriends = new \Doctrine\Common\Collections\ArrayCollection(); + } + // ... } From 62c4f3e6fb54ae02a6d5dde7cdc9560fa136f2bd Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 23 Jul 2010 23:21:17 +0200 Subject: [PATCH 073/430] Updated documentation on chosing inverse-owning side for many-to-many associations and came up with a significantly enhanced example for the working with associations chapter --- manual/en/association-mapping.txt | 40 ++++ manual/en/working-with-objects.txt | 281 +++++++++++++++++++++++------ 2 files changed, 269 insertions(+), 52 deletions(-) diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index 1ca3afd47..6c9324dc6 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -573,6 +573,46 @@ Here is a similar many-to-many relationship as above except this one is bidirect The MySQL schema is exactly the same as for the Many-To-Many uni-directional case above. ++++ Picking the Owning and Inverse Side + +For Many-To-Many associations you can chose which entity is the owning and which the inverse side. There is +a very simple semantic rule to decide which side is more suitable to be the owning side from a developers perspective. +You only have to ask yourself, which entity is responsible for the connection management and pick that as the owning side. + +Take an example of two entities `Article` and `Tag`. Whenever you want to connect an Article to a Tag and vice-versa, it is mostly +the Article that is responsible for this relation. Whenever you add a new article, you want to connect it with existing or new tags. +Your create Article form will probably support this notion and allow to specify the tags directly. This is why you should +pick the Article as owning side, as it makes the code more understandable: + + [php] + class Article + { + private $tags; + + public function addTag(Tag $tag) + { + $tag->addArticle($this); // synchronously updating inverse side + $this->tags[] = $tag; + } + } + + class Tag + { + private $articles; + + public function addArticle(Article $article) + { + $this->articles[] = $article; + } + } + +This allows to group the tag adding on the `Article` side of the association: + + [php] + $article = new Article(); + $article->addTag($tagA); + $article->addTag($tagB); + ++ Many-To-Many, Self-referencing You can even have a self-referencing many-to-many association. A common scenario is where a `User` has friends and the target entity of that relationship is a `User` so it is self referencing. In this example it is bidirectional so `User` has a field named `$friendsWithMe` and `$myFriends`. diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 289c2a102..5a3b9063d 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -180,29 +180,168 @@ and you want to modify and persist such an entity. Associations between entities are represented just like in regular object-oriented PHP, with references to other objects or collections of objects. When it comes to persistence, it is important to understand three main things: * The concept of owning and inverse sides in bidirectional associations as described [here](http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#owning-side-and-inverse-side). - * A collection of entities always only represents the association to the containing entities. If an entity is removed from a collection, the association is removed, not the entity itself. - * Collection-valued persistent fields and properties must be defined in terms of the Doctrine\Common\Collections\Collection interface. [See here](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities:persistent-fields) for more details. + * If an entity is removed from a collection, the association is removed, not the entity itself. A collection of entities always only represents the association to the containing entities, not the entity itself. + * Collection-valued persistent fields have to be instances of the `Doctrine\Common\Collections\Collection` interface. [See here](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities:persistent-fields) for more details. ++++ Example Associations + +We will use a simple comment system with Users and Comments as entity to show examples of association management: + + [php] + /** @Entity */ + class User + { + /** @Id @GeneratedValue @Column(type="string") */ + private $id; + + /** + * Bidirectional - Many users have Many favorite comments (OWNING SIDE) + * + * @ManyToMany(targetEntity="Comment", inversedBy="userFavorites") + * @JoinTable(name="user_favorite_comments", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="favorite_comment_id", referencedColumnName="id")} + * ) + */ + private $favorites; + + /** + * Unidirectional - Many users have marked many comments as read + * + * @ManyToMany(targetEntity="Comment") + * @JoinTable(name="user_read_comments", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="comment_id", referencedColumnName="id")} + * ) + */ + private $commentsRead; + + /** + * Bidirectional - One-To-Many (INVERSE SIDE) + * + * @OneToMany(targetEntity="Comment", mappedBy="author") + */ + private $commentsAuthored; + + /** + * Unidirectional - Many-To-One + * + * @ManyToOne(targetEntity="Comment") + */ + private $firstComment; + } + + /** @Entity */ + class Comment + { + /** @Id @GeneratedValue @Column(type="string") */ + private $id; + + /** + * Bidirectional - Many comments are favorited by many users (INVERSE SIDE) + * + * @ManyToMany(targetEntity="User", mappedBy="favorites") + */ + private $userFavorites; + + /** + * Bidirectional - Many Comments are authored by one user (OWNING SIDE) + * + * @ManyToOne(targetEntity="User", inversedBy="authoredComments") + */ + private $author; + } + +This two entities generate the following MySQL Schema (Foreign Key definitions omitted): + + [sql] + CREATE TABLE User ( + id VARCHAR(255) NOT NULL, + firstComment_id VARCHAR(255) DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + CREATE TABLE Comment ( + id VARCHAR(255) NOT NULL, + author_id VARCHAR(255) DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + CREATE TABLE user_favorite_comments ( + user_id VARCHAR(255) NOT NULL, + favorite_comment_id VARCHAR(255) NOT NULL, + PRIMARY KEY(user_id, favorite_comment_id) + ) ENGINE = InnoDB; + + CREATE TABLE user_read_comments ( + user_id VARCHAR(255) NOT NULL, + comment_id VARCHAR(255) NOT NULL, + PRIMARY KEY(user_id, comment_id) + ) ENGINE = InnoDB; ++ Establishing Associations -Establishing an association between two entities is straight-forward. Here are some examples: +Establishing an association between two entities is straight-forward. Here are some examples for the unidirectional +relations of the `User`: [php] - // Article <- one-to-many -> Comment - $article->getComments()->add($comment); - $comment->setArticle($article); + class User + { + // ... + public function getReadComments() { + return $this->commentsRead; + } + + public function setFirstComment(Comment $c) { + $this->firstComment = $c; + } + } + + // unidirectional many to many + $user->getReadComments()->add($comment); + + // unidirectional many to one + $user->setFirstComment($myFirstComment); + +In the case of bi-directional associations you have to update the fields on both sides: + + [php] + class User + { + // .. + + public function getAuthoredComments() { + return $this->commentsAuthored; + } + + public function getFavoriteComments() { + return $this->favorites; + } + } + + class Comment + { + // ... + + public function getUserFavorites() { + return $this->userFavorites; + } + + public function setAuthor(User $author = null) { + $this->author = $author; + } + } + + // Many-to-Many + $user->getFavorites()->add($favoriteComment); + $favoriteComment->getUserFavorites()->add($user); - // User <- many-to-many -> Groups - $user->getGroups()->add($group); - $group->getUsers()->add($user); - - // User <- one-to-one -> Address - $user->setAddress($address); - $address->setUser($user); + // Many-To-One / One-To-Many Bidirectional + $user->getAuthoredComments()->add($newComment); + $newComment->setAuthor($user); -Notice how always both sides of the bidirectional association are updated. Unidirectional associations are consequently simpler to handle. +Notice how always both sides of the bidirectional association are updated. The previous unidirectional associations were simpler to handle. ++ Removing Associations @@ -211,30 +350,31 @@ to do so, by key and by element. Here are some examples: [php] // Remove by Elements - // Article <- one-to-many -> Comment - $article->getComments()->removeElement($comment); - $comment->setArticle(null); - - // User <- many-to-many -> Group - $user->getGroups()->removeElement($group); - $group->getUsers()->removeElement($user); + $user->getComments()->removeElement($comment); + $comment->setAuthor(null); - // Remove by key - $article->getComments()->remove($ithComment); - $comment->setArticle(null); + $user->getFavorites()->removeElement($comment); + $comment->getUserFavorites()->removeElement($user); - // User <- one-to-one -> Address - $user->setAddress(null); - $address->setUser(null); + // Remove by Key + $user->getComments()->removeElement($ithComment); + $comment->setAuthor(null); - -Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does not allow null values and setAddress(null) will fail for removing the association. If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`. This can also provide better encapsulation as it hides the internal meaning of not having an address. +Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently +simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does only +allows null values if `null` is set as default value. Otherwise setAddress(null) will fail for removing the association. +If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`. +This can also provide better encapsulation as it hides the internal meaning of not having an address. When working with collections, keep in mind that a Collection is essentially an ordered map (just like a PHP array). That is why the `remove` operation accepts an index/key. `removeElement` is a separate method -that has O(n) complexity, where n is the size of the map. +that has O(n) complexity using `array_search`, where n is the size of the map. -Since Doctrine always only looks at the owning side of a bidirectional association, it is essentially not necessary that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. +> **NOTE** +> +> Since Doctrine always only looks at the owning side of a bidirectional association for updates, it is not necessary +> for write operations that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. +> This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. > **NOTE* > @@ -250,38 +390,75 @@ Since Doctrine always only looks at the owning side of a bidirectional associati It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained. -The following code shows a simple, idiomatic example for a bidirectional one-to-many association between an Article and its Comments. +The following code shows updates to the previous User and Comment example that encapsulate much of +the association management code: [php] - // Mappings not shown. - class Article { - // The comments of the article. - private $comments; - // ... constructor omitted ... + class User + { + //... + public function markCommentRead(Comment $comment) { + // Collections implement ArrayAccess + $this->commentsRead[] = $comment; + } + public function addComment(Comment $comment) { - $this->comments->add($comment); - $comment->setArticle($this); + if (count($this->commentsAuthored) == 0) { + $this->setFirstComment($comment); + } + $this->comments[] = $comment; + $comment->setAuthor($this); } - public function getComments() { - return $this->comments; + + private function setFirstComment(Comment $c) { + $this->firstComment = $c; } - } - class Comment { - // The article the comment refers to. - private $article; - // ... constructor omitted ... - public function setArticle($article) { - $this->article = $article; + + public function addFavorite(Comment $comment) { + $this->favorites->add($comment); + $comment->addUserFavorite($this); } - public function getArticle() { - return $this->article; + + public function removeFavorite(Comment $comment) { + $this->favorites->removeElement($comment); + $comment->removeUserFavorite($this); } } -With the above implementation, it is always ensured that at least the owning side from Doctrine's point of view (Comment) is properly updated. You will notice that `setArticle` does not call `addComment`, thus the bidirectional association is strictly-speaking still incomplete, if a user of the class only invokes `setArticle`. If you naively call `addComment` in `setArticle`, however, you end up with an infinite loop, so more work is needed. As you can see, proper bidirectional association management in plain OOP is a non-trivial task and encapsulating all the details inside the classes can be challenging. + class Comment + { + // .. -There is no single, best way for association management. It greatly depends on the requirements of your concrete domain model as well as your preferences. + public function addUserFavorite(User $user) { + $this->userFavorites[] = $user; + } + public function removeUserFavorite(User $user) { + $this->userFavorites->removeElement($user); + } + } + +You will notice that `addUserFavorite` and `removeUserFavorite` do not call `addFavorite` and `removeFavorite`, +thus the bidirectional association is strictly-speaking still incomplete. However if you would naively add the +`addFavorite` in `addUserFavorite`, you end up with an infinite loop, so more work is needed. +As you can see, proper bidirectional association management in plain OOP is a non-trivial task +and encapsulating all the details inside the classes can be challenging. + +> **NOTE** +> +> If you want to make sure that your collections are perfectly encapsulated you should not return +> them from a `getCollectionName()` method directly, but call `$collection->toArray()`. This way a client programmer +> for the entity cannot circumvent the logic you implement on your entity for association management. Example: +> +> [php] +> class User { +> public function getReadComments() { +> return $this->commentsRead->toArray(); +> } +> } + +There is no single, best way for association management. It greatly depends on the requirements of your concrete +domain model as well as your preferences. ++ Transitive persistence From 31b8141fee767d0ef3dd3200025fe1f088eae01d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 23 Jul 2010 23:31:27 +0200 Subject: [PATCH 074/430] Reworked Introduction a bit --- manual/en/introduction.txt | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index 489c41a47..cb074343a 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -2,11 +2,15 @@ Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides transparent persistence for PHP objects. It sits on top of a powerful database -abstraction layer (DBAL). One of its key features is the option to write -database queries in a proprietary object oriented SQL dialect called Doctrine -Query Language (DQL), inspired by Hibernates HQL. This provides developers with -a powerful alternative to SQL that maintains flexibility without requiring -unnecessary code duplication. +abstraction layer (DBAL). Object-Relational Mappers primary task is the transparent +translation between (PHP) objects and relational database rows. + +One of Doctrines key features is the option to write database queries in a +proprietary object oriented SQL dialect called Doctrine +Query Language (DQL), inspired by Hibernates HQL. Besides DQLs slight +differences to SQL it abstracts the mapping between database rows and +objects considerably, allowing developers to write powerful queries +in a simple and flexible fashion. ++ Disclaimer @@ -14,6 +18,13 @@ This is the Doctrine 2 reference documentation. Introductory guides and tutorial that you can follow along from start to finish, like the "Guide to Doctrine" book known from the Doctrine 1.x series, will be available at a later date. +++ Using an Object-Relational Mapper + +As the term ORM already hints at, Doctrine 2 aims to simplify the translation +between database rows and the PHP object model. The primary use case for Doctrine +are therefore applications that utilize the Object-Oriented Programming Paradigm. +For applications that not primarily work with objects Doctrine 2 is not suited very well. + ++ Requirements Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved performance it From b5e5cb1c6595e2c8ff9532ce95cfae3267228e89 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 23 Jul 2010 23:39:36 +0200 Subject: [PATCH 075/430] Removed examples of Nested Path Expressions from DQL docs --- manual/en/dql-doctrine-query-language.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index e2d9e6cdb..034b8b546 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -119,13 +119,13 @@ Retrieve the IDs of all CmsUsers: Retrieve the IDs of all users that have written an article: [php] - $query = $em->createQuery('SELECT DISTINCT a.user.id FROM CmsArticle a'); + $query = $em->createQuery('SELECT DISTINCT u.id FROM CmsArticle a JOIN a.user u'); $ids = $query->getResult(); // array of CmsUser ids Retrieve all articles and sort them by the name of the articles users instance: [php] - $query = $em->createQuery('SELECT a FROM CmsArticle a ORDER BY a.user.name ASC'); + $query = $em->createQuery('SELECT a FROM CmsArticle a JOIN a.user u ORDER BY u.name ASC'); $articles = $query->getResult(); // array of CmsArticle objects Retrieve the Username and Name of a CmsUser: From e0c702d068d0b35060e304afc038ae7bd50a4fb4 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 23 Jul 2010 23:42:03 +0200 Subject: [PATCH 076/430] Fix little bug in Note box --- manual/en/working-with-objects.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 5a3b9063d..b167841fe 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -376,7 +376,7 @@ that has O(n) complexity using `array_search`, where n is the size of the map. > for write operations that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. > This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. -> **NOTE* +> **NOTE** > > You can also clear the contents of a whole collection using the `Collections::clear()` method. You > should be aware that using this method can lead to a straight and optimized database delete or update call From 3b76ea9ffe69c5d7e7b41b1bb35b3845b71e3bbe Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 23 Jul 2010 23:43:44 +0200 Subject: [PATCH 077/430] Fix code box in Note --- manual/en/working-with-objects.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index b167841fe..b17d81fb0 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -448,14 +448,14 @@ and encapsulating all the details inside the classes can be challenging. > > If you want to make sure that your collections are perfectly encapsulated you should not return > them from a `getCollectionName()` method directly, but call `$collection->toArray()`. This way a client programmer -> for the entity cannot circumvent the logic you implement on your entity for association management. Example: -> -> [php] -> class User { -> public function getReadComments() { -> return $this->commentsRead->toArray(); -> } -> } +> for the entity cannot circumvent the logic you implement on your entity for association management. For example: + + [php] + class User { + public function getReadComments() { + return $this->commentsRead->toArray(); + } + } There is no single, best way for association management. It greatly depends on the requirements of your concrete domain model as well as your preferences. From f309190486e34e5ef80ea783cad07b7bdd67efba Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 23 Jul 2010 23:46:47 +0200 Subject: [PATCH 078/430] Clarified up and downsides of $collection->toArray() --- manual/en/working-with-objects.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index b17d81fb0..cc0878069 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -457,6 +457,10 @@ and encapsulating all the details inside the classes can be challenging. } } +This will however always initialize the collection, with all the performance penalties given the size. In +some scenarios of large collections it might even be a good idea to completely hide the read access behind +methods on the EntityRepository. + There is no single, best way for association management. It greatly depends on the requirements of your concrete domain model as well as your preferences. From 99533afd11ea3ed71e728eaa2e5b9e6acc056dd5 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 23 Jul 2010 23:51:25 +0200 Subject: [PATCH 079/430] Added EntityManager interaction into examples --- manual/en/working-with-objects.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index cc0878069..5c677cc67 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -297,12 +297,23 @@ relations of the `User`: } } +Interaction would then look like the following code parts, `$em` here is an instance of the EntityManager: + + $user = $em->find('User', $userId); + // unidirectional many to many + $comment = $em->find('Comment', $readCommentId); $user->getReadComments()->add($comment); + $em->flush(); + // unidirectional many to one + $myFirstComment = new Comment(); $user->setFirstComment($myFirstComment); + $em->persist($myFirstComment); + $em->flush(); + In the case of bi-directional associations you have to update the fields on both sides: [php] @@ -335,11 +346,17 @@ In the case of bi-directional associations you have to update the fields on both // Many-to-Many $user->getFavorites()->add($favoriteComment); $favoriteComment->getUserFavorites()->add($user); + + $em->flush(); // Many-To-One / One-To-Many Bidirectional + $newComment = new Comment(); $user->getAuthoredComments()->add($newComment); $newComment->setAuthor($user); + $em->persist(); + $m->flush(); + Notice how always both sides of the bidirectional association are updated. The previous unidirectional associations were simpler to handle. @@ -360,6 +377,8 @@ to do so, by key and by element. Here are some examples: $user->getComments()->removeElement($ithComment); $comment->setAuthor(null); +You need to call `$em->flush()` to make persist these changes in the database permanently. + Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does only allows null values if `null` is set as default value. Otherwise setAddress(null) will fail for removing the association. From 23efc790c193bc4b3c59e49b8f618193778024b9 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 24 Jul 2010 10:46:54 +0200 Subject: [PATCH 080/430] Split Working with Objects into two chapters, adding Working with Associations --- manual/en.txt | 1 + manual/en/association-mapping.txt | 6 +- manual/en/introduction.txt | 6 +- manual/en/working-with-associations.txt | 417 ++++++++++++++++++++++++ manual/en/working-with-objects.txt | 376 ++------------------- 5 files changed, 453 insertions(+), 353 deletions(-) create mode 100644 manual/en/working-with-associations.txt diff --git a/manual/en.txt b/manual/en.txt index a95c260f6..0319f824c 100644 --- a/manual/en.txt +++ b/manual/en.txt @@ -5,6 +5,7 @@ + Association Mapping + Inheritance Mapping + Working with objects ++ Working with associations + Transactions and Concurrency + Events + Batch processing diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index 6c9324dc6..d4200f751 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -57,7 +57,9 @@ In all the examples of many-valued associations in this manual we will make use ++ Mapping Defaults -The @JoinColumn and @JoinTable definitions are usually optional and have sensible default values. The defaults for a join column in a one-to-one/many-to-one association is as follows: +Before we introduce all the association mappings in detailyou should note that the @JoinColumn and @JoinTable +definitions are usually optional and have sensible default values. +The defaults for a join column in a one-to-one/many-to-one association is as follows: name: "_id" referencedColumnName: "id" @@ -573,7 +575,7 @@ Here is a similar many-to-many relationship as above except this one is bidirect The MySQL schema is exactly the same as for the Many-To-Many uni-directional case above. -+++ Picking the Owning and Inverse Side ++++ Picking Owning and Inverse Side For Many-To-Many associations you can chose which entity is the owning and which the inverse side. There is a very simple semantic rule to decide which side is more suitable to be the owning side from a developers perspective. diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index cb074343a..0d1fb6e98 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -289,4 +289,8 @@ but we wanted to introduce you to DQL at this point. Can you **find** the easier > schema with the command `doctrine orm:schema-tool --drop` followed by > `doctrine orm:schema-tool --create`. -6) Explore Doctrine 2! \ No newline at end of file +6) Explore Doctrine 2! + +See the following links if you want to start with more complex tutorials rather than reading the manual: + +* Doctrine2 Cookbook: [Getting Started XML Edition](http://www.doctrine-project.org/projects/orm/2.0/docs/cookbook/getting-started-xml-edition/en) diff --git a/manual/en/working-with-associations.txt b/manual/en/working-with-associations.txt new file mode 100644 index 000000000..04f820d1a --- /dev/null +++ b/manual/en/working-with-associations.txt @@ -0,0 +1,417 @@ +++ Associations + +Associations between entities are represented just like in regular object-oriented PHP, with references to other objects +or collections of objects. When it comes to persistence, it is important to understand three main things: + + * The concept of owning and inverse sides in bidirectional associations as described [here](http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#owning-side-and-inverse-side). + * If an entity is removed from a collection, the association is removed, not the entity itself. A collection of entities always only represents the association to the containing entities, not the entity itself. + * Collection-valued persistent fields have to be instances of the `Doctrine\Common\Collections\Collection` interface. [See here](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities:persistent-fields) for more details. + +Changes to associations in your code are not synchronized to the database directly, but upon calling `EntityManager#flush()`. + ++++ Example: Entities with Associations + +We will use a simple comment system with Users and Comments as entities to show examples of association management. +See the docblocks of each association in the following example for information about its type and if its the owning or inverse side. + + [php] + /** @Entity */ + class User + { + /** @Id @GeneratedValue @Column(type="string") */ + private $id; + + /** + * Bidirectional - Many users have Many favorite comments (OWNING SIDE) + * + * @ManyToMany(targetEntity="Comment", inversedBy="userFavorites") + * @JoinTable(name="user_favorite_comments", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="favorite_comment_id", referencedColumnName="id")} + * ) + */ + private $favorites; + + /** + * Unidirectional - Many users have marked many comments as read + * + * @ManyToMany(targetEntity="Comment") + * @JoinTable(name="user_read_comments", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="comment_id", referencedColumnName="id")} + * ) + */ + private $commentsRead; + + /** + * Bidirectional - One-To-Many (INVERSE SIDE) + * + * @OneToMany(targetEntity="Comment", mappedBy="author") + */ + private $commentsAuthored; + + /** + * Unidirectional - Many-To-One + * + * @ManyToOne(targetEntity="Comment") + */ + private $firstComment; + } + + /** @Entity */ + class Comment + { + /** @Id @GeneratedValue @Column(type="string") */ + private $id; + + /** + * Bidirectional - Many comments are favorited by many users (INVERSE SIDE) + * + * @ManyToMany(targetEntity="User", mappedBy="favorites") + */ + private $userFavorites; + + /** + * Bidirectional - Many Comments are authored by one user (OWNING SIDE) + * + * @ManyToOne(targetEntity="User", inversedBy="authoredComments") + */ + private $author; + } + +This two entities generate the following MySQL Schema (Foreign Key definitions omitted): + + [sql] + CREATE TABLE User ( + id VARCHAR(255) NOT NULL, + firstComment_id VARCHAR(255) DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + CREATE TABLE Comment ( + id VARCHAR(255) NOT NULL, + author_id VARCHAR(255) DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + CREATE TABLE user_favorite_comments ( + user_id VARCHAR(255) NOT NULL, + favorite_comment_id VARCHAR(255) NOT NULL, + PRIMARY KEY(user_id, favorite_comment_id) + ) ENGINE = InnoDB; + + CREATE TABLE user_read_comments ( + user_id VARCHAR(255) NOT NULL, + comment_id VARCHAR(255) NOT NULL, + PRIMARY KEY(user_id, comment_id) + ) ENGINE = InnoDB; + +++ Establishing Associations + +Establishing an association between two entities is straight-forward. Here are some examples for the unidirectional +relations of the `User`: + + [php] + class User + { + // ... + public function getReadComments() { + return $this->commentsRead; + } + + public function setFirstComment(Comment $c) { + $this->firstComment = $c; + } + } + +Interaction would then look like the following code parts, `$em` here is an instance of the EntityManager: + + $user = $em->find('User', $userId); + + // unidirectional many to many + $comment = $em->find('Comment', $readCommentId); + $user->getReadComments()->add($comment); + + $em->flush(); + + // unidirectional many to one + $myFirstComment = new Comment(); + $user->setFirstComment($myFirstComment); + + $em->persist($myFirstComment); + $em->flush(); + +In the case of bi-directional associations you have to update the fields on both sides: + + [php] + class User + { + // .. + + public function getAuthoredComments() { + return $this->commentsAuthored; + } + + public function getFavoriteComments() { + return $this->favorites; + } + } + + class Comment + { + // ... + + public function getUserFavorites() { + return $this->userFavorites; + } + + public function setAuthor(User $author = null) { + $this->author = $author; + } + } + + // Many-to-Many + $user->getFavorites()->add($favoriteComment); + $favoriteComment->getUserFavorites()->add($user); + + $em->flush(); + + // Many-To-One / One-To-Many Bidirectional + $newComment = new Comment(); + $user->getAuthoredComments()->add($newComment); + $newComment->setAuthor($user); + + $em->persist(); + $m->flush(); + + +Notice how always both sides of the bidirectional association are updated. The previous unidirectional associations were simpler to handle. + +++ Removing Associations + +Removing an association between two entities is similarly straight-forward. There are two strategies +to do so, by key and by element. Here are some examples: + + [php] + // Remove by Elements + $user->getComments()->removeElement($comment); + $comment->setAuthor(null); + + $user->getFavorites()->removeElement($comment); + $comment->getUserFavorites()->removeElement($user); + + // Remove by Key + $user->getComments()->removeElement($ithComment); + $comment->setAuthor(null); + +You need to call `$em->flush()` to make persist these changes in the database permanently. + +Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently +simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does only +allows null values if `null` is set as default value. Otherwise setAddress(null) will fail for removing the association. +If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`. +This can also provide better encapsulation as it hides the internal meaning of not having an address. + +When working with collections, keep in mind that a Collection is essentially an ordered map (just like a PHP array). +That is why the `remove` operation accepts an index/key. `removeElement` is a separate method +that has O(n) complexity using `array_search`, where n is the size of the map. + +> **NOTE** +> +> Since Doctrine always only looks at the owning side of a bidirectional association for updates, it is not necessary +> for write operations that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. +> This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. + +> **NOTE** +> +> You can also clear the contents of a whole collection using the `Collections::clear()` method. You +> should be aware that using this method can lead to a straight and optimized database delete or update call +> during the flush operation that is not aware of entities that have been re-added to the collection. +> +> Say you clear a collection of tags by calling `$post->getTags()->clear();` and then call +> `$post->getTags()->add($tag)`. This will not recognize tag being already added before and issue +> two database calls. + +++ Association Management Methods + +It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained. + +The following code shows updates to the previous User and Comment example that encapsulate much of +the association management code: + + [php] + class User + { + //... + public function markCommentRead(Comment $comment) { + // Collections implement ArrayAccess + $this->commentsRead[] = $comment; + } + + public function addComment(Comment $comment) { + if (count($this->commentsAuthored) == 0) { + $this->setFirstComment($comment); + } + $this->comments[] = $comment; + $comment->setAuthor($this); + } + + private function setFirstComment(Comment $c) { + $this->firstComment = $c; + } + + public function addFavorite(Comment $comment) { + $this->favorites->add($comment); + $comment->addUserFavorite($this); + } + + public function removeFavorite(Comment $comment) { + $this->favorites->removeElement($comment); + $comment->removeUserFavorite($this); + } + } + + class Comment + { + // .. + + public function addUserFavorite(User $user) { + $this->userFavorites[] = $user; + } + + public function removeUserFavorite(User $user) { + $this->userFavorites->removeElement($user); + } + } + +You will notice that `addUserFavorite` and `removeUserFavorite` do not call `addFavorite` and `removeFavorite`, +thus the bidirectional association is strictly-speaking still incomplete. However if you would naively add the +`addFavorite` in `addUserFavorite`, you end up with an infinite loop, so more work is needed. +As you can see, proper bidirectional association management in plain OOP is a non-trivial task +and encapsulating all the details inside the classes can be challenging. + +> **NOTE** +> +> If you want to make sure that your collections are perfectly encapsulated you should not return +> them from a `getCollectionName()` method directly, but call `$collection->toArray()`. This way a client programmer +> for the entity cannot circumvent the logic you implement on your entity for association management. For example: + + [php] + class User { + public function getReadComments() { + return $this->commentsRead->toArray(); + } + } + +This will however always initialize the collection, with all the performance penalties given the size. In +some scenarios of large collections it might even be a good idea to completely hide the read access behind +methods on the EntityRepository. + +There is no single, best way for association management. It greatly depends on the requirements of your concrete +domain model as well as your preferences. + +++ Synchronizing Bidirectional Collections + +In the case of Many-To-Many associations you as the developer are responsible to keep the collections on the +owning and inverse side up in sync, when you apply changes to them. Doctrine can only guarantee a consistent +state for the hydration, not for your client code. + +Using the User-Comment entities from above, a very simple example can show the possible caveats you can encounter: + + [php] + $user->getFavorites()->add($favoriteComment); + // not calling $favoriteComment->getUserFavorites()->add($user); + + $user->getFavorites()->contains($favoriteComment); // TRUE + $favoriteComment->getUerFavorites()->contains($user); // FALSE + +There are to approaches to handle this problem in your code: + +1. Ignore updating the inverse side of bidirectional collections, BUT never read from them in requests that changed + their state. In the next Request Doctrine hydrates the consistent collection state again. +2. Always keep the bidirectional collections in sync through association management methods. Reads of + the Collections directly after changes are consistent then. + +++ Transitive persistence / Cascade Operations + +Persisting, removing, detaching and merging individual entities can become pretty +cumbersome, especially when a larger object graph with collections is involved. +Therefore Doctrine 2 provides a mechanism for transitive persistence through +cascading of these operations. Each association to another entity or a collection +of entities can be configured to automatically cascade certain operations. By +default, no operations are cascaded. + +The following cascade options exist: + + * persist : Cascades persist operations to the associated entities. + * remove : Cascades remove operations to the associated entities. + * merge : Cascades merge operations to the associated entities. + * detach : Cascades detach operations to the associated entities. + * all : Cascades persist, remove, merge and detach operations to associated entities. + +The following example is an extension to the User-Comment example of this chapter. +Suppose in our application a user is created whenever he writes his first comment. +In this case we would use the following code: + + [php] + $user = new User(); + $myFirstComment = new Comment(); + $user->addComment($myFirstComment); + + $em->persist($user); + $em->persist($myFirstComment); + $em->flush(); + +Even if you *persist* a new User that contains our new Comment this code would fail +if you removed the call to `EntityManager#persist($myFirstComment)`. Doctrine 2 does +not cascade the persist operation to all nested entities that are new as well. + +More complicated is the deletion of all a users comments when he is removed from the system: + + $user = $em->find('User', $deleteUserId); + + foreach ($user->getAuthoredComments() AS $comment) { + $em->remove($comment); + } + $em->remove($user); + $em->flush(); + +Without the loop over all the authored comments Doctrine would use an UPDATE statement only +to set the foreign key to NULL and only the User would be deleted from the database +during the flush()-Operation. + +To have Doctrine handle both cases automatically we can change the `User#commentsAuthored` +property to cascade both the "persist" and the "remove" operation. + + [php] + class User + { + //... + /** + * Bidirectional - One-To-Many (INVERSE SIDE) + * + * @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"}) + */ + private $commentsAuthored; + //... + } + +Even though automatic cascading is convenient it should be used with care. +Do not blindly apply cascade=all to all associations as it will unnecessarily +degrade the performance of your application. For each cascade operation that gets +activated Doctrine also applies that operation to the association, be it +single or collection valued. + ++++ Persistence by Reachability: Cascade Persist + +There are additional semantics that apply to the Cascade Persist operation. +During each flush() operation Doctrine detects if there are new entities in any +collection and three possible cases can happen: + +1. New entities in a collection marked as cascade persist will be directly persisted by Doctrine. +2. New entities in a collection not marked as cascade persist will produce an Exception and rollback the flush() operation. +3. Collections without new entities are skipped. + +This concept is called Persistence by Reachability: New entities that are found on +already managed entities are automatically persisted as long as the association is defined +as cascade persist. \ No newline at end of file diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 5c677cc67..fbfc45f20 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -9,6 +9,15 @@ A Unit of Work can be manually closed by calling EntityManager#close(). Any changes to objects within this Unit of Work that have not yet been persisted are lost. +> **NOTE** +> +> It is very important to understand that only `EntityManager#flush()` ever causes +> write operations against the database to be executed. Any other methods such +> as `EntityManager#persist($entity)` or `EntityManager#remove($entity)` only +> notify the UnitOfWork to perform these operations during flush. +> +> Not calling `EntityManager#flush()` will lead to all changes during that request being lost. + ++ Persisting entities An entity can be made persistent by passing it to the `EntityManager#persist($entity)` @@ -174,352 +183,6 @@ and you want to modify and persist such an entity. > there is no need to use `merge`. I.e. you can simply pass detached objects from a cache > directly to the view. - -++ Associations - -Associations between entities are represented just like in regular object-oriented PHP, with references to other objects or collections of objects. When it comes to persistence, it is important to understand three main things: - - * The concept of owning and inverse sides in bidirectional associations as described [here](http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#owning-side-and-inverse-side). - * If an entity is removed from a collection, the association is removed, not the entity itself. A collection of entities always only represents the association to the containing entities, not the entity itself. - * Collection-valued persistent fields have to be instances of the `Doctrine\Common\Collections\Collection` interface. [See here](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities:persistent-fields) for more details. - -+++ Example Associations - -We will use a simple comment system with Users and Comments as entity to show examples of association management: - - [php] - /** @Entity */ - class User - { - /** @Id @GeneratedValue @Column(type="string") */ - private $id; - - /** - * Bidirectional - Many users have Many favorite comments (OWNING SIDE) - * - * @ManyToMany(targetEntity="Comment", inversedBy="userFavorites") - * @JoinTable(name="user_favorite_comments", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="favorite_comment_id", referencedColumnName="id")} - * ) - */ - private $favorites; - - /** - * Unidirectional - Many users have marked many comments as read - * - * @ManyToMany(targetEntity="Comment") - * @JoinTable(name="user_read_comments", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="comment_id", referencedColumnName="id")} - * ) - */ - private $commentsRead; - - /** - * Bidirectional - One-To-Many (INVERSE SIDE) - * - * @OneToMany(targetEntity="Comment", mappedBy="author") - */ - private $commentsAuthored; - - /** - * Unidirectional - Many-To-One - * - * @ManyToOne(targetEntity="Comment") - */ - private $firstComment; - } - - /** @Entity */ - class Comment - { - /** @Id @GeneratedValue @Column(type="string") */ - private $id; - - /** - * Bidirectional - Many comments are favorited by many users (INVERSE SIDE) - * - * @ManyToMany(targetEntity="User", mappedBy="favorites") - */ - private $userFavorites; - - /** - * Bidirectional - Many Comments are authored by one user (OWNING SIDE) - * - * @ManyToOne(targetEntity="User", inversedBy="authoredComments") - */ - private $author; - } - -This two entities generate the following MySQL Schema (Foreign Key definitions omitted): - - [sql] - CREATE TABLE User ( - id VARCHAR(255) NOT NULL, - firstComment_id VARCHAR(255) DEFAULT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - - CREATE TABLE Comment ( - id VARCHAR(255) NOT NULL, - author_id VARCHAR(255) DEFAULT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - - CREATE TABLE user_favorite_comments ( - user_id VARCHAR(255) NOT NULL, - favorite_comment_id VARCHAR(255) NOT NULL, - PRIMARY KEY(user_id, favorite_comment_id) - ) ENGINE = InnoDB; - - CREATE TABLE user_read_comments ( - user_id VARCHAR(255) NOT NULL, - comment_id VARCHAR(255) NOT NULL, - PRIMARY KEY(user_id, comment_id) - ) ENGINE = InnoDB; - -++ Establishing Associations - -Establishing an association between two entities is straight-forward. Here are some examples for the unidirectional -relations of the `User`: - - [php] - class User - { - // ... - public function getReadComments() { - return $this->commentsRead; - } - - public function setFirstComment(Comment $c) { - $this->firstComment = $c; - } - } - -Interaction would then look like the following code parts, `$em` here is an instance of the EntityManager: - - $user = $em->find('User', $userId); - - // unidirectional many to many - $comment = $em->find('Comment', $readCommentId); - $user->getReadComments()->add($comment); - - $em->flush(); - - // unidirectional many to one - $myFirstComment = new Comment(); - $user->setFirstComment($myFirstComment); - - $em->persist($myFirstComment); - $em->flush(); - -In the case of bi-directional associations you have to update the fields on both sides: - - [php] - class User - { - // .. - - public function getAuthoredComments() { - return $this->commentsAuthored; - } - - public function getFavoriteComments() { - return $this->favorites; - } - } - - class Comment - { - // ... - - public function getUserFavorites() { - return $this->userFavorites; - } - - public function setAuthor(User $author = null) { - $this->author = $author; - } - } - - // Many-to-Many - $user->getFavorites()->add($favoriteComment); - $favoriteComment->getUserFavorites()->add($user); - - $em->flush(); - - // Many-To-One / One-To-Many Bidirectional - $newComment = new Comment(); - $user->getAuthoredComments()->add($newComment); - $newComment->setAuthor($user); - - $em->persist(); - $m->flush(); - - -Notice how always both sides of the bidirectional association are updated. The previous unidirectional associations were simpler to handle. - -++ Removing Associations - -Removing an association between two entities is similarly straight-forward. There are two strategies -to do so, by key and by element. Here are some examples: - - [php] - // Remove by Elements - $user->getComments()->removeElement($comment); - $comment->setAuthor(null); - - $user->getFavorites()->removeElement($comment); - $comment->getUserFavorites()->removeElement($user); - - // Remove by Key - $user->getComments()->removeElement($ithComment); - $comment->setAuthor(null); - -You need to call `$em->flush()` to make persist these changes in the database permanently. - -Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently -simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does only -allows null values if `null` is set as default value. Otherwise setAddress(null) will fail for removing the association. -If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`. -This can also provide better encapsulation as it hides the internal meaning of not having an address. - -When working with collections, keep in mind that a Collection is essentially an ordered map (just like a PHP array). -That is why the `remove` operation accepts an index/key. `removeElement` is a separate method -that has O(n) complexity using `array_search`, where n is the size of the map. - -> **NOTE** -> -> Since Doctrine always only looks at the owning side of a bidirectional association for updates, it is not necessary -> for write operations that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. -> This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. - -> **NOTE** -> -> You can also clear the contents of a whole collection using the `Collections::clear()` method. You -> should be aware that using this method can lead to a straight and optimized database delete or update call -> during the flush operation that is not aware of entities that have been re-added to the collection. -> -> Say you clear a collection of tags by calling `$post->getTags()->clear();` and then call -> `$post->getTags()->add($tag)`. This will not recognize tag being already added before and issue -> two database calls. - -++ Association Management Methods - -It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained. - -The following code shows updates to the previous User and Comment example that encapsulate much of -the association management code: - - [php] - class User - { - //... - public function markCommentRead(Comment $comment) { - // Collections implement ArrayAccess - $this->commentsRead[] = $comment; - } - - public function addComment(Comment $comment) { - if (count($this->commentsAuthored) == 0) { - $this->setFirstComment($comment); - } - $this->comments[] = $comment; - $comment->setAuthor($this); - } - - private function setFirstComment(Comment $c) { - $this->firstComment = $c; - } - - public function addFavorite(Comment $comment) { - $this->favorites->add($comment); - $comment->addUserFavorite($this); - } - - public function removeFavorite(Comment $comment) { - $this->favorites->removeElement($comment); - $comment->removeUserFavorite($this); - } - } - - class Comment - { - // .. - - public function addUserFavorite(User $user) { - $this->userFavorites[] = $user; - } - - public function removeUserFavorite(User $user) { - $this->userFavorites->removeElement($user); - } - } - -You will notice that `addUserFavorite` and `removeUserFavorite` do not call `addFavorite` and `removeFavorite`, -thus the bidirectional association is strictly-speaking still incomplete. However if you would naively add the -`addFavorite` in `addUserFavorite`, you end up with an infinite loop, so more work is needed. -As you can see, proper bidirectional association management in plain OOP is a non-trivial task -and encapsulating all the details inside the classes can be challenging. - -> **NOTE** -> -> If you want to make sure that your collections are perfectly encapsulated you should not return -> them from a `getCollectionName()` method directly, but call `$collection->toArray()`. This way a client programmer -> for the entity cannot circumvent the logic you implement on your entity for association management. For example: - - [php] - class User { - public function getReadComments() { - return $this->commentsRead->toArray(); - } - } - -This will however always initialize the collection, with all the performance penalties given the size. In -some scenarios of large collections it might even be a good idea to completely hide the read access behind -methods on the EntityRepository. - -There is no single, best way for association management. It greatly depends on the requirements of your concrete -domain model as well as your preferences. - -++ Transitive persistence - -Persisting, removing, detaching and merging individual entities can become pretty -cumbersome, especially when a larger object graph with collections is involved. -Therefore Doctrine 2 provides a mechanism for transitive persistence through -cascading of these operations. Each association to another entity or a collection -of entities can be configured to automatically cascade certain operations. By -default, no operations are cascaded. - -The following cascade options exist: - - * persist : Cascades persist operations to the associated entities. - * remove : Cascades remove operations to the associated entities. - * merge : Cascades merge operations to the associated entities. - * detach : Cascades detach operations to the associated entities. - * all : Cascades persist, remove, merge and detach operations to associated entities. - -The following example shows an association to a number of addresses. If persist() -or remove() is invoked on any User entity, it will be cascaded to all associated -Address entities in the $addresses collection. - - [php] - class User - { - //... - /** - * @OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "remove"}) - */ - private $addresses; - //... - } - -Even though automatic cascading is convenient it should be used with care. -Do not blindly apply cascade=all to all associations as it will unnecessarily -degrade the performance of your application. - - ++ Synchronization with the Database The state of persistent entities is synchronized with the database on flush of an `EntityManager` @@ -528,11 +191,21 @@ persistent entities and their relationships to the database. Thereby bidirection are persisted based on the references held by the owning side of the relationship as explained in the Association Mapping chapter. +When `EntityManager#flush()` is called, Doctrine inspects all managed, new and removed entities +and will perform the following operations. + ++++ Synchronizing New and Managed Entities + The flush operation applies to a managed entity with the following semantics: -* The entity itself is synchronized to the database, if it has changed. +* The entity itself is synchronized to the database using a SQL UPDATE statement, only if at least one persistent field has changed. +* No SQL updates are executed if the entity did not change. -For all (initialized) relationships of the entity the following semantics apply to each +The flush operation applies to a new entity with the following semantics: + +* The entity itself is synchronized to the database using a SQL INSERT statement. + +For all (initialized) relationships of the new or managed entity the following semantics apply to each associated entity X: * If X is new and persist operations are configured to cascade on the relationship, @@ -542,10 +215,13 @@ associated entity X: * If X is removed and persist operations are configured to cascade on the relationship, an exception will be thrown as this indicates a programming error (X would be re-persisted by the cascade). * If X is detached and persist operations are configured to cascade on the relationship, - an exception will be thrown (This is semantically the same as passing X to persist()). + an exception will be thrown (This is semantically the same as passing X to persist()). + ++++ Synchronizing Removed Entities The flush operation applies to a removed entity by deleting its persistent state from the database. -No cascade options are relevant for removed entities on flush. +No cascade options are relevant for removed entities on flush, the cascade remove option is already +executed during `EntityManager#remove($entity)`. +++ The size of a Unit of Work From 9c28cc5b1946ff2a62075a92dbe5304e9e5f27db Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 24 Jul 2010 10:53:35 +0200 Subject: [PATCH 081/430] Rephrased some sentences in Working with Assocations --- manual/en/working-with-associations.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/manual/en/working-with-associations.txt b/manual/en/working-with-associations.txt index 04f820d1a..28d19c674 100644 --- a/manual/en/working-with-associations.txt +++ b/manual/en/working-with-associations.txt @@ -1,5 +1,3 @@ -++ Associations - Associations between entities are represented just like in regular object-oriented PHP, with references to other objects or collections of objects. When it comes to persistence, it is important to understand three main things: @@ -9,10 +7,13 @@ or collections of objects. When it comes to persistence, it is important to unde Changes to associations in your code are not synchronized to the database directly, but upon calling `EntityManager#flush()`. -+++ Example: Entities with Associations +To describe all the concepts of working with associations we introduce a specific set of example entities that show +all the different flavors of association management in Doctrine. + ++++ Association Example Entities We will use a simple comment system with Users and Comments as entities to show examples of association management. -See the docblocks of each association in the following example for information about its type and if its the owning or inverse side. +See the PHP docblocks of each association in the following example for information about its type and if its the owning or inverse side. [php] /** @Entity */ @@ -124,8 +125,9 @@ relations of the `User`: } } -Interaction would then look like the following code parts, `$em` here is an instance of the EntityManager: +The interaction code would then look like in the following snippet (`$em` here is an instance of the EntityManager): + [php] $user = $em->find('User', $userId); // unidirectional many to many From c6e423024e1cbb5cfa932ea8df45961c8ec25b5e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 24 Jul 2010 14:23:35 +0200 Subject: [PATCH 082/430] Reference Configuration section for bootstrapping after each Install section --- manual/en/introduction.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index 0d1fb6e98..07d51c480 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -144,6 +144,9 @@ see what you can do with it. You can also use Doctrine 2 by downloading the latest release package from [the download page](http://www.doctrine-project.org/download). +See the configuration section on how to configure and bootstrap a downloaded +version of Doctrine. + +++ GitHub Alternatively you can clone the latest version of Doctrine 2 via GitHub.com: @@ -159,6 +162,9 @@ submodules for the Common and DBAL package dependencies: This updates your Git checkout to use the Doctrine\Common and Doctrine\DBAL package versions that are recommended for the cloned Master version of Doctrine 2. +See the configuration chapter on how to configure a Github installation of Doctrine +with regards to autoloading. + > **NOTE** > > You should not combine the Doctrine-Common, Doctrine-DBAL and Doctrine-ORM master commits From 84c1cc8865da095d9bd9a51d06f71af269061624 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 24 Jul 2010 15:16:13 +0200 Subject: [PATCH 083/430] Fix bug in the documentation leading to an error --- manual/en/working-with-associations.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/working-with-associations.txt b/manual/en/working-with-associations.txt index 28d19c674..9094380c1 100644 --- a/manual/en/working-with-associations.txt +++ b/manual/en/working-with-associations.txt @@ -10,7 +10,7 @@ Changes to associations in your code are not synchronized to the database direct To describe all the concepts of working with associations we introduce a specific set of example entities that show all the different flavors of association management in Doctrine. -+++ Association Example Entities +++ Association Example Entities We will use a simple comment system with Users and Comments as entities to show examples of association management. See the PHP docblocks of each association in the following example for information about its type and if its the owning or inverse side. From bf235030003ae213af7466845be3e2ac6e0c2e72 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 29 Jul 2010 18:41:16 +0800 Subject: [PATCH 084/430] Missing field name? --- manual/en/events.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/events.txt b/manual/en/events.txt index 951ed153d..f08bac819 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -387,7 +387,7 @@ A simple example for this event looks like: public function preUpdate(PreUpdateEventArgs $eventArgs) { if ($eventArgs->getEntity() instanceof User) { - if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue() == 'Alice') { + if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') { $eventArgs->setNewValue('name', 'Bob'); } } From fcea78a6cb705695cacf8f38a12f30eacca195c0 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Sat, 31 Jul 2010 12:00:34 +0200 Subject: [PATCH 085/430] Updated merging docs. --- manual/en/working-with-objects.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index fbfc45f20..9b10a3377 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -159,8 +159,8 @@ Example: The semantics of the merge operation, applied to an entity X, are as follows: -* If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity or a new managed copy X' of X is created. -* If X is a new entity instance, an InvalidArgumentException will be thrown. +* If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity. +* If X is a new entity instance, a new managed copy X' will be created and the state of X is copied onto this managed instance. * If X is a removed entity instance, an InvalidArgumentException will be thrown. * If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been mapped with the cascade element value MERGE or ALL (see "Transitive Persistence"). * For all entities Y referenced by relationships from X having the cascade element value From 96538ae2fe037c44329ffd87c013e280bad60de8 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 1 Aug 2010 15:41:40 +0200 Subject: [PATCH 086/430] Seperated Change Tracking Policies and Partial Objects from Configuration Chapter, Finished XML Mapping Chapter with a reference on all tags and attributes --- manual/en.txt | 2 + manual/en/configuration.txt | 174 -------------- manual/en/xml-mapping.txt | 452 ++++++++++++++++++++++++++++++++++-- 3 files changed, 441 insertions(+), 187 deletions(-) diff --git a/manual/en.txt b/manual/en.txt index 0319f824c..5c79a1bb2 100644 --- a/manual/en.txt +++ b/manual/en.txt @@ -12,6 +12,8 @@ + DQL (Doctrine Query Language) + Query Builder + Native SQL ++ Change Tracking Policies ++ Partial Objects + XML Mapping + YAML Mapping + Annotations Reference diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index 4431f2124..8b23bbfe2 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -245,180 +245,6 @@ or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is direct DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The DBAL configuration is explained in the [DBAL section](./../../../../../dbal/2.0/docs/reference/configuration/en). -++ 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 should 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; - } - - protected function _onPropertyChanged($propName, $oldValue, $newValue) - { - if ($this->_listeners) { - foreach ($this->_listeners as $listener) { - $listener->propertyChanged($this, $propName, $oldValue, $newValue); - } - } - } - } - -Then, in each property setter of this class or derived classes, you need to -invoke `_onPropertyChanged` as follows to notify listeners: - - [php] - // ... - - class MyEntity implements NotifyPropertyChanged - { - // ... - - public function setData($data) - { - if ($data != $this->data) { - $this->_onPropertyChanged('data', $this->data, $data); - $this->data = $data; - } - } - } - -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. - -++ Partial Objects - -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. - ++ Proxy Objects A proxy object is an object that is put in place or used instead of the "real" object. A proxy object can add behavior to the object being proxied without that object being aware of it. In Doctrine 2, proxy objects are used to realize several features but mainly for transparent lazy-loading. diff --git a/manual/en/xml-mapping.txt b/manual/en/xml-mapping.txt index 063b2cc89..140308b9c 100644 --- a/manual/en/xml-mapping.txt +++ b/manual/en/xml-mapping.txt @@ -15,8 +15,11 @@ The XML driver is backed by an XML Schema document that describes the structure The XML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: * Each entity/mapped superclass must get its own dedicated XML mapping document. - * The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.). - * All mapping documents should get the extension ".dcm.xml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough. + * The name of the mapping document must consist of the fully qualified name of the class, where namespace + separators are replaced by dots (.). For example an Entity with the fully qualified class-name "MyProject\Entities\User" + would require a mapping file "MyProject.Entities.User.dcm.xml" unless the extension is changed. + * All mapping documents should get the extension ".dcm.xml" to identify it as a Doctrine mapping file. This is more of + a convention and you are not forced to do this. You can change the file extension easily enough. - @@ -26,8 +29,8 @@ The XML mapping document of a class is loaded on-demand the first time it is req It is recommended to put all XML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the XmlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: [php] - // $config instanceof Doctrine\ORM\Configuration - $driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files')); + $config = new \Doctrine\ORM\Configuration(); + $driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files1', '/path/to/files2')); $config->setMetadataDriverImpl($driver); @@ -38,46 +41,469 @@ As a quick start, here is a small example document that makes use of several com [xml] // Doctrine.Tests.ORM.Mapping.User.dcm.xml - + http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> + + + + + + + + + - + + + + - + + - - + + + + + + + + + - + - + + - Be aware that class-names specified in the XML files should be fully qualified. \ No newline at end of file + Be aware that class-names specified in the XML files should be fully qualified. + +++ XML-Element Reference + +The XML-Element reference explains all the tags and attributes that the Doctrine Mapping XSD Schema defines. +You should read the Basic-, Association- and Inheritance Mapping chapters to understand what each of this +definitions means in detail. + ++++ Defining an Entity + +Each XML Mapping File contains the definition of one entity, specified as the `` element +as a direct child of the `` element: + + [xml] + + + + + + +Required attributes: + +* name - The fully qualified class-name of the entity. + +Optional attributes: + +* table - The Table-Name to be used for this entity. Otherwise the Unqualified Class-Name is used by default. +* repository-class - The fully qualified class-name of an alternative `Doctrine\ORM\EntityRepository` implementation to be used with this entity. +* inheritance-type - The type of inheritance, defaults to none. A more detailed description follows in the *Defining Inheritance Mappings* section. + ++++ Defining Fields + +Each entity class can contain zero to infinite fields that are managed by Doctrine. You can define +them using the `` element as a children to the `` element. The field element is only +used for primitive types that are not the ID of the entity. For the ID mapping you have to use the `` element. + + [xml] + + + + + + + + + +Required attributes: + +* name - The name of the Property/Field on the given Entity PHP class. + +Optional attributes: + +* type - The `Doctrine\DBAL\Types\Type` name, defaults to "string" +* column - Name of the column in the database, defaults to the field name. +* length - The length of the given type, for use with strings only. +* unique - Should this field contain a unique value across the table? Defaults to false. +* nullable - Should this field allow NULL as a value? Defaults to false. +* version - Should this field be used for optimistic locking? Only works on fields with type integer or datetime. +* scale - Scale of a decimal type. +* precision - Precision of a decimal type. +* column-definition - Optional alternative SQL representation for this column. This definition begin after the + field-name and has to specify the complete column definition. Using this feature will turn this field dirty + for Schema-Tool update commands at all times. + ++++ Defining Identity and Generator Strategies + +An entity has to have at least one `` element. For composite keys you can specify more than one id-element, +however surrogate keys are recommended for use with Doctrine 2. The Id field allows to define properties of +the identifier and allows a subset of the `` element attributes: + + [xml] + + + + +Required attributes: + +* name - The name of the Property/Field on the given Entity PHP class. +* type - The `Doctrine\DBAL\Types\Type` name, preferably "string" or "integer". + +Optional attributes: + +* column - Name of the column in the database, defaults to the field name. + +Using the simplified definition above Doctrine will use no identifier strategy for this entity. That means +you have to manually set the identifier before calling `EntityManager#persist($entity)`. This is the +so called `ASSIGNED` strategy. + +If you want to switch the identifier generation strategy you have to nest a `` element inside +the id-element. This of course only works for surrogate keys. For composite keys you always have to use +the `ASSIGNED` strategy. + + [xml] + + + + + + +The following values are allowed for the `` strategy attribute: + +* AUTO - Automatic detection of the identifier strategy based on the preferred solution of the database vendor. +* IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs available to Doctrine AFTER the INSERT statement has been executed. +* SEQUENCE - Use of a database sequence to retrieve the entity-ids. This is possible before the INSERT statement is executed. + +If you are using the SEQUENCE strategy you can define an additional element to describe the sequence: + + [xml] + + + + + + + +Required attributes for ``: + +* sequence-name - The name of the sequence + +Optional attributes for ``: + +* allocation-size - By how much steps should the sequence be incremented when a value is retrieved. Defaults to 1 +* initial-value - What should the initial value of the sequence be. + +> **NOTE** +> +> If you want to implement a cross-vendor compatible application you have to specify and +> additionaly define the element, if Doctrine chooses the sequence strategy for a platform. + ++++ Defining a Mapped Superclass + +Sometimes you want to define a class that multiple entities inherit from, which itself is not an entity however. +The chapter on *Inheritance Mapping* describes a Mapped Superclass in detail. You can define it in XML using +the `` tag. + + [xml] + + + + + + + +Required attributes: + +* name - Class name of the mapped superclass. + +You can nest any number of `` and unidirectional `` or `` associations inside +a mapped superclass. + ++++ Defining Inheritance Mappings + +There are currently two inheritance persistence strategies that you can choose from when defining entities that +inherit from each other. Single Table inheritance saves the fields of the complete inheritance hierachy in a single table, +joined table inheritance creates a table for each entity combining the fields using join conditions. + +You can specify the inheritance type in the `` element and then use the `` and +`` attributes. + + [xml] + + + + + + + + + +The allowed values for inheritance-type attribute are `JOINED` or `SINGLE_TABLE`. + +> **NOTE** +> +> All inheritance related definitions have to be defined on the root entity of the hierachy. + ++++ Defining Lifecycle Callbacks + +You can define the lifecycle callback methods on your entities using the `` element: + + [xml] + + + + + + + ++++ Defining One-To-One Relations + +You can define One-To-One Relations/Assocations using the `` element. The required +and optional attributes depend on the associations being on the inverse or owning side. + +For the inverse side the mapping is as simple as: + + [xml] + + + + +Required attributes for inverse One-To-One: + +* field - Name of the property/field on the entitys PHP class. +* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. +* mapped-by - Name of the field on the owning side (here Address entity) that contains the owning side association. + +For the owning side this mapping would look like: + + [xml] + + + + +Required attributes for owning One-to-One: + +* field - Name of the property/field on the entitys PHP class. +* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. + +Optional attributes for owning One-to-One: + +* inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference. +* orphan-removal - If true, the inverse side entity is always deleted when the owning side entity is. Defaults to false. +* fetch - Either LAZY or FETCH, defaults to LAZY. This attribute makes only sense on the owning side, the inverse side *ALWAYS* has to use the `FETCH` strategy. + +The definition for the owning side relies on a bunch of mapping defaults for the join column names. +Without the nested `` element Doctrine assumes to foreign key to be called `user_id` on the Address +Entities table. This is because the `MyProject\Address` entity is the owning side of this association, which means +it contains the foreign key. + +The completed explictly defined mapping is: + + [xml] + + + + + + ++++ Defining Many-To-One Associations + +The many-to-one association is *ALWAYS* the owning side of any bidirectional association. This simplifies the mapping +compared to the one-to-one case. The minimal mapping for this association looks like: + + [xml] + + + + +Required attributes: + +* field - Name of the property/field on the entitys PHP class. +* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. + +Optional attributes: + +* inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference. +* orphan-removal - If true the entity on the inverse side is always deleted when the owning side entity is and it is not connected to any other owning side entity anymore. Defaults to false. +* fetch - Either LAZY or FETCH, defaults to LAZY. + +This definition relies on a bunch of mapping defaults with regards to the naming of the join-column/foreign key. The +explictly defined mapping includes a `` tag nested inside the many-to-one association tag: + + [xml] + + + + + + +The join-column attribute `name` specifies the column name of the foreign key and +the `referenced-column-name` attribute specifies the name of the primary key column +on the User entity. + ++++ Defining One-To-Many Associations + +The one-to-many association is *ALWAYS* the inverse side of any association. There exists no such thing as a +uni-directional one-to-many association, which means this association only ever exists for bi-directional associations. + + [xml] + + + + +Required attributes: + +* field - Name of the property/field on the entitys PHP class. +* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. +* mapped-by - Name of the field on the owning side (here Phonenumber entity) that contains the owning side association. + +Optional attributes: + +* fetch - Either LAZY or FETCH, defaults to LAZY. + ++++ Defining Many-To-Many Associations + +From all the associations the many-to-many has the most complex definition. When you rely on the mapping defaults +you can omit many definitions and rely on their implicit values. + + [xml] + + + + +Required attributes: + +* field - Name of the property/field on the entitys PHP class. +* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. + +Optional attributes: + +* mapped-by - Name of the field on the owning side that contains the owning side association if the defined many-to-many association is on the inverse side. +* inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference. +* fetch - Either LAZY or FETCH, defaults to LAZY. + +The mapping defaults would lead to a join-table with the name "User_Group" being created that contains two columns +"user_id" and "group_id". The explicit definition of this mapping would be: + + [xml] + + + + + + + + + + + + + +Here both the and tags are necessary to tell Doctrine for which side the +specified join-columns apply. These are nested inside a `` attribute which allows to specify +the table name of the many-to-many join-table. + ++++ Cascade Element + +Doctrine allows cascading of several UnitOfWork operations to related entities. You can specifiy the cascade +operations in the `` element inside any of the association mapping tags. + + [xml] + + + + + + + + +Besides `` the following operations can be specifed by their respective tags: + +* +* +* +* + ++++ Join Column Element + +In any explicitly defined association mapping you will need the `` tag. It defines how the +foreign key and primary key names are called that are used for joining two entities. + +Required attributes: + +* name - The column name of the foreign key. +* referenced-column-name - The column name of the associated entities primary key + +Optional attributes: + +* unique - If the join column should contain a UNIQUE constraint. This makes sense for Many-To-Many join-columns only to simulate a one-to-many unidirectional using a join-table. +* nullable - should the join column be nullable, defaults to true. +* on-delete - Foreign Key Cascade action to perform when entity is deleted, defaults to NO ACTION/RESTRICT but can be set to "CASCADE". + ++++ Defining Order of To-Many Associations + +You can require one-to-many or many-to-many associations to be retrieved using an additional `ORDER BY`. + + [xml] + + + + + + + + ++++ Defining Indexes or Unique Constraints + +To define additional indexes or unique constraints on the entities table you can use the +`` and `` elements: + + [xml] + + + + + + + + + + + + +You have to specify the column and not the entity-class field names in the index and unique-constraint +definitions. \ No newline at end of file From 93c061b7a5cd53c5ddbfa4ec20c0158b6ee02625 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 1 Aug 2010 15:47:34 +0200 Subject: [PATCH 087/430] Fixed some escaping issue with xml reference --- manual/en/xml-mapping.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/manual/en/xml-mapping.txt b/manual/en/xml-mapping.txt index 140308b9c..b96420147 100644 --- a/manual/en/xml-mapping.txt +++ b/manual/en/xml-mapping.txt @@ -147,7 +147,7 @@ used for primitive types that are not the ID of the entity. For the ID mapping y - + Required attributes: @@ -433,7 +433,7 @@ The mapping defaults would lead to a join-table with the name "User_Group" being -Here both the and tags are necessary to tell Doctrine for which side the +Here both the `` and `` tags are necessary to tell Doctrine for which side the specified join-columns apply. These are nested inside a `` attribute which allows to specify the table name of the many-to-many join-table. @@ -453,10 +453,10 @@ operations in the `` element inside any of the association mapping ta Besides `` the following operations can be specifed by their respective tags: -* -* -* -* +* `` +* `` +* `` +* `` +++ Join Column Element From a6b5ee3cd330ef537b8f48377e16f95280c074fb Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 1 Aug 2010 16:37:53 +0200 Subject: [PATCH 088/430] 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 From c5512b933ec730fee0b81862c67633fabaeaa98b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 1 Aug 2010 16:41:22 +0200 Subject: [PATCH 089/430] Add Limitations and Known Issues Appendix to the manual --- manual/en/limitations-and-known-issues.txt | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt index 135c1d2f1..c22a15a68 100644 --- a/manual/en/limitations-and-known-issues.txt +++ b/manual/en/limitations-and-known-issues.txt @@ -1,12 +1,8 @@ +We try to make using Doctrine2 a very pleasant experience. Therefore it is our believe +that being honest about the limitations to our users as early as possible is very important. 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! +This section should give you an overview of current limitations of Doctrine 2 as well as critical known issues that +you should know about. ++ Current Limitations @@ -159,4 +155,7 @@ to evaluate a feature that optionally persists and hydrates the keys of a Collec ++ Known Issues -There are currently no known critical/blocker or backward compatibility issues. \ No newline at end of file +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!* \ No newline at end of file From 437f14aa4ec5619171f307f5c93bb291aae23721 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 1 Aug 2010 16:44:01 +0200 Subject: [PATCH 090/430] Fix bug in ticket link --- manual/en/limitations-and-known-issues.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt index c22a15a68..c2005bdfb 100644 --- a/manual/en/limitations-and-known-issues.txt +++ b/manual/en/limitations-and-known-issues.txt @@ -82,8 +82,8 @@ how they translate into a Doctrine Mapping (Using Annotations): 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] +The following Jira Issue [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 From ec307a6046b9958a426eb463ed032846a7b1773e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 1 Aug 2010 16:56:36 +0200 Subject: [PATCH 091/430] Added small block on not supporting behaviors to limitations section --- manual/en/limitations-and-known-issues.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt index c2005bdfb..d39b17663 100644 --- a/manual/en/limitations-and-known-issues.txt +++ b/manual/en/limitations-and-known-issues.txt @@ -9,6 +9,20 @@ you should know about. 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. ++++ Behaviors + +Doctrine 2 *will never* include a behavior system like Doctrine 1 in the core library. We don't think behaviors +add more value than they cost pain and debugging hell. Please see the many different blog posts we have written on this +topics: + +* [Doctrine2 "Behaviors" in a Nutshell](http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell) +* [A re-usable Versionable behavior for Doctrine2](http://www.doctrine-project.org/blog/doctrine2-versionable) +* [Write your own ORM on top of Doctrine2](http://www.doctrine-project.org/blog/your-own-orm-doctrine2) + +Doctrine 2 has enough hooks and extension points so that *you* can add whatever you wan't on top of it. +None of this will ever become core functionality of Doctrine2 however, you will have to rely on third party extensions +for magical behaviors. + +++ Foreign Keys as Identifiers There are many use-cases where you would want to use an Entity-Attribute-Value approach to modelling and From f89646eabed947249d18d894a5f450f94293b3fc Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 1 Aug 2010 17:02:27 +0200 Subject: [PATCH 092/430] Added note about NestedSet to limitations section --- manual/en/limitations-and-known-issues.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt index d39b17663..81fd37c69 100644 --- a/manual/en/limitations-and-known-issues.txt +++ b/manual/en/limitations-and-known-issues.txt @@ -167,6 +167,14 @@ to evaluate a feature that optionally persists and hydrates the keys of a Collec [Ticket DDC-213](http://www.doctrine-project.org/jira/browse/DDC-213) ++++ Nested Set + +NestedSet was offered as a behavior in Doctrine 1 and will not be included in the core of Doctrine 2. However there +are already two extensions out there that offer support for Nested Set with Doctrine 2: + +* [Doctrine2 Hierachical-Structural Behavior](http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior) +* [Doctrine2 NestedSet](http://github.com/blt04/doctrine2-nestedset) + ++ Known Issues The Known Issues section describes critical/blocker bugs and other issues that are either complicated to fix, From d859a77b527992619ab56312b0c292ef4a1e0e79 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 1 Aug 2010 17:09:02 +0200 Subject: [PATCH 093/430] Fix a typo, reworked main paragraph and moved behavior section to bottom --- manual/en/limitations-and-known-issues.txt | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt index 81fd37c69..5373cc1f4 100644 --- a/manual/en/limitations-and-known-issues.txt +++ b/manual/en/limitations-and-known-issues.txt @@ -1,5 +1,5 @@ -We try to make using Doctrine2 a very pleasant experience. Therefore it is our believe -that being honest about the limitations to our users as early as possible is very important. +We try to make using Doctrine2 a very pleasant experience. Therefore we think it is very important +to be honest about the current limitations to our users. 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 critical known issues that you should know about. @@ -9,20 +9,6 @@ you should know about. 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. -+++ Behaviors - -Doctrine 2 *will never* include a behavior system like Doctrine 1 in the core library. We don't think behaviors -add more value than they cost pain and debugging hell. Please see the many different blog posts we have written on this -topics: - -* [Doctrine2 "Behaviors" in a Nutshell](http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell) -* [A re-usable Versionable behavior for Doctrine2](http://www.doctrine-project.org/blog/doctrine2-versionable) -* [Write your own ORM on top of Doctrine2](http://www.doctrine-project.org/blog/your-own-orm-doctrine2) - -Doctrine 2 has enough hooks and extension points so that *you* can add whatever you wan't on top of it. -None of this will ever become core functionality of Doctrine2 however, you will have to rely on third party extensions -for magical behaviors. - +++ Foreign Keys as Identifiers There are many use-cases where you would want to use an Entity-Attribute-Value approach to modelling and @@ -167,6 +153,20 @@ to evaluate a feature that optionally persists and hydrates the keys of a Collec [Ticket DDC-213](http://www.doctrine-project.org/jira/browse/DDC-213) ++++ Behaviors + +Doctrine 2 *will never* include a behavior system like Doctrine 1 in the core library. We don't think behaviors +add more value than they cost pain and debugging hell. Please see the many different blog posts we have written on this +topics: + +* [Doctrine2 "Behaviors" in a Nutshell](http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell) +* [A re-usable Versionable behavior for Doctrine2](http://www.doctrine-project.org/blog/doctrine2-versionable) +* [Write your own ORM on top of Doctrine2](http://www.doctrine-project.org/blog/your-own-orm-doctrine2) + +Doctrine 2 has enough hooks and extension points so that *you* can add whatever you want on top of it. +None of this will ever become core functionality of Doctrine2 however, you will have to rely on third party extensions +for magical behaviors. + +++ Nested Set NestedSet was offered as a behavior in Doctrine 1 and will not be included in the core of Doctrine 2. However there From 1fe35e518c44c591e44f2cf92388d7083e1af68d Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 2 Aug 2010 13:05:21 +0200 Subject: [PATCH 094/430] Fixed typo --- manual/en/events.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/events.txt b/manual/en/events.txt index f08bac819..6a8eaa527 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -268,7 +268,7 @@ carefully since operations in the wrong event may produce lots of different erro lost updates/persists/removes. For the described events that are also lifecycle callback events the restrictions -apply aswell, with the additional restriction that you do not have access to the EntityManager +apply as well, with the additional restriction that you do not have access to the EntityManager or UnitOfWork APIs inside these events. +++ prePersist From fa5768b14da80e2e1e648f2dd71ed7158bd60978 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 2 Aug 2010 13:13:36 +0200 Subject: [PATCH 095/430] Fixed typos --- manual/en/events.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/en/events.txt b/manual/en/events.txt index 6a8eaa527..aa405450d 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -290,7 +290,7 @@ The following restrictions apply to `prePersist`: * If you are using a PrePersist Identity Generator such as sequences the ID value will *NOT* be available within any PrePersist events. * Doctrine will not recognize changes made to relations in a pre persist event - called by "reachibility" through a cascade persist unless you use the internal + called by "reachability" through a cascade persist unless you use the internal `UnitOfWork` API. We do not recommend such operations in the persistence by reachability context, so do this at your own risk and possibly supported by unit-tests. @@ -316,7 +316,7 @@ been computed. This means, the `onFlush` event has access to the sets of: * Collections scheduled for update * Collections scheduled for removal -To make use of the onFlush event you have to be familiar with interal UnitOfWork API, +To make use of the onFlush event you have to be familiar with the internal UnitOfWork API, which grants you access to the previously mentioned sets. See this example: [php] From 732dee92ad3a05587916b51194ed90eb356a7071 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 2 Aug 2010 13:16:07 +0200 Subject: [PATCH 096/430] I guess you meant "Now you can test" or "Now test the ..." --- manual/en/events.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/events.txt b/manual/en/events.txt index aa405450d..264c361d1 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -84,7 +84,7 @@ Now when you dispatch an event any event subscribers will be notified for that e [php] $evm->dispatchEvent(TestEventSubscriber::preFoo); -Now the test the `$eventSubscriber` instance to see if the `preFoo()` method was invoked. +Now you can test the `$eventSubscriber` instance to see if the `preFoo()` method was invoked. [php] if ($eventSubscriber->preFooInvoked) { From 70d12fc57aa3ea3b94b1b8ff8efdf8b7e0543988 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Sun, 8 Aug 2010 12:21:03 -0500 Subject: [PATCH 097/430] [DDC-663] Making example for installing via pear more generic. --- manual/en/introduction.txt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index 07d51c480..913525c68 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -77,17 +77,24 @@ line installation utility. To install just the `Common` package you can run the following command: - $ sudo pear install pear.doctrine-project.org/DoctrineCommon-2.0.0 + $ sudo pear install pear.doctrine-project.org/DoctrineCommon- If you want to use the Doctrine Database Abstraction Layer you can install it with the following command. - $ sudo pear install pear.doctrine-project.org/DoctrineDBAL-2.0.0 + $ sudo pear install pear.doctrine-project.org/DoctrineDBAL- Or, if you want to get the works and go for the ORM you can install it with the following command. - $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.0 + $ sudo pear install pear.doctrine-project.org/DoctrineORM- + +> **NOTE** +> The `` tag above represents the version you want to install. For example the +> current version at the time of writing this is `2.0.0BETA3` for the ORM, so you could +> install it like the following: +> +> $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.0BETA3 When you have a package installed via PEAR you can require and load the `ClassLoader` with the following code. From 9fa64adbda0bc706497a378fd99b08e718fa3204 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 8 Aug 2010 21:14:56 +0200 Subject: [PATCH 098/430] Added another fact to Limitations section --- manual/en/limitations-and-known-issues.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt index 5373cc1f4..87ba8081d 100644 --- a/manual/en/limitations-and-known-issues.txt +++ b/manual/en/limitations-and-known-issues.txt @@ -146,13 +146,19 @@ can benefit from custom persister implementations: * [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 ++++ Persist Keys 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) ++++ Mapping many tables to one entity + +It is not possible to map several equally looking tables onto one entity. For example if you have +a production and an archive table of a certain business concept then you cannot have both tables +map to the same entity. + +++ Behaviors Doctrine 2 *will never* include a behavior system like Doctrine 1 in the core library. We don't think behaviors From 82874181b0000541c5cde05f1389769c7ca99a8b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 8 Aug 2010 21:20:58 +0200 Subject: [PATCH 099/430] Add section on INSTANCE OF Dql Operator --- manual/en/dql-doctrine-query-language.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index 034b8b546..e0808e977 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -272,6 +272,12 @@ Get all users that have no phonenumber $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); $users = $query->getResult(); +Get all instances of a specific type, for use with inheritance hierachies: + + [php] + $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee'); + $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1'); + ++++ Partial Object Syntax By default when you run a DQL query in Doctrine and select only a subset of the From 42325094277416646638a65d7a78c09123672be9 Mon Sep 17 00:00:00 2001 From: Christian Heinrich Date: Tue, 17 Aug 2010 01:50:51 +0200 Subject: [PATCH 100/430] Renamed a variable --- manual/en/events.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/events.txt b/manual/en/events.txt index 264c361d1..782f87f83 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -82,7 +82,7 @@ method which returns an array of events it should be subscribed to. Now when you dispatch an event any event subscribers will be notified for that event. [php] - $evm->dispatchEvent(TestEventSubscriber::preFoo); + $evm->dispatchEvent(TestEvent::preFoo); Now you can test the `$eventSubscriber` instance to see if the `preFoo()` method was invoked. From d2d32e5439be5a0745be6a38772345a535855799 Mon Sep 17 00:00:00 2001 From: beberlei Date: Sun, 29 Aug 2010 10:38:57 +0200 Subject: [PATCH 101/430] Fix Cookbook XML Getting Started still refering to old Doctrine Console --- cookbook/en/getting-started-xml-edition.txt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cookbook/en/getting-started-xml-edition.txt b/cookbook/en/getting-started-xml-edition.txt index 93851cc13..2a1a06b3f 100644 --- a/cookbook/en/getting-started-xml-edition.txt +++ b/cookbook/en/getting-started-xml-edition.txt @@ -453,8 +453,10 @@ For the commandline tool to work a cli-config.php file has to be present in the where you will execute the doctrine command. Its a fairly simple file: [php] - $cliConfig = new Doctrine\Common\Cli\Configuration(); - $cliConfig->setAttribute('em', $entityManager); + $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( + 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager) + )); + $cli->setHelperSet($helperSet); You can then use your favorite console tool to call: @@ -462,6 +464,12 @@ You can then use your favorite console tool to call: doctrine@my-desktop> cd myproject/ doctrine@my-desktop> doctrine orm:schema-tool:create +> **NOTE** +> +> The `doctrine` command will only be present if you installed Doctrine from PEAR. +> Otherwise you will have to dig into the `bin/doctrine.php` code of your Doctrine 2 +> directory to setup your doctrine commandline client. + During the development you probably need to re-create the database several times when changing the Entity metadata. You can then either re-create the database: From 088ccf58fc3e9f8ca2bd544da00acc046602dd2f Mon Sep 17 00:00:00 2001 From: beberlei Date: Sun, 29 Aug 2010 10:50:07 +0200 Subject: [PATCH 102/430] Updated XML Cookbook Getting Started and Tools Chapter --- cookbook/en/getting-started-xml-edition.txt | 5 ++++- manual/en/tools.txt | 9 +++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cookbook/en/getting-started-xml-edition.txt b/cookbook/en/getting-started-xml-edition.txt index 2a1a06b3f..f78834064 100644 --- a/cookbook/en/getting-started-xml-edition.txt +++ b/cookbook/en/getting-started-xml-edition.txt @@ -458,7 +458,7 @@ where you will execute the doctrine command. Its a fairly simple file: )); $cli->setHelperSet($helperSet); -You can then use your favorite console tool to call: +You can then change into your project directory and call the Doctrine commandline tool: [console] doctrine@my-desktop> cd myproject/ @@ -469,6 +469,9 @@ You can then use your favorite console tool to call: > The `doctrine` command will only be present if you installed Doctrine from PEAR. > Otherwise you will have to dig into the `bin/doctrine.php` code of your Doctrine 2 > directory to setup your doctrine commandline client. +> +> See the [Tools section of the manual](http://www.doctrine-project.org/projects/orm/2.0/docs/reference/tools/en) +> on how to setup the Doctrine console correctly. During the development you probably need to re-create the database several times when changing the Entity metadata. You can then either re-create the database: diff --git a/manual/en/tools.txt b/manual/en/tools.txt index 24c380914..a3a8eda7c 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -1,13 +1,13 @@ ++ The Doctrine Console -The Doctrine Console is a Command Line Interface tool for simplifying many common commands during the development of a project that uses Doctrine. +The Doctrine Console is a Command Line Interface tool for simplifying common tasks during the development of a project that uses Doctrine 2. +++ Installation If you installed Doctrine 2 through PEAR, the `doctrine` command line tool should already be available to you. If you use Doctrine through SVN or a release package you need to copy the `doctrine` and `doctrine.php` files from the `tools/sandbox` or `bin` folder, respectively, to a location of your choice, for example a `tools` folder of your project. -In addition you may need to edit `doctrine.php` and adjust some paths to the new environment. You may want to add require_once() statement at the top of doctrine.php to set up the include_path for Doctrine classes. +You probably need to edit `doctrine.php` to adjust some paths to the new environment, most importantly the first line that includes the `Doctrine\Common\ClassLoader`. +++ Getting Help @@ -17,8 +17,9 @@ Type `doctrine` on the command line and you should see an overview of the availa +++ Configuration -Whenever the `doctrine` command line tool is invoked, it is only able to access Commands that were defined by developer. Dependency Injection (DI) is the responsable to inject support into this utility, but it is up to the developer define it. -The Doctrine CLI tool from the bin/ folder already defines all the DBAL and ORM commands shipped with Doctrine. +Whenever the `doctrine` command line tool is invoked, it can access alls Commands that were registered by developer. +There is no auto-detection mechanism at work. The `bin\doctrine.php` file already registers all the commands that +currently ship with Doctrine DBAL and ORM. If you want to use additional commands you have to register them yourself. All the commands of the Doctrine Console require either the `db` or the `em` helpers to be defined in order to work correctly. Doctrine Console requires the definition of a HelperSet that is the DI tool to be injected in the Console. In case of a project that is dealing exclusively with DBAL, the ConnectionHelper is required: From d4489d1bdccae1a3aa0cadaf222618658b9c08be Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 31 Aug 2010 23:23:05 +0200 Subject: [PATCH 103/430] Added large Note/Warning on SchemaTool usage and assumptions --- manual/en/tools.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/manual/en/tools.txt b/manual/en/tools.txt index a3a8eda7c..152788c7a 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -110,6 +110,17 @@ The following Commands are currently available: ++ Database Schema Generation +> **Note** +> +> SchemaTool can do harm to your database. It will drop or alter tables, indexes, sequences and such. Please use +> this tool with caution in development and not on a production server. It is meant for helping you develop your +> Database Schema, but NOT with migrating schema from A to B in production. A safe approach would be generating +> the SQL on development server and saving it into SQL Migration files that are executed manually on the production +> server. +> +> SchemaTool assumes your Doctrine Project uses the given database on its own. Update and Drop commands will +> mess with other tables if they are not related to the current project that is using Doctrine. Please be careful! + To generate your database schema from your Doctrine mapping files you can use the `SchemaTool` class or the `schema-tool` Console Command. From d8dd44aa161d4cabbb1f890f5a2470c0812ab02b Mon Sep 17 00:00:00 2001 From: Christian Heinrich Date: Tue, 17 Aug 2010 01:43:54 +0200 Subject: [PATCH 104/430] Added a naming convention for events --- manual/en/events.txt | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/manual/en/events.txt b/manual/en/events.txt index 782f87f83..47fbc1b20 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -2,8 +2,8 @@ Doctrine 2 features a lightweight event system that is part of the Common packag ++ The Event System -The event system is controlled by the `EventManager`. It is the central point -of Doctrine's event listener system. Listeners are registered on the manager +The event system is controlled by the `EventManager`. It is the central point +of Doctrine's event listener system. Listeners are registered on the manager and events are dispatched through the manager. [php] @@ -61,8 +61,6 @@ method which returns an array of events it should be subscribed to. [php] class TestEventSubscriber implements \Doctrine\Common\EventSubscriber { - const preFoo = 'preFoo'; - public $preFooInvoked = false; public function preFoo() @@ -72,7 +70,7 @@ method which returns an array of events it should be subscribed to. public function getSubscribedEvents() { - return array(self::preFoo); + return array(TestEvent::preFoo); } } @@ -91,6 +89,16 @@ Now you can test the `$eventSubscriber` instance to see if the `preFoo()` method echo 'pre foo invoked!'; } ++++ Naming convention + +Events being used with the Doctrine 2 EventManager are best named with camelcase and the value of the corresponding constant should be the name of the constant itself, even with spelling. This has several reasons: + +* It is easy to read. +* Simplicity. +* Each method within an EventSubscriber is named after the corresponding constant. If constant name and constant value differ, you MUST use the new value and thus, your code might be subject to codechanges when the value changes. This contradicts the intention of a constant. + +An example for a correct notation can be found in the example `EventTest` above. + ++ Lifecycle Events The EntityManager and UnitOfWork trigger a bunch of events during the life-time of their registered entities. @@ -128,12 +136,12 @@ These can be hooked into by two different types of event listeners: ++ Lifecycle Callbacks -A lifecycle event is a regular event with the additional feature of providing +A lifecycle event is a regular event with the additional feature of providing a mechanism to register direct callbacks inside the corresponding entity classes that are executed when the lifecycle event occurs. [php] - + /** @Entity @HasLifecycleCallbacks */ class User { @@ -430,7 +438,7 @@ to ++ Load ClassMetadata Event -When the mapping information for an entity is read, it is populated in to a +When the mapping information for an entity is read, it is populated in to a `ClassMetadataInfo` instance. You can hook in to this process and manipulate the instance. From 3d7eb3bac81684a33791679f2be2c9f48296da2d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 4 Sep 2010 09:18:57 +0200 Subject: [PATCH 105/430] Enhanced Native SQL documentation --- manual/en/native-sql.txt | 48 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/manual/en/native-sql.txt b/manual/en/native-sql.txt index 2e2b9b34b..f6dd12096 100644 --- a/manual/en/native-sql.txt +++ b/manual/en/native-sql.txt @@ -1,4 +1,8 @@ -A `NativeQuery` lets you execute native SQL, mapping the results according to your specifications. Such a specification that describes how an SQL result set is mapped to a Doctrine result is represented by a `ResultSetMapping`. +A `NativeQuery` lets you execute native SQL, mapping the results according to your specifications. +Such a specification that describes how an SQL result set is mapped to a Doctrine result is +represented by a `ResultSetMapping`. It describes how each column of the database result should +be mapped by Doctrine in terms of the object graph. This allows you to map arbitrary SQL code to objects, such as +highly vendor-optimized SQL or stored-procedures. ++ The NativeQuery class @@ -117,6 +121,23 @@ A meta result column (foreign key or discriminator column) always belongs to to The second parameter is the column alias/name of the column in the SQL result set and the third parameter is the column name used in the mapping. ++++ Discriminator Column + +When joining an inheritance tree you have to give Doctrine a hint which meta-column is the discriminator column +of this tree. + + [php] + /** + * Sets a discriminator column for an entity result or joined entity result. + * The discriminator column will be used to determine the concrete class name to + * instantiate. + * + * @param string $alias The alias of the entity result or joined entity result the discriminator + * column should be used for. + * @param string $discrColumn The name of the discriminator column in the SQL result set. + * @todo Rename: addDiscriminatorColumn + */ + public function setDiscriminatorColumn($alias, $discrColumn) +++ Examples @@ -171,6 +192,30 @@ based on this key. Consequently, associations that are *fetch-joined* do not require the foreign keys to be present in the SQL result set, only associations that are lazy. + [php] + // Equivalent DQL query: "select u from User u join u.address a WHERE u.name = ?1" + // User owns association to an Address and the Address is loaded in the query. + $rsm = new ResultSetMapping; + $rsm->addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address'); + $rsm->addFieldResult('a', 'address_id', 'id'); + $rsm->addFieldResult('a', 'street', 'street'); + $rsm->addFieldResult('a', 'city', 'city'); + + $sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' . + 'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?'; + $query = $this->_em->createNativeQuery($sql, $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +In this case the nested entity `Address` is registered with the `ResultSetMapping#addJoinedEntityResult` +method, which notifies Doctrine that this entity is not hydrated at the root level, but as a joined entity +somewhere inside the object graph. In this case we specify the alias 'u' as third parameter and `address` +as fourth parameter, which means the `Address` is hydrated into the `User::$address` property. + If a fetched entity is part of a mapped hierarchy that requires a discriminator column, this column must be present in the result set as a meta column so that Doctrine can create the appropriate concrete type. This is shown in the following example where we assume that there @@ -185,6 +230,7 @@ is used to map the hierarchy (both use a discriminator column). $rsm->addFieldResult('u', 'id', 'id'); $rsm->addFieldResult('u', 'name', 'name'); $rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column + $rsm->setDiscriminatorColumn('u', 'discr'); $query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm); $query->setParameter(1, 'romanb'); From e342b4536a8e3e6058cb40f51ceb3cadb8ab81b4 Mon Sep 17 00:00:00 2001 From: Christian Heinrich Date: Sat, 4 Sep 2010 13:36:02 +0200 Subject: [PATCH 106/430] Added a caution sign for case-sensitivity of mapping types. This might help to prevent beginners from being confused when an error is raised due to wrong spelling. (Like DateTime (wrong) <-> datetime (right)) --- manual/en/basic-mapping.txt | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 8acf712eb..8e333dc58 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -40,7 +40,7 @@ In order to mark a class for object-relational persistence it needs to be design { //... } - + By default, the entity will be persisted to a table with the same name as the class name. In order to change that, you can use the `@Table` annotation as follows: [php] @@ -52,9 +52,9 @@ By default, the entity will be persisted to a table with the same name as the cl { //... } - + Now instances of MyPersistentClass will be persisted into a table named `my_persistent_class`. - + ++ Doctrine Mapping Types A Doctrine Mapping Type defines the mapping between a PHP type and an SQL type. All Doctrine Mapping Types that ship with Doctrine are fully portable between different RDBMS. You can even write your own custom mapping types that might or might not be portable, which is explained later in this chapter. @@ -78,6 +78,10 @@ For example, the Doctrine Mapping Type `string` defines the mapping from a PHP s > Doctrine Mapping Types are NOT SQL types and NOT PHP types! They are mapping types > between 2 types. +> **CAUTION** +> Mapping types are *case-sensitive*. For example, using a DateTime column will NOT match the datetime type +> that ships with Doctrine 2! + ++ Property Mapping After a class has been marked as an entity it can specify mappings for its instance fields. Here we will only look at simple fields that hold scalar values like strings, numbers, etc. Associations to other objects are covered in the chapter "Association Mapping". @@ -85,7 +89,7 @@ After a class has been marked as an entity it can specify mappings for its insta To mark a property for relational persistence the `@Column` docblock annotation is used. This annotation usually requires at least 1 attribute to be set, the `type`. The `type` attribute specifies the Doctrine Mapping Type to use for the field. If the type is not specified, 'string' is used as the default mapping type since it is the most flexible. Example: - + [php] /** @Entity */ class MyPersistentClass @@ -110,7 +114,7 @@ The Column annotation has some more attributes. Here is a complete list: * `length`: (optional, default 255) The length of the column in the database. (Applies only if a string-valued column is used). * `unique`: (optional, default FALSE) Whether the column is a unique key. * `nullable`: (optional, default FALSE) Whether the database column is nullable. -* `precision`: (optional, default 0) The precision for a decimal (exact numeric) column. (Applies only if a decimal column is used.) +* `precision`: (optional, default 0) The precision for a decimal (exact numeric) column. (Applies only if a decimal column is used.) * `scale`: (optional, default 0) The scale for a decimal (exact numeric) column. (Applies only if a decimal column is used.) ++ Custom Mapping Types @@ -159,7 +163,7 @@ Restrictions to keep in mind: * If the value of the field is *NULL* the method `convertToDatabaseValue()` is not called. * The `UnitOfWork` never passes values to the database convert method that did not change in the request. - + When you have implemented the type you still need to let Doctrine know about it. This can be achieved through the `Doctrine\DBAL\Configuration#setCustomTypes(array $types)` method. @@ -173,14 +177,14 @@ Here is an example: // in bootstrapping code // ... - + use Doctrine\DBAL\Types\Type; - + // ... // Register my type Type::addType('mytype', 'My\Project\Types\MyType'); - + As can be seen above, when registering the custom types in the configuration you specify a unique name for the mapping type and map that to the corresponding fully qualified class name. Now you can use your new type in your mapping like this: @@ -310,4 +314,4 @@ Doctrine will then quote this column name in all SQL statements according to the > **CAUTION** > Identifier Quoting is a feature that is mainly intended to support legacy database -> schemas. The use of reserved words and identifier quoting is generally discouraged. \ No newline at end of file +> schemas. The use of reserved words and identifier quoting is generally discouraged. From 13a99c30ea5d8b8ee587640800b03275fa5b1f3b Mon Sep 17 00:00:00 2001 From: Michael Ridgway Date: Thu, 9 Sep 2010 10:24:10 -0400 Subject: [PATCH 107/430] Fixed example in Establishing Associations --- manual/en/working-with-associations.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/en/working-with-associations.txt b/manual/en/working-with-associations.txt index 9094380c1..06dd1f767 100644 --- a/manual/en/working-with-associations.txt +++ b/manual/en/working-with-associations.txt @@ -183,8 +183,8 @@ In the case of bi-directional associations you have to update the fields on both $user->getAuthoredComments()->add($newComment); $newComment->setAuthor($user); - $em->persist(); - $m->flush(); + $em->persist($newComment); + $em->flush(); Notice how always both sides of the bidirectional association are updated. The previous unidirectional associations were simpler to handle. From 453341c7540f0e67fac4be059de711f64d68908f Mon Sep 17 00:00:00 2001 From: Christian Heinrich Date: Sun, 12 Sep 2010 12:32:54 +0200 Subject: [PATCH 108/430] Updated strategy cookbook introduction - Removed annoying tabs - Removed some comments - Hopefully, it will display now correctly --- .../en/strategy-cookbook-introduction.txt | 292 +++++++++--------- 1 file changed, 145 insertions(+), 147 deletions(-) diff --git a/cookbook/en/strategy-cookbook-introduction.txt b/cookbook/en/strategy-cookbook-introduction.txt index 987018e3c..88c02a7da 100644 --- a/cookbook/en/strategy-cookbook-introduction.txt +++ b/cookbook/en/strategy-cookbook-introduction.txt @@ -12,7 +12,7 @@ Given a Content-Management-System, we probably want to add / edit some so-called So, in this scenario, when building your CMS, you will surely add lots of blocks and panels to your pages and you will find yourself highly uncomfortable because of the following: -* Every existing page needs to know about the panels it contains - therefore, you'll have an association to your panels. But if you've got several types of panels - what do you do? Add an association to +* Every existing page needs to know about the panels it contains - therefore, you'll have an association to your panels. But if you've got several types of panels - what do you do? Add an association to every paneltype? This wouldn't be flexible. You might be tempted to add an AbstractPanelEntity and an AbstractBlockEntity that use class inheritance. Your page could then only confer to the AbstractPanelType and Doctrine 2 would do the rest for you, i.e. load the right entites. But - you'll for sure have lots of panels and blocks, and even worse, you'd have to edit the discriminator map *manually* every time you or another developer implements a new block / entity. This would tear down any effort of modular programming. Therefore, we need something thats far more flexible. @@ -26,140 +26,140 @@ First of all, we need to make sure that we have an interface that contains every Such an interface could look like this: [php] - /** - * This interface defines the basic actions that a block / panel needs to support. - * - * Every blockstrategy is *only* responsible for rendering a block and declaring some basic - * support, but *not* for updating its configuration etc. For this purpose, use controllers - * and models. - * - * @author Christian Heinrich - * @version $Id: BlockStrategyInterface.php 657 2010-03-16 00:08:50Z cheinrich $ - */ - interface BlockStrategyInterface { - /** - * This could configure your entity - */ - public function setConfig(Config\EntityConfig $config); + /** + * This interface defines the basic actions that a block / panel needs to support. + * + * Every blockstrategy is *only* responsible for rendering a block and declaring some basic + * support, but *not* for updating its configuration etc. For this purpose, use controllers + * and models. + */ + interface BlockStrategyInterface { + /** + * This could configure your entity + */ + public function setConfig(Config\EntityConfig $config); - /** - * Returns the config this strategy is configured with. - * @return Core\Model\Config\EntityConfig - */ - public function getConfig(); + /** + * Returns the config this strategy is configured with. + * @return Core\Model\Config\EntityConfig + */ + public function getConfig(); - /** - * Set the view object. - * - * @param \Zend_View_Interface $view - * @return \Zend_View_Helper_Interface - */ - public function setView(\Zend_View_Interface $view); + /** + * Set the view object. + * + * @param \Zend_View_Interface $view + * @return \Zend_View_Helper_Interface + */ + public function setView(\Zend_View_Interface $view); - /** - * @return \Zend_View_Interface - */ - public function getView(); + /** + * @return \Zend_View_Interface + */ + public function getView(); - /** - * Renders this strategy. This method will be called when the user - * displays the site. - * - * @return string - */ - public function renderFrontend(); + /** + * Renders this strategy. This method will be called when the user + * displays the site. + * + * @return string + */ + public function renderFrontend(); - /** - * Renders the backend of this block. This method will be called when - * a user tries to reconfigure this block instance. - * - * Most of the time, this method will return / output a simple form which in turn - * calls some controllers. - * - * @return string - */ - public function renderBackend(); + /** + * Renders the backend of this block. This method will be called when + * a user tries to reconfigure this block instance. + * + * Most of the time, this method will return / output a simple form which in turn + * calls some controllers. + * + * @return string + */ + public function renderBackend(); - /** - * Returns all possible types of panels this block can be stacked onto - * - * @return array - */ - public function getRequiredPanelTypes(); + /** + * Returns all possible types of panels this block can be stacked onto + * + * @return array + */ + public function getRequiredPanelTypes(); - /** - * Determines whether a Block is able to use a given type or not - * @param string $typeName The typename - * @return boolean - */ - public function canUsePanelType($typeName); + /** + * Determines whether a Block is able to use a given type or not + * @param string $typeName The typename + * @return boolean + */ + public function canUsePanelType($typeName); - public function setBlockEntity(AbstractBlock $block); + public function setBlockEntity(AbstractBlock $block); - public function getBlockEntity(); - } + public function getBlockEntity(); + } As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this: [php] - /** - * This is the base class for both Panels and Blocks. - * It shouldn't be extended by your own blocks - simply write a strategy! - */ - abstract class AbstractBlock { - /** - * The id of the block item instance - * @var integer - */ - private $id; + /** + * This is the base class for both Panels and Blocks. + * It shouldn't be extended by your own blocks - simply write a strategy! + */ + abstract class AbstractBlock { + /** + * The id of the block item instance + * This is a doctrine field, so you need to setup generation for it + * @var integer + */ + private $id; - // Add code for relation to the parent panel, configuration objects, .... + // Add code for relation to the parent panel, configuration objects, .... - /** - * This var contains the classname of the strategy - * that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2) - * - * @var string - */ - protected $strategyClassName; + /** + * This var contains the classname of the strategy + * that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2) + * + * This is a doctrine field, so make sure that you use an @column annotation or setup your + * yaml or xml files correctly + * @var string + */ + protected $strategyClassName; - /** - * This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2 - * @var BlockStrategyInterface - */ - protected $strategyInstance; + /** + * This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2 + * @var BlockStrategyInterface + */ + protected $strategyInstance; - /** - * Returns the strategy that is used for this blockitem. - * - * The strategy itself defines how this block can be rendered etc. - * - * @return string - */ - public function getStrategyClassName() { - return $this->strategyClassName; - } + /** + * Returns the strategy that is used for this blockitem. + * + * The strategy itself defines how this block can be rendered etc. + * + * @return string + */ + public function getStrategyClassName() { + return $this->strategyClassName; + } - /** - * Returns the instantiated strategy - * - * @return BlockStrategyInterface - */ - public function getStrategyInstance() { - return $this->strategyInstance; - } + /** + * Returns the instantiated strategy + * + * @return BlockStrategyInterface + */ + public function getStrategyInstance() { + return $this->strategyInstance; + } - /** - * Sets the strategy this block / panel should work as. Make sure that you've used - * this method before persisting the block! - * - * @param BlockStrategyInterface $strategy - */ - public function setStrategy(BlockStrategyInterface $strategy) { - $this->strategyInstance = $strategy; - $this->strategyClassName = get_class($strategy); - $strategy->setBlockEntity($this); - } + /** + * Sets the strategy this block / panel should work as. Make sure that you've used + * this method before persisting the block! + * + * @param BlockStrategyInterface $strategy + */ + public function setStrategy(BlockStrategyInterface $strategy) { + $this->strategyInstance = $strategy; + $this->strategyClassName = get_class($strategy); + $strategy->setBlockEntity($this); + } Now, the important point is that $strategyClassName is a Doctrine 2 field, i.e. Doctrine will persist this value. This is only the class name of your strategy and not an instance! @@ -167,42 +167,40 @@ Finishing your strategy pattern, we hook into the Doctrine postLoad event and ch This might look like this: - [php] - use \Doctrine\ORM, - \Doctrine\Common; + [php] + use \Doctrine\ORM, + \Doctrine\Common; - /** - * The BlockStrategyEventListener will initialize a strategy after the - * block itself was loaded. - * - * @author Christian Heinrich - */ - class BlockStrategyEventListener implements Common\EventSubscriber { + /** + * The BlockStrategyEventListener will initialize a strategy after the + * block itself was loaded. + */ + class BlockStrategyEventListener implements Common\EventSubscriber { - protected $view; + protected $view; - public function __construct(\Zend_View_Interface $view) { - $this->view = $view; - } + public function __construct(\Zend_View_Interface $view) { + $this->view = $view; + } - public function getSubscribedEvents() { - return array(ORM\Events::postLoad); - } + public function getSubscribedEvents() { + return array(ORM\Events::postLoad); + } - public function postLoad(ORM\Event\LifecycleEventArgs $args) { - $blockItem = $args->getEntity(); + public function postLoad(ORM\Event\LifecycleEventArgs $args) { + $blockItem = $args->getEntity(); - // Both blocks and panels are instances of Block\AbstractBlock - if ($blockItem instanceof Block\AbstractBlock) { - $strategy = $blockItem->getStrategyClassName(); - $strategyInstance = new $strategy(); - if (null !== $blockItem->getConfig()) { - $strategyInstance->setConfig($blockItem->getConfig()); - } - $strategyInstance->setView($this->view); - $blockItem->setStrategy($strategyInstance); - } - } - } + // Both blocks and panels are instances of Block\AbstractBlock + if ($blockItem instanceof Block\AbstractBlock) { + $strategy = $blockItem->getStrategyClassName(); + $strategyInstance = new $strategy(); + if (null !== $blockItem->getConfig()) { + $strategyInstance->setConfig($blockItem->getConfig()); + } + $strategyInstance->setView($this->view); + $blockItem->setStrategy($strategyInstance); + } + } + } In this example, even some variables are set - like a view object or a specific configuration object. From bebc8cf871c2f82c0d5ba149ddd029230a1abda4 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 21 Sep 2010 21:55:35 +0200 Subject: [PATCH 109/430] Add section about deferred schema validation into association mapping chapter, add note about private/protected property in entities for lazy-laoding purposes, added subchapters on identity map internals and object graph traversal to the Working with Objects section --- manual/en/architecture.txt | 1 + manual/en/association-mapping.txt | 24 ++++++ manual/en/working-with-objects.txt | 131 +++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+) diff --git a/manual/en/architecture.txt b/manual/en/architecture.txt index 7d2452fde..81c33ea49 100644 --- a/manual/en/architecture.txt +++ b/manual/en/architecture.txt @@ -7,6 +7,7 @@ An entity is a lightweight, persistent domain object. An entity can be any regul PHP class observing the following restrictions: * An entity class must not be final or contain final methods. +* All persistent properties/field of any entity class should always be private or protected, otherwise lazy-loading might not work as expected. * An entity class must not implement `__clone` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). * An entity class must not implement `__wakeup` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). Also consider implementing [Serializable](http://de3.php.net/manual/en/class.serializable.php]) instead. diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index d4200f751..dc30ce31b 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -161,6 +161,30 @@ Now the following code will be working even if the Entity hasn't been associated $user = new User(); $user->getGroups()->add($group); +++ Association Runtime vs Development Validation + +For performance reasons Doctrine 2 has to skip some of the necessary validation of association mappings. +You have to execute this validation in your development workflow to verify the associations are correctly +defined. + +You can either use the Doctrine Command Line Tool: + + doctrine orm:validate-schema + +Or you can trigger the validation manually: + + use Doctrine\ORM\Tools\SchemaValidator; + + $validator = new SchemaValidator($entityManager); + $errors = $validator->validateMapping(); + + if (count($errors) > 0) { + // Lots of errors! + echo implode("\n\n", $errors); + } + +If the mapping is invalid the errors array contains a positive number of elements with error messages. + ++ One-To-One, Unidirectional A unidirectional one-to-one association is very common. Here is an example of a `Product` that has one `Shipping` object associated to it. The `Shipping` side does not reference back to the `Product` so it is unidirectional. diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 9b10a3377..5fa6455c5 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -18,6 +18,137 @@ are lost. > > Not calling `EntityManager#flush()` will lead to all changes during that request being lost. +++ Entities and the Identity Map + +Entities are objects with identity. Their identity has a conceptual meaning inside your domain. +In a CMS application each article has a unique id. You can uniquely identify each article +by that id. + +Take the following example, where you find an article with the headline "Hello World" +with the ID 1234: + + [php] + $article = $entityManager->find('CMS\Article', 1234); + $article->setHeadline('Hello World dude!'); + + $article2 = $entityManager->find('CMS\Article', 1234); + echo $article2->getHeadline(); + +In this case the Article is accessed from the entity manager twice, but modified in between. +Doctrine 2 realizes this and will only ever give you access to one instance of the Article +with ID 1234, no matter how often do you retrieve it from the EntityManager and even no +matter what kind of Query method you are using (find, Repository Finder or DQL). +This is called "Identity Map" pattern, which means Doctrine keeps a map of each entity +and ids that have been retrieved per PHP request and keeps returning you the same instances. + +In the previous example the echo prints "Hello World dude!" to the screen. You can +even verify that `$article` and `$article2` are indeed pointing to the same instance +by running the following code: + + [php] + if ($article === $article2) { + echo "Yes we are the same!"; + } + +Sometimes you want to clear the identity map of an EntityManager to start over. We use +this regularly in our unit-tests to enforce loading objects from the database again +instead of serving them from the identity map. You can call `EntityManager#clear()` +to achieve this result. + +++ Entity Object Graph Traversal + +Although Doctrine allows for a complete separation of your domain model (Entity classes) +there will never be a situation where objects are "missing" when traversing associations. +You can walk all the associations inside your entity models as deep as you want. + +Take the following example of a single `Article` entity fetched from newly opened EntityManager. + + [php] + /** @Entity */ + class Article + { + /** @Id @Column(type="integer") @GeneratedValue */ + private $id; + + /** @Column(type="string") */ + private $headline; + + /** @ManyToOne(targetEntity="User") */ + private $author; + + /** @OneToMany(targetEntity="Comment", mappedBy="article") */ + private $comments; + + public function __construct { + $this->comments = new ArrayCollection(); + } + + public function getAuthor() { return $this->author; } + public function getComments() { return $this->comments; } + } + + $article = $em->find('Article', 1); + +This code only retrieves the `User` instance with id 1 executing a single SELECT statement +against the user table in the database. You can still access the associated properties author +and comments and the associated objects they contain. + +This works by utilizing the lazy loading pattern. Instead of passing you back a real +Author instance and a collection of comments Doctrine will create proxy instances for you. +Only if you access these proxies for the first time they will go through the EntityManager +and load their state from the database. + +This lazy-loading process happens behind the scenes, hidden from your code. See the following code: + + [php] + $article = $em->find('Article', 1); + + // accessing a method of the user instance triggers the lazy-load + echo "Author: " . $article->getAuthor()->getName() . "\n"; + + // Lazy Loading Proxies pass instanceof tests: + if ($article->getAuthor() instanceof User) { + // a User Proxy is a generated "UserProxy" class + } + + // accessing the comments as an iterator triggers the lazy-load + // retrieving ALL the comments of this article from the database + // using a single SELECT statement + foreach ($article->getComments() AS $comment) { + echo $comment->getText() . "\n\n"; + } + + // Article::$comments passes instanceof tests for the Collection interface + // But it will NOT pass for the ArrayCollection interface + if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) { + echo "This will always be true!"; + } + +A slice of the generated proxy classes code looks like the following piece of code. A real proxy +class override ALL public methods along the lines of the `getName()` method shown below: + + [php] + class UserProxy extends User implements Proxy + { + private function _load() + { + // lazy loading code + } + + public function getName() + { + $this->_load(); + return parent::getName(); + } + // .. other public methods of User + } + +> **Warning** +> +> Traversing the object graph for parts that are lazy-loaded will easily trigger lots +> of SQL queries and will perform badly if used to heavily. Make sure to use DQL +> to fetch-join all the parts of the object-graph that you need as efficiently as possible. + ++ Persisting entities An entity can be made persistent by passing it to the `EntityManager#persist($entity)` From 7a8b69edbb48741ee025f6ff55f621d3eb03d101 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 23 Sep 2010 22:40:12 +0200 Subject: [PATCH 110/430] Extend restriction and caution notes on identifier quoting --- manual/en/basic-mapping.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 8e333dc58..7a1359ccc 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -315,3 +315,6 @@ Doctrine will then quote this column name in all SQL statements according to the > **CAUTION** > Identifier Quoting is a feature that is mainly intended to support legacy database > schemas. The use of reserved words and identifier quoting is generally discouraged. +> Identifier quoting should not be used to enable the use non-standard-characters such +> as a dash in a hypothetical column `test-name`. Also Schema-Tool will likely have +> troubles when quoting is used for case-sensitivity reasons (in Oracle for example). \ No newline at end of file From e8eed2ebaee4af1a2ab0424ff0ae53d842f45b2f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 23 Sep 2010 22:48:39 +0200 Subject: [PATCH 111/430] Add legacy identifier quoting to the list of Known Issues --- manual/en/limitations-and-known-issues.txt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt index 87ba8081d..23540c10e 100644 --- a/manual/en/limitations-and-known-issues.txt +++ b/manual/en/limitations-and-known-issues.txt @@ -186,4 +186,18 @@ are already two extensions out there that offer support for Nested Set with Doct 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!* \ No newline at end of file +or pain of any sort. + ++++ Identifier Quoting and Legacy Databases + +For compatibility reasons between all the supported vendors and edge case problems +Doctrine 2 does *NOT* do automatic identifier quoting. This can lead to problems when trying to get +legacy-databases to work with Doctrine 2. + +* You can quote column-names as described in the [Basic-Mapping](basic-mapping) section. +* You cannot quote join column names. +* You cannot use non [a-zA-Z0-9_]+ characters, they will break several SQL statements. + +Having problems with these kind of column names? Many databases support all CRUD operations on views that +semantically map to certain tables. You can create views for all your problematic tables and column names +to avoid the legacy quoting nightmare. \ No newline at end of file From d2efb5bbc8a4c958c95f4a825375a5b6b0448ecb Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 27 Sep 2010 22:59:50 +0200 Subject: [PATCH 112/430] Add NOT INSTANCE OF use-case to DQL manual --- manual/en/dql-doctrine-query-language.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index e0808e977..86533ac7c 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -277,6 +277,7 @@ Get all instances of a specific type, for use with inheritance hierachies: [php] $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee'); $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1'); + $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1'); ++++ Partial Object Syntax From 4f18aaaf6e7dfd30995e0aefb615cc04d074371e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 30 Sep 2010 22:05:39 +0200 Subject: [PATCH 113/430] DDC-797 - Fix dql example bug --- manual/en/dql-doctrine-query-language.txt | 2 +- manual/en/events.txt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index 86533ac7c..edaf0e02f 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -251,7 +251,7 @@ CONCAT() DQL Function: EXISTS in WHERE clause with correlated Subquery [php] - $query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.phonenumber = u.id)'); + $query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.user = u.id)'); $ids = $query->getResult(); Get all users who are members of $group. diff --git a/manual/en/events.txt b/manual/en/events.txt index 47fbc1b20..3df59c619 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -436,6 +436,10 @@ The three post events are called inside `EntityManager#flush()`. Changes in here are not relevant to the persistence in the database, but you can use this events to ++++ postLoad + +This event is called after an entity is constructed by the EntityManager. + ++ Load ClassMetadata Event When the mapping information for an entity is read, it is populated in to a From e9617f1e502266c8e94cafbec351f1537cc933f9 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 30 Sep 2010 22:09:49 +0200 Subject: [PATCH 114/430] DDC-798 - Clarify parameters for MEMBER OF expression --- manual/en/dql-doctrine-query-language.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index edaf0e02f..e92db7aef 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -257,7 +257,8 @@ EXISTS in WHERE clause with correlated Subquery Get all users who are members of $group. [php] - $query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE :param MEMBER OF u.groups'); + $query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE :groupId MEMBER OF u.groups'); + $query->setParameter(':groupId', $group); $ids = $query->getResult(); Get all users that have more than 1 phonenumber From efe2a181891ea5f3ab630f6667784e8c5f0422e8 Mon Sep 17 00:00:00 2001 From: Ralfas Date: Mon, 4 Oct 2010 11:29:40 -0700 Subject: [PATCH 115/430] From 6b01986c48fe032e58e386b888a2a139eea15c51 Mon Sep 17 00:00:00 2001 From: Ralfas Date: Tue, 5 Oct 2010 12:58:43 +0100 Subject: [PATCH 116/430] fixed typos --- cookbook/en/getting-started-xml-edition.txt | 60 ++++++++++----------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/cookbook/en/getting-started-xml-edition.txt b/cookbook/en/getting-started-xml-edition.txt index f78834064..74e637b0c 100644 --- a/cookbook/en/getting-started-xml-edition.txt +++ b/cookbook/en/getting-started-xml-edition.txt @@ -1,13 +1,13 @@ Doctrine 2 is a project that aims to handle the persistence of the domain model in a non-interfering way. The Data Mapper pattern is at the heart of this project, aiming for a complete separation of the domain/business logic from the persistence in a relational database management system. The benefit of Doctrine for the programmer is the -possibility can focus soley on the business and worry about persistence only as a secondary task. This doesn't mean +possibility can focus solely on the business and worry about persistence only as a secondary task. This doesn't mean persistence is not important to Doctrine 2, however it is our belief that there are considerable benefits for object-oriented -programming, if persistence and entities are kept perfectly seperated. +programming, if persistence and entities are kept perfectly separated. ## What are Entities? -Entities are leightweight PHP Objects that don't need to extend any abstract base class or interface. +Entities are lightweight PHP Objects that don't need to extend any abstract base class or interface. An entity class must not be final or contain final methods. Additionally it must not implement __clone nor __wakeup or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). @@ -20,7 +20,7 @@ For this Getting Started Guide for Doctrine we will implement the Bug Tracker do documentation. Reading their documentation we can extract the requirements to be: * A Bugs has a description, creation date, status, reporter and engineer -* A bug can occour on different products (platforms) +* A bug can occur on different products (platforms) * Products have a name. * Bug Reporter and Engineers are both Users of the System. * A user can create new bugs. @@ -69,7 +69,7 @@ A first simplified design for this domain model might look like the following se > public properties can make up for pretty nasty bugs. Because we will focus on the mapping aspect, no effort is being made to encapsulate the business logic in this example. -All peristable properties are public in visibility. We will soon see that this is not the best solution in combination +All persistable properties are public in visibility. We will soon see that this is not the best solution in combination with Doctrine 2, one restriction that actually forces you to encapsulate your properties. For persistence Doctrine 2 actually uses Reflection to access the values in all your entities properties. @@ -121,24 +121,24 @@ Now that we know this, we have to clear up our domain model to cope with the ass Whenever an entity is recreated from the database, an Collection implementation of the type Doctrine\ORM\PersistantCollection is injected into your entity instead of an array. Compared to the ArrayCollection this implementation helps the Doctrine ORM understand the changes that -have happend to the collection which are noteworthy for persistence. +have happened to the collection which are noteworthy for persistence. > **Warning** > Lazy load proxies always contain an instance of Doctrine's EntityManager and all its dependencies. Therefore a var_dump() > will possibly dump a very large recursive structure which is impossible to render and read. You have to use > `Doctrine\Common\Util\Debug::dump()` to restrict the dumping to a human readable level. Additionally you should be aware > that dumping the EntityManager to a Browser may take several minutes, and the Debug::dump() method just ignores any -> occurences of it in Proxy instances. +> occurrences of it in Proxy instances. Because we only work with collections for the references we must be careful to implement a bidirectional reference in the domain model. The concept of owning or inverse side of a relation is central to this notion and should always be kept in mind. The following assumptions are made about relations and have to be followed to be able to work with Doctrine 2. These assumptions are not unique to Doctrine 2 but are best practices in handling database relations and Object-Relational Mapping. -* Changes to Collections are saved or updated, when the entity on the *ownin*g side of the collection is saved or updated. +* Changes to Collections are saved or updated, when the entity on the *owning* side of the collection is saved or updated. * Saving an Entity at the inverse side of a relation never triggers a persist operation to changes to the collection. * In a one-to-one relation the entity holding the foreign key of the related entity on its own database table is *always* the owning side of the relation. -* In a many-to-many relation, both sides can be the owning side of the relation. However in a bi-directional many-tomany relation only one is allowed to be. +* In a many-to-many relation, both sides can be the owning side of the relation. However in a bi-directional many-to-many relation only one is allowed to be. * In a many-to-one relation the Many-side is the owning side by default, because it holds the foreign key. * The OneToMany side of a relation is inverse by default, since the foreign key is saved on the Many side. A OneToMany relation can only be the owning side, if its implemented using a ManyToMany relation with join table and restricting the one side to allow only UNIQUE values per database constraint. @@ -236,13 +236,13 @@ object-oriented best-practices. ## Metadata Mappings for our Entities -Up to now we have only implemented our Entites as Data-Structures without actually telling Doctrine how to persist +Up to now we have only implemented our Entities as Data-Structures without actually telling Doctrine how to persist them in the database. If perfect in-memory databases would exist, we could now finish the application using these entities -by implementing code to fullfil all the requirements. However the world isn't perfect and we have to persist our +by implementing code to fulfil all the requirements. However the world isn't perfect and we have to persist our entities in some storage to make sure we don't loose their state. Doctrine currently serves Relational Database Management Systems. In the future we are thinking to support NoSQL vendors like CouchDb or MongoDb, however this is still far in the future. -The next step for persistance with Doctrine is to describe the structure of our domain model entities to Doctrine +The next step for persistence with Doctrine is to describe the structure of our domain model entities to Doctrine using a metadata language. The metadata language describes how entities, their properties and references should be persisted and what constraints should be applied to them. @@ -272,7 +272,7 @@ The first discussed definition will be for the Product, since it is the most sim -The toplevel `entity` definition tag specifies information about the class and table-name. The +The top-level `entity` definition tag specifies information about the class and table-name. The primitive type `Product::$name` is defined as `field` attributes. The Id property is defined with the `id` tag. The id has a `generator` tag nested inside which defines that the primary key generation mechanism automatically uses the database platforms native id generation strategy, for example AUTO INCREMENT @@ -325,14 +325,14 @@ which translates the YYYY-mm-dd HH:mm:ss Database format into a PHP DateTime ins After the field definitions the two qualified references to the user entity are defined. They are created by the `many-to-one` tag. The class name of the related entity has to be specified with the `target-entity` attribute, which is enough information for the database mapper to access the foreign-table. The -`join-column` tags are used to specifiy how the foreign and referend columns are named, an information +`join-column` tags are used to specify how the foreign and referenced columns are named, an information Doctrine needs to construct joins between those two entities correctly. Since `reporter` and `engineer` -are on the owning side of a bi-direcitonal relation we also have to specify the `inversed-by` attribute. +are on the owning side of a bi-directional relation we also have to specify the `inversed-by` attribute. They have to point to the field names on the inverse side of the relationship. The last missing property is the `Bug::$products` collection. It holds all products where the specific -bug is occouring in. Again you have to define the `target-entity` and `field` attributes on the `many-to-many` -tag. Furthermore you have to specifiy the details of the many-to-many join-table and its foreign key columns. +bug is occurring in. Again you have to define the `target-entity` and `field` attributes on the `many-to-many` +tag. Furthermore you have to specify the details of the many-to-many join-table and its foreign key columns. The definition is rather complex, however relying on the XML auto-completion I got it working easily, although I forget the schema details all the time. @@ -390,8 +390,8 @@ steps and then discuss them step by step: $config->setMetadataDriverImpl($driverImpl); // Caching Configuration (5) - if (APPLICATION_ENV == "develoment") { - $cache = new \Doctrine\Common\Cache\ArayCache(); + if (APPLICATION_ENV == "development") { + $cache = new \Doctrine\Common\Cache\ArrayCache(); } else { $cache = new \Doctrine\Common\Cache\ApcCache(); } @@ -409,7 +409,7 @@ steps and then discuss them step by step: $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config, $evm); The first block sets up the autoloading capabilities of Doctrine. I am registering the Doctrine -namespace to the given path. To add your own namespace you can instantiate another `CloassLoader` +namespace to the given path. To add your own namespace you can instantiate another `ClassLoader` with different namespace and path arguments. There is no requirement to use the Doctrine `ClassLoader` for your autoloading needs, you can use whatever suits you best. @@ -417,10 +417,10 @@ The second block contains of the instantiation of the ORM Configuration object. configuration shown in the next blocks there are several others with are all explained in the [Configuration section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). -The Proxy Configuration is a required block for your application, you have to specifiy where +The Proxy Configuration is a required block for your application, you have to specify where Doctrine writes the PHP code for Proxy Generation. Proxies are children of your entities generated by Doctrine to allow for type-safe lazy loading. We will see in a later chapter how exactly this works. -Besides the path to the proxies we also specifiy which namespace they will reside under aswell as +Besides the path to the proxies we also specify which namespace they will reside under as well as a flag `autoGenerateProxyClasses` indicating that proxies should be re-generated on each request, which is recommended for development. In production this should be prevented at all costs, the proxy class generation can be quite costly. @@ -449,7 +449,7 @@ we want to generate the relational database schema from it. Doctrine has a Command-Line-Interface that allows you to access the SchemaTool, a component that generates the required tables to work with the metadata. -For the commandline tool to work a cli-config.php file has to be present in the project root directry, +For the command-line tool to work a cli-config.php file has to be present in the project root directory, where you will execute the doctrine command. Its a fairly simple file: [php] @@ -458,7 +458,7 @@ where you will execute the doctrine command. Its a fairly simple file: )); $cli->setHelperSet($helperSet); -You can then change into your project directory and call the Doctrine commandline tool: +You can then change into your project directory and call the Doctrine command-line tool: [console] doctrine@my-desktop> cd myproject/ @@ -468,7 +468,7 @@ You can then change into your project directory and call the Doctrine commandlin > > The `doctrine` command will only be present if you installed Doctrine from PEAR. > Otherwise you will have to dig into the `bin/doctrine.php` code of your Doctrine 2 -> directory to setup your doctrine commandline client. +> directory to setup your doctrine command-line client. > > See the [Tools section of the manual](http://www.doctrine-project.org/projects/orm/2.0/docs/reference/tools/en) > on how to setup the Doctrine console correctly. @@ -478,7 +478,7 @@ metadata. You can then either re-create the database: [console] doctrine@my-desktop> doctrine orm:schema-tool:drop - doctrine@my-dekstop> doctrine orm:schema-tool:create + doctrine@my-desktop> doctrine orm:schema-tool:create Or use the update functionality: @@ -609,7 +609,7 @@ in one single SQL statement. The console output of this script is then: > and powerful queries. > > An important reason why DQL is favourable to the Query API of most ORMs is its similarity to SQL. The DQL language -> allows query constructs that most ORMs don't, GROUP BY even with HAVING, Subselects, Fetch-Joins of nested +> allows query constructs that most ORMs don't, GROUP BY even with HAVING, Sub-selects, Fetch-Joins of nested > classes, mixed results with entities and scalar data such as COUNT() results and much more. Using > DQL you should seldom come to the point where you want to throw your ORM into the dumpster, because it > doesn't support some the more powerful SQL concepts. @@ -625,9 +625,9 @@ in one single SQL statement. The console output of this script is then: ### Array Hydration of the Bug List In the previous use-case we retrieved the result as their respective object instances. -We are not limitied to retrieving objects only from Doctrine however. For a simple list view +We are not limited to retrieving objects only from Doctrine however. For a simple list view like the previous one we only need read access to our entities and can switch the hydration -from objects to simple PHP arrays instead. This can obviously yiel considerable performance benefits for read-only requests. +from objects to simple PHP arrays instead. This can obviously yield considerable performance benefits for read-only requests. Implementing the same list view as before using array hydration we can rewrite our code: @@ -667,7 +667,7 @@ However we will soon see another problem with our entities using this approach. echo "Bug: ".$bug->description."\n"; echo "Engineer: ".$bug->getEngineer()->name."\n"; -It will be null! What is happening? It worked in the previous example, so it can't be a problem with the persistance +It will be null! What is happening? It worked in the previous example, so it can't be a problem with the persistence code of Doctrine. What is it then? You walked in the public property trap. Since we only retrieved the bug by primary key both the engineer and reporter are not immediately loaded From 8ac6d29c741039c644547f2a7145f4df88ae423c Mon Sep 17 00:00:00 2001 From: Ralfas Date: Tue, 5 Oct 2010 13:09:10 +0100 Subject: [PATCH 117/430] fixed typos --- cookbook/en/aggregate-fields.txt | 6 +++--- cookbook/en/dql-custom-walkers.txt | 6 +++--- cookbook/en/dql-user-defined-functions.txt | 12 ++++++------ ...implementing-the-notify-changetracking-policy.txt | 2 +- cookbook/en/strategy-cookbook-introduction.txt | 6 +++--- cookbook/en/validation-of-entities.txt | 8 ++++---- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cookbook/en/aggregate-fields.txt b/cookbook/en/aggregate-fields.txt index 0dc5d18d7..18e445034 100644 --- a/cookbook/en/aggregate-fields.txt +++ b/cookbook/en/aggregate-fields.txt @@ -248,11 +248,11 @@ code in `Account::getBalance()` and `Account:addEntry()`: } This is a very simple change, but all the tests still pass. Our account entities return -the correct balance. Now calling the `Account::getBalance()` method will not occour the +the correct balance. Now calling the `Account::getBalance()` method will not occur the overhead of loading all entries anymore. Adding a new Entry to the `Account::$entities` will also not initialize the collection internally. -Adding a new entry is therefore very performant and explictly hooked into the domain model. +Adding a new entry is therefore very performant and explicitly hooked into the domain model. It will only update the account with the current balance and insert the new entry into the database. ## Tackling Race Conditions with Aggregate Fields @@ -293,7 +293,7 @@ Optimistic locking is as easy as adding a version column: The previous example would then throw an exception in the face of whatever request saves the entity last (and would create the inconsistent state). -Pessimmistic locking requires an additional flag set on the `EntityManager::find()` +Pessimistic locking requires an additional flag set on the `EntityManager::find()` call, enabling write locking directly in the database using a FOR UPDATE. [php] diff --git a/cookbook/en/dql-custom-walkers.txt b/cookbook/en/dql-custom-walkers.txt index 48d4840a4..2f314cd78 100644 --- a/cookbook/en/dql-custom-walkers.txt +++ b/cookbook/en/dql-custom-walkers.txt @@ -1,13 +1,13 @@ # Extending DQL in Doctrine 2: Custom AST Walkers -The Doctrine Query Language (DQL) is a propriotary sql-dialect that substitutes +The Doctrine Query Language (DQL) is a proprietary sql-dialect that substitutes tables and columns for Entity names and their fields. Using DQL you write a query against the database using your entities. With the help of the metadata you can write very concise, compact and powerful queries that are then translated into SQL by the Doctrine ORM. In Doctrine 1 the DQL language was not implemented using a real parser. This -made modifications of the DQL by the user impossible. Doctrine 2 in constrast +made modifications of the DQL by the user impossible. Doctrine 2 in contrast has a real parser for the DQL language, which transforms the DQL statement into an [Abstract Syntax Tree](http://en.wikipedia.org/wiki/Abstract_syntax_tree) and generates the appropriate SQL statement for it. Since this process is @@ -130,7 +130,7 @@ execution plans. You can write your own output walker to introduce certain keywords using the Query Hint API. A query hint can be set via `Query::setHint($name, $value)` as shown in the previous example with the `HINT_CUSTOM_TREE_WALKERS` query hint. -We will implement a custom Output Walker that allows to specifiy the SQL_NO_CACHE +We will implement a custom Output Walker that allows to specify the SQL_NO_CACHE query hint. [php] diff --git a/cookbook/en/dql-user-defined-functions.txt b/cookbook/en/dql-user-defined-functions.txt index d738617c4..811a5cf36 100644 --- a/cookbook/en/dql-user-defined-functions.txt +++ b/cookbook/en/dql-user-defined-functions.txt @@ -48,7 +48,7 @@ takes two dates as argument and calculates the difference in days with `date1-da The DQL parser is a top-down recursive descent parser to generate the Abstract-Syntax Tree (AST) and uses a TreeWalker approach to generate the appropriate -SQL from the AST. This makes reading the Parser/TreeWalker code managable +SQL from the AST. This makes reading the Parser/TreeWalker code manageable in a finite amount of time. The `FunctionNode` class I referred to earlier requires you to implement @@ -98,13 +98,13 @@ to the generation of a DateDiff FunctionNode somewhere in the AST of the dql statement. The `ArithmeticPrimary` method call is the most common denominator of valid -EBNF tokens taken from the [DQL EBNF grammer](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf) +EBNF tokens taken from the [DQL EBNF grammar](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf) that matches our requirements for valid input into the DateDiff Dql function. Picking the right tokens for your methods is a tricky business, but the EBNF -grammer is pretty helpful finding it, as is looking at the Parser source code. +grammar is pretty helpful finding it, as is looking at the Parser source code. Now in the TreeWalker process we have to pick up this node and generate SQL -from it, which apprently is quite easy looking at the code in (7). Since +from it, which apparently is quite easy looking at the code in (7). Since we don't know which type of AST Node the first and second Date expression are we are just dispatching them back to the SQL Walker to generate SQL from and then wrap our DATEDIFF function call around this output. @@ -125,7 +125,7 @@ We can do fancy stuff like: Often useful it the ability to do some simple date calculations in your DQL query using [MySql's DATE_ADD function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add). -I'll skip the bla and show the code for this function: +I'll skip the blah and show the code for this function: [php] /** @@ -176,7 +176,7 @@ the value of the `T_IDENTIFIER` token for the Date Interval unit, for example th The above method now only supports the specification using `INTERVAL`, to also allow a real date in DATE_ADD we need to add some decision logic to the parsing -process (makes up for a nice excercise). +process (makes up for a nice exercise). Now as you see, the Parsing process doesn't catch all the possible SQL errors, here we don't match for all the valid inputs for the interval unit. diff --git a/cookbook/en/implementing-the-notify-changetracking-policy.txt b/cookbook/en/implementing-the-notify-changetracking-policy.txt index 025375487..7c401e4dd 100644 --- a/cookbook/en/implementing-the-notify-changetracking-policy.txt +++ b/cookbook/en/implementing-the-notify-changetracking-policy.txt @@ -1,5 +1,5 @@ -The NOTIFY changetracking policy is the most effective changetracking policy provided by Doctrine but it requires some boilerplate code. This recipe will show you how this boilerplate code should look like. We will implement it on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. +The NOTIFY change-tracking policy is the most effective change-tracking policy provided by Doctrine but it requires some boilerplate code. This recipe will show you how this boilerplate code should look like. We will implement it on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. ++ Implementing NotifyPropertyChanged diff --git a/cookbook/en/strategy-cookbook-introduction.txt b/cookbook/en/strategy-cookbook-introduction.txt index 88c02a7da..18cd0fe58 100644 --- a/cookbook/en/strategy-cookbook-introduction.txt +++ b/cookbook/en/strategy-cookbook-introduction.txt @@ -13,15 +13,15 @@ Given a Content-Management-System, we probably want to add / edit some so-called So, in this scenario, when building your CMS, you will surely add lots of blocks and panels to your pages and you will find yourself highly uncomfortable because of the following: * Every existing page needs to know about the panels it contains - therefore, you'll have an association to your panels. But if you've got several types of panels - what do you do? Add an association to -every paneltype? This wouldn't be flexible. You might be tempted to add an AbstractPanelEntity and an AbstractBlockEntity that use class inheritance. Your page could then only confer to the AbstractPanelType and Doctrine 2 would do the rest for you, i.e. load the right entites. But - you'll for sure have lots of panels and blocks, and even worse, you'd have to edit the discriminator map *manually* every time you or another developer implements a new block / entity. This would tear down any effort of modular programming. +every panel-type? This wouldn't be flexible. You might be tempted to add an AbstractPanelEntity and an AbstractBlockEntity that use class inheritance. Your page could then only confer to the AbstractPanelType and Doctrine 2 would do the rest for you, i.e. load the right entities. But - you'll for sure have lots of panels and blocks, and even worse, you'd have to edit the discriminator map *manually* every time you or another developer implements a new block / entity. This would tear down any effort of modular programming. Therefore, we need something thats far more flexible. ++ Solution -The solution itself is pretty easy. We will have one base class that will be loaded via the page and that has specific behaviour - a Block class might render the frontend and even the backend, for example. Now, every block that you'll write might look different or need different data - therefore, we'll offer an API to these methods but internally, we use a strategy that exactly knows what to do. +The solution itself is pretty easy. We will have one base class that will be loaded via the page and that has specific behaviour - a Block class might render the front-end and even the backend, for example. Now, every block that you'll write might look different or need different data - therefore, we'll offer an API to these methods but internally, we use a strategy that exactly knows what to do. -First of all, we need to make sure that we have an interface that contains every needed action. Such actions would be rendering the frontend or the backend, solving dependencies (blocks that are supposed to be placed in the sidebar could refuse to be placed in the middle of your page, for example). +First of all, we need to make sure that we have an interface that contains every needed action. Such actions would be rendering the front-end or the backend, solving dependencies (blocks that are supposed to be placed in the sidebar could refuse to be placed in the middle of your page, for example). Such an interface could look like this: diff --git a/cookbook/en/validation-of-entities.txt b/cookbook/en/validation-of-entities.txt index 739a608d2..047fed22d 100644 --- a/cookbook/en/validation-of-entities.txt +++ b/cookbook/en/validation-of-entities.txt @@ -1,5 +1,5 @@ Doctrine 2 does not ship with any internal validators, the reason being that -we think all the frameworks out there already ship with quite decents ones that can be integrated +we think all the frameworks out there already ship with quite decent ones that can be integrated into your Domain easily. What we offer are hooks to execute any kind of validation. > **Note** @@ -7,7 +7,7 @@ into your Domain easily. What we offer are hooks to execute any kind of validati > one of many options. Of course you can also perform validations in value setters > or any other method of your entities that are used in your code. -Entities can register lifecycle evnt methods with Doctrine that are called on +Entities can register lifecycle event methods with Doctrine that are called on different occasions. For validation we would need to hook into the events called before persisting and updating. Even though we don't support validation out of the box, the implementation is even simpler than in Doctrine 1 @@ -71,7 +71,7 @@ this will happen before Beta 1 though. Now validation is performed whenever you call `EntityManager#persist($order)` or when you call `EntityManager#flush()` and an order is about to be updated. -Any Exception that happens in the lifecycle callbacks will be catched by the +Any Exception that happens in the lifecycle callbacks will be cached by the EntityManager and the current transaction is rolled back. Of course you can do any type of primitive checks, not null, email-validation, string size, @@ -105,7 +105,7 @@ Additionally there is no limitation in the number of methods you register on one particular event, i.e. you can register multiple methods for validation in "PrePersist" or "PreUpdate" or mix and share them in any combinations between those two events. -There is no limit to what you can and can't validate in "PrePersist" and "PreUpdate" aslong as +There is no limit to what you can and can't validate in "PrePersist" and "PreUpdate" as long as you don't create new entity instances. This was already discussed in the previous blog post on the Versionable extension, which requires another type of event called "onFlush". From 61c1f0e1ed72eb5fedede45d0cb485502c6d2fac Mon Sep 17 00:00:00 2001 From: Ralfas Date: Tue, 5 Oct 2010 13:11:34 +0100 Subject: [PATCH 118/430] fixed typos --- cookbook/en.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbook/en.txt b/cookbook/en.txt index 7240ec98d..e7ee1629b 100644 --- a/cookbook/en.txt +++ b/cookbook/en.txt @@ -1,6 +1,6 @@ + Getting Started XML-Edition + Implementing ArrayAccess for domain objects -+ Implementing the NOTIFY changetracking policy ++ Implementing the NOTIFY change-tracking policy + Validation of Entities + Implementing wakeup or clone + Integrating with CodeIgniter From f8ba66bb0caff8d6b4c4bd2e3f451e7b6b3cc666 Mon Sep 17 00:00:00 2001 From: Ralfas Date: Tue, 5 Oct 2010 22:43:56 +0100 Subject: [PATCH 119/430] fixed typos --- manual/en/annotations-reference.txt | 28 +++++++++--------- manual/en/architecture.txt | 2 +- manual/en/association-mapping.txt | 10 +++---- manual/en/basic-mapping.txt | 6 ++-- manual/en/best-practices.txt | 2 +- manual/en/dql-doctrine-query-language.txt | 36 +++++++++++------------ manual/en/events.txt | 10 +++---- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/manual/en/annotations-reference.txt b/manual/en/annotations-reference.txt index fa154687a..37b035c0e 100644 --- a/manual/en/annotations-reference.txt +++ b/manual/en/annotations-reference.txt @@ -50,9 +50,9 @@ Optional attributes: * length - Used by the "string" type to determine its maximum length in the database. Doctrine does not validate the length of a string values for you. * precision - The precision for a decimal (exact numeric) column (Applies only for decimal column) * scale - The scale for a decimal (exact numeric) column (Applies only for decimal column) -* unique - Boolean value to determine if the value of the column should be unique accross all rows of the underlying entities table. +* unique - Boolean value to determine if the value of the column should be unique across all rows of the underlying entities table. * nullable - Determines if NULL values allowed for this column. -* columnDefinition - DDL SQL snippet that starts after the column name and specificies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. However you should make careful use of this feature and the consequences. Additionally you should remember that the "type" attribute still handles the conversion between PHP and Database values. If you use this attribute on a column that is used for joins between tables you should also take a look at [@JoinColumn](#ann_joincolumn). +* columnDefinition - DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. However you should make careful use of this feature and the consequences. Additionally you should remember that the "type" attribute still handles the conversion between PHP and Database values. If you use this attribute on a column that is used for joins between tables you should also take a look at [@JoinColumn](#ann_joincolumn). Examples: @@ -76,7 +76,7 @@ Examples: +++ @ChangeTrackingPolicy The Change Tracking Policy annotation allows to specify how the Doctrine 2 UnitOfWork should detect changes -in properties of entities during flush. By default each entity is checked according to a deferred implict +in properties of entities during flush. By default each entity is checked according to a deferred implicit strategy, which means upon flush UnitOfWork compares all the properties of an entity to a previously stored snapshot. This works out of the box, however you might want to tweak the flush performance where using another change tracking policy is an interesting option. @@ -98,7 +98,7 @@ Example: +++ @DiscrimnatorColumn -This annotation is a required annotation for the topmost/super class of an inheritance hierachy. It specifies +This annotation is a required annotation for the topmost/super class of an inheritance hierarchy. It specifies the details of the column which saves the name of the class, which the entity is actually instantiated as. Required attributes: @@ -113,7 +113,7 @@ Optional attributes: +++ @DiscriminatorMap -The discrimnator map is a required annotation on the top-most/super class in an inheritance hierachy. It takes +The discriminator map is a required annotation on the top-most/super class in an inheritance hierarchy. It takes an array as only argument which defines which class should be saved under which name in the database. Keys are the database value and values are the classes, either as fully- or as unqualified class names depending if the classes are in the namespace or not. @@ -138,7 +138,7 @@ Required annotation to mark a PHP class as Entity. Doctrine manages the persiste Optional attributes: -* repositoryClass - Specifies the FQCN of a subclass of the Doctrine\ORM\EntityRepository. Use of repositories for entites is encouraged to keep specialized DQL and SQL operations separated from the Model/Domain Layer. +* repositoryClass - Specifies the FQCN of a subclass of the Doctrine\ORM\EntityRepository. Use of repositories for entities is encouraged to keep specialized DQL and SQL operations separated from the Model/Domain Layer. Example: @@ -222,7 +222,7 @@ Example: +++ @Id The annotated instance variable will be marked as entity identifier, the primary key in the database. -This annotation is a marker only and has no required or optional attributes. For entites that have multiple +This annotation is a marker only and has no required or optional attributes. For entities that have multiple identifier columns each column has to be marked with @Id. Example: @@ -237,7 +237,7 @@ Example: +++ @InheritanceType -In an inheritance hierachy you have to use this annotation on the topmost/super class to define which +In an inheritance hierarchy you have to use this annotation on the topmost/super class to define which strategy should be used for inheritance. Currently Single Table and Class Table Inheritance are supported. This annotation has always been used in conjunction with the [@DiscriminatorMap](#ann_discriminatormap) and @@ -273,7 +273,7 @@ Examples: This annotation is used in the context of relations in [@ManyToOne](#ann_manytoone), [@OneToOne](#ann_onetoone) fields and in the Context of [@JoinTable](#ann_jointable) nested inside a @ManyToMany. This annotation is not required. -If its not specified the attributes *name* and *referencedColumnName* are infered from the table and primary key names. +If its not specified the attributes *name* and *referencedColumnName* are inferred from the table and primary key names. Required attributes: @@ -286,7 +286,7 @@ Optional attributes: * nullable - Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true. * onDelete - Cascade Action (Database-level) * onUpdate - Cascade Action (Database-level) -* columnDefinition - DDL SQL snippet that starts after the column name and specificies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on @JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a "columnDefinition" attribute on [@Column](#ann_column) also sets the related @JoinColumn's columnDefinition. This is necessary to make foreign keys work. +* columnDefinition - DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on @JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a "columnDefinition" attribute on [@Column](#ann_column) also sets the related @JoinColumn's columnDefinition. This is necessary to make foreign keys work. Example: @@ -306,14 +306,14 @@ with an entity that has multiple identifiers. +++ @JoinTable -Using [@OneToMany](#ann_onetomany) or [@ManyToMany](#ann_manytomany) on the owning side of the relation requires to specifiy +Using [@OneToMany](#ann_onetomany) or [@ManyToMany](#ann_manytomany) on the owning side of the relation requires to specify the @JoinTable annotation which describes the details of the database join table. If you do not specify @JoinTable on these relations reasonable mapping defaults apply using the affected table and the column names. Required attributes: * name - Database name of the join-table -* joinColumns - An array of @JoinColumn annotations describing the join-relation between the owning entites table and the join table. +* joinColumns - An array of @JoinColumn annotations describing the join-relation between the owning entities table and the join table. * inverseJoinColumns - An array of @JoinColumn annotations describing the join-relation between the inverse entities table and the join table. Optional attributes: @@ -345,7 +345,7 @@ Optional attributes: * cascade - Cascade Option * fetch - One of LAZY or EAGER -* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. +* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. Example: @@ -521,7 +521,7 @@ Marks a method on the entity to be called as a @PreUpdate event. Only works with +++ @SequenceGenerator -For the use with @generatedValue(strategy="SEQUENCE") this annotation allows to specifiy details about the sequence, +For the use with @generatedValue(strategy="SEQUENCE") this annotation allows to specify details about the sequence, such as the increment size and initial values of the sequence. Required attributes: diff --git a/manual/en/architecture.txt b/manual/en/architecture.txt index 81c33ea49..e9ef689d5 100644 --- a/manual/en/architecture.txt +++ b/manual/en/architecture.txt @@ -54,7 +54,7 @@ entity instance still holds references to proxy objects or is still managed by a If you intend to serialize (and unserialize) entity instances that still hold references to proxy objects you may run into problems with private properties because of technical limitations. Proxy objects implement `__sleep` and it is not possible for `__sleep` to return names of -private properties in parent classes. On ther other hand it is not a solution for proxy objects +private properties in parent classes. On the other hand it is not a solution for proxy objects to implement `Serializable` because Serializable does not work well with any potential cyclic object references (at least we did not find a way yet, if you did, please contact us). diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index dc30ce31b..52ba3200f 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -11,9 +11,9 @@ When mapping bidirectional associations it is important to understand the concep The following rules apply to *bidirectional* associations: -* The inverse side of a bidirectional relationship must refer to its owning side by use of the mappedBy attribute of the OneToOne, OneToMany, or ManyToMany mapping declaration. The mappedBy attribute designates the field in the entity that is the owner of the relationship. -* The owning side of a bidirectional relationship must refer to its inverse side by use of the inversedBy attribute of the OneToOne, ManyToOne, or ManyToMany mapping declaration. The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. -* The many side of OneToMany/ManyToOne bidirectional relationships *must* be the owning side, hence the mappedBy element can not be specified on the ManyToOne side. +* The inverse side of a bidirectional relationship must refer to its owning side by use of the mappedBy attribute of the OneToOne, OneToMany, or ManyToMany mapping declaration. The mappedBy attribute designates the field in the entity that is the owner of the relationship. +* The owning side of a bidirectional relationship must refer to its inverse side by use of the inversedBy attribute of the OneToOne, ManyToOne, or ManyToMany mapping declaration. The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. +* The many side of OneToMany/ManyToOne bidirectional relationships *must* be the owning side, hence the mappedBy element can not be specified on the ManyToOne side. * For OneToOne bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key (@JoinColumn(s)). * For ManyToMany bidirectional relationships either side may be the owning side (the side that defines the @JoinTable and/or does not make use of the mappedBy attribute, thus using a default join table). @@ -43,7 +43,7 @@ in the database. ++ Collections -In all the examples of many-valued associations in this manual we will make use of a `Collection` interface and a corresponding default implementation `ArrayCollection` that are defined in the `Doctrine\Common\Collections` namespace. Why do we need that? Doesn't that couple my domain model to Doctrine? Unfortunately, PHP arrays, while being great for many things, do not make up for good collections of business objects, especially not in the context of an ORM. The reason is that plain PHP arrays can not be transparently extended / instrumented in PHP code, which is necessary for a lot of advanced ORM features. The classes / interfaces that come closest to an OO collection are ArrayAccess and ArrayObject but until instances of these types can be used in all places where a plain array can be used (something that may happen in PHP6) their useability is fairly limited. You "can" type-hint on `ArrayAccess` instead of `Collection`, since the Collection interface extends `ArrayAccess`, but this will severely limit you in the way you can work with the collection, because the `ArrayAccess` API is (intentionally) very primitive and more importantly because you can not pass this collection to all the useful PHP array functions, which makes it very hard to work with. +In all the examples of many-valued associations in this manual we will make use of a `Collection` interface and a corresponding default implementation `ArrayCollection` that are defined in the `Doctrine\Common\Collections` namespace. Why do we need that? Doesn't that couple my domain model to Doctrine? Unfortunately, PHP arrays, while being great for many things, do not make up for good collections of business objects, especially not in the context of an ORM. The reason is that plain PHP arrays can not be transparently extended / instrumented in PHP code, which is necessary for a lot of advanced ORM features. The classes / interfaces that come closest to an OO collection are ArrayAccess and ArrayObject but until instances of these types can be used in all places where a plain array can be used (something that may happen in PHP6) their usability is fairly limited. You "can" type-hint on `ArrayAccess` instead of `Collection`, since the Collection interface extends `ArrayAccess`, but this will severely limit you in the way you can work with the collection, because the `ArrayAccess` API is (intentionally) very primitive and more importantly because you can not pass this collection to all the useful PHP array functions, which makes it very hard to work with. > **CAUTION** > The Collection interface and ArrayCollection class, like everything else in the @@ -57,7 +57,7 @@ In all the examples of many-valued associations in this manual we will make use ++ Mapping Defaults -Before we introduce all the association mappings in detailyou should note that the @JoinColumn and @JoinTable +Before we introduce all the association mappings in detail, you should note that the @JoinColumn and @JoinTable definitions are usually optional and have sensible default values. The defaults for a join column in a one-to-one/many-to-one association is as follows: diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 7a1359ccc..39120851f 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -237,7 +237,7 @@ This tells Doctrine to automatically generate a value for the identifier. How th +++ Identifier Generation Strategies The previous example showed how to use the default identifier generation strategy without knowing the underlying database with the AUTO-detection strategy. -It is also possible to specifiy the identifier generation strategy more explicitly, which allows to make use of some additional features. +It is also possible to specify the identifier generation strategy more explicitly, which allows to make use of some additional features. Here is the list of possible generation strategies: @@ -256,7 +256,7 @@ Here is the list of possible generation strategies: ++++ Sequence Generator The Sequence Generator can currently be used in conjunction with Oracle or Postgres and allows some additional configuration options besides -specifiying the sequence's name: +specifying the sequence's name: [php] class User { @@ -292,7 +292,7 @@ Doctrine can generate identifier values for the allocationSizes amount of entiti +++ Composite Keys -Doctrine 2 allows to use composite primary keys. There are however some restrictions oposed to using a single identifier. +Doctrine 2 allows to use composite primary keys. There are however some restrictions opposed to using a single identifier. The use of the `@GeneratedValue` annotation is only supported for simple (not composite) primary keys, which means you can only use composite keys if you generate the primary key values yourself before calling `EntityManager#persist()` on the entity. diff --git a/manual/en/best-practices.txt b/manual/en/best-practices.txt index 9c095a5ae..116761b5f 100644 --- a/manual/en/best-practices.txt +++ b/manual/en/best-practices.txt @@ -72,6 +72,6 @@ Foreign keys have no meaning whatsoever in an object model. Foreign keys are how While Doctrine will automatically wrap all DML operations in a transaction on flush(), it is considered best practice to explicitly set the transaction boundaries yourself. Otherwise every single query is wrapped in a small transaction (Yes, SELECT queries, too) since you can not talk to your database outside of a transaction. -While such short transactions for read-only (SELECT) queries generally dont have any noticable performance impact, it is still preferrable to use fewer, well-defined transactions +While such short transactions for read-only (SELECT) queries generally don't have any noticeable performance impact, it is still preferable to use fewer, well-defined transactions that are established through explicit transaction boundaries. diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index e92db7aef..c8dcefbc9 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -273,7 +273,7 @@ Get all users that have no phonenumber $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); $users = $query->getResult(); -Get all instances of a specific type, for use with inheritance hierachies: +Get all instances of a specific type, for use with inheritance hierarchies: [php] $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee'); @@ -304,7 +304,7 @@ You use the partial syntax when joining as well: The INDEX BY construct is nothing that directly translates into SQL but that affects object and array hydration. After each FROM and JOIN clause you specify by which field this class should be indexed in the result. By default a result is incremented -by numerical keys starting with 0. However with INDEX BY you can specifiy any +by numerical keys starting with 0. However with INDEX BY you can specify any other column to be the key of your result, it really only makes sense with primary or unique fields though: @@ -340,7 +340,7 @@ example shows: UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) -References to related entities are only possible in the WHERE clause and using subselects. +References to related entities are only possible in the WHERE clause and using sub-selects. > **CAUTION** > DQL UPDATE statements are ported directly into a Database UPDATE statement and therefore bypass @@ -375,11 +375,11 @@ The following functions are supported in SELECT, WHERE and HAVING clauses: * CURRENT_TIME() - Returns the current time * CURRENT_TIMESTAMP() - Returns a timestamp of the current date and time. * LENGTH(str) - Returns the length of the given string -* LOCATE(needle, haystack [, offset]) - Locate the first occurance of the substring in the string. +* LOCATE(needle, haystack [, offset]) - Locate the first occurrence of the substring in the string. * LOWER(str) - returns the string lowercased. * MOD(a, b) - Return a MOD b. * SIZE(collection) - Return the number of elements in the specified collection -* SQRT(q) - Return the squareroot of q. +* SQRT(q) - Return the square-root of q. * SUBSTRING(str, start [, length]) - Return substring of given string. * TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str) - Trim the string by the given trim char, defaults to whitespaces. * UPPER(str) - Return the upper-case of the given string. @@ -399,12 +399,12 @@ The following aggregate functions are allowed in SELECT and GROUP BY clauses: AV DQL offers a wide-range of additional expressions that are known from SQL, here is a list of all the supported constructs: -* `ALL/ANY/SOME` - Used in a WHERE clause followed by a subselect this works like the equivalent constructs in SQL. +* `ALL/ANY/SOME` - Used in a WHERE clause followed by a sub-select this works like the equivalent constructs in SQL. * `BETWEEN a AND b` and `NOT BETWEEN a AND b` can be used to match ranges of arithmetic values. * `IN (x1, x2, ...)` and `NOT IN (x1, x2, ..)` can be used to match a set of given values. * `LIKE ..` and `NOT LIKE ..` match parts of a string or text using % as a wildcard. * `IS NULL` and `IS NOT NULL` to check for null values -* `EXISTS` and `NOT EXISTS` in combination with a subselect +* `EXISTS` and `NOT EXISTS` in combination with a sub-select +++ Adding your own functions to the DQL language @@ -586,7 +586,7 @@ An instance of the `Doctrine\ORM\Query` class represents a DQL query. You create // example1: passing a DQL string $q = $em->createQuery('select u from MyProject\Model\User u'); - // example2: usin setDql + // example2: using setDql $q = $em->createQuery(); $q->setDql('select u from MyProject\Model\User u'); @@ -612,7 +612,7 @@ The use of the methods mentioned earlier is generally preferred as it leads to m +++ Pure and Mixed Results -The nature of a result returned by a DQL SELECT query retrieved through `Query#getResult()` or `Query#getArrayResult()` can be of 2 forms: **pure** and **mixed**. In the previous simple examples, you already saw a "pure" query result, with only objects. By default, the result type is **pure** but **as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. A mixed result has a different structure than a pure result in order to accomodate for the scalar values. +The nature of a result returned by a DQL SELECT query retrieved through `Query#getResult()` or `Query#getArrayResult()` can be of 2 forms: **pure** and **mixed**. In the previous simple examples, you already saw a "pure" query result, with only objects. By default, the result type is **pure** but **as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. A mixed result has a different structure than a pure result in order to accommodate for the scalar values. A pure result usually looks like this: @@ -755,10 +755,10 @@ Now the hydrator is ready to be used in your queries: $query = $em->createQuery('SELECT u FROM CmsUser u'); $results = $query->getResult('CustomHydrator'); -+++ Iterating Large Resultsets ++++ Iterating Large Result Sets There are situations when a query you want to execute returns a very large result-set that needs -to be processed. All the previously described hydration modes completly load a result-set into +to be processed. All the previously described hydration modes completely load a result-set into memory which might not be feasible with large result sets. See the [Batch Processing](batch-processing) section on details how to iterate large result sets. @@ -818,10 +818,10 @@ are to be used in userland: hint can be used to handle memory consumption problems with large result-sets that contain char or binary data. Doctrine has no way of implicitly reloading this data. Partially loaded objects have to be passed to `EntityManager::refresh()` if they are to be reloaded fully from the database. -* Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland aswell. +* Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland as well. If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the fields of the existing entity will be refreshed. In normal operation a result-set that loads data of an already existing -entity is discarded in favour of the already existing entity. +entity is discarded in favor of the already existing entity. * Query::HINT_CUSTOM_TREE_WALKERS - An array of additional `Doctrine\ORM\Query\TreeWalker` instances that are attached to the DQL query parsing process. @@ -845,7 +845,7 @@ of the Query Cache, however if you do there are several methods to interact with ++++ First and Max Result Items (DQL Query Only) -You can limit the number of results returned from a DQL query aswell as specify the starting offset, Doctrine +You can limit the number of results returned from a DQL query as well as specify the starting offset, Doctrine then uses a strategy of manipulating the select query to return only the requested number of results: * `Query::setMaxResults($maxResults)` @@ -854,7 +854,7 @@ then uses a strategy of manipulating the select query to return only the request > **NOTE** > If your query contains a fetch-joined collection specifying the result limit methods are not working > as you would expect. Set Max Results restricts the number of database result rows, however in the -> case of fetch-joined collections one root entity might appear in many rows, effectivly hydrating +> case of fetch-joined collections one root entity might appear in many rows, effectively hydrating > less than the specified number of results. ++ EBNF @@ -866,8 +866,8 @@ The following context-free grammar, written in an EBNF variant, describes the Do * non-terminals begin with an upper case character * terminals begin with a lower case character * parentheses (...) are used for grouping -* square brackets [...] are used for defining an optional part, eg. zero or one time -* curly brackets {...} are used for repetion, eg. zero or more times +* square brackets [...] are used for defining an optional part, e.g. zero or one time +* curly brackets {...} are used for repetition, e.g. zero or more times * double quotation marks "..." define a terminal string a vertical bar | represents an alternative +++ Terminals @@ -901,7 +901,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do AbstractSchemaName ::= identifier /* identifier that must be a field (the "name" of "u.name") */ - /* This is responsable to know if the field exists in Object, no matter if it's a relation or a simple field */ + /* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */ FieldIdentificationVariable ::= identifier /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ diff --git a/manual/en/events.txt b/manual/en/events.txt index 3df59c619..3218814d3 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -111,7 +111,7 @@ The EntityManager and UnitOfWork trigger a bunch of events during the life-time * postUpdate - The postUpdate event occurs after the database update operations to entity data. * postLoad - The postLoad event occurs for an entity after the entity has been loaded into the current EntityManager from the database or after the refresh operation has been applied to it. * loadClassMetadata - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (annotations/xml/yaml). -* onFlush - The onFlush event occours after the change-sets of all managed entities are computed. This event is not a lifecycle callback. +* onFlush - The onFlush event occurs after the change-sets of all managed entities are computed. This event is not a lifecycle callback. > **CAUTION** > Note that the postLoad event occurs for an entity before any associations have been @@ -126,8 +126,8 @@ You can access the Event constants from the `Events` class in the ORM package. These can be hooked into by two different types of event listeners: -* Lifecycle Callbacks are methods on the entity classes that are called when the event is triggered. They recieve absolutely no arguments and are specifically designed to allow changes inside the entity classes state. -* Lifecycle Event Listeners are classes with specific callback methods that recieves some kind of `EventArgs` instance which give access to the entity, EntityManager or other relevant data. +* Lifecycle Callbacks are methods on the entity classes that are called when the event is triggered. They receive absolutely no arguments and are specifically designed to allow changes inside the entity classes state. +* Lifecycle Event Listeners are classes with specific callback methods that receives some kind of `EventArgs` instance which give access to the entity, EntityManager or other relevant data. > **NOTE** > All Lifecycle events that happen during the `flush()` of an EntityManager have very specific constraints on the allowed @@ -249,7 +249,7 @@ the event type. The allowed event types are the ones listed in the previous Life ++ Listening to Lifecycle Events Lifecycle event listeners are much more powerful than the simple lifecycle callbacks that are defined on the entity -classes. They allow to implement re-usable behaviours between different entity classes, yet require much more detailed +classes. They allow to implement re-usable behaviors between different entity classes, yet require much more detailed knowledge about the inner workings of the EntityManager and UnitOfWork. Please read the *Implementing Event Listeners* section carefully if you are trying to write your own listener. @@ -420,7 +420,7 @@ to call: private function validateCreditCard($no) { - // throw an exception to interupt flush event. Transaction will be rolled back. + // throw an exception to interrupt flush event. Transaction will be rolled back. } } From e4b1357a22e059b64b665f0db6b2bb73aaf005b3 Mon Sep 17 00:00:00 2001 From: Ralfas Date: Thu, 7 Oct 2010 22:39:52 +0100 Subject: [PATCH 120/430] fixed typos and grammar --- manual/en/improving-performance.txt | 4 ++-- manual/en/inheritance-mapping.txt | 2 +- manual/en/limitations-and-known-issues.txt | 2 +- manual/en/metadata-drivers.txt | 2 +- manual/en/native-sql.txt | 2 +- manual/en/partial-objects.txt | 2 +- manual/en/query-builder.txt | 28 +++++++++++----------- manual/en/tools.txt | 6 ++--- manual/en/transactions-and-concurrency.txt | 14 +++++------ manual/en/working-with-associations.txt | 2 +- manual/en/working-with-objects.txt | 12 +++++----- manual/en/xml-mapping.txt | 26 ++++++++++---------- 12 files changed, 51 insertions(+), 51 deletions(-) diff --git a/manual/en/improving-performance.txt b/manual/en/improving-performance.txt index 644636fc9..f0f05710e 100644 --- a/manual/en/improving-performance.txt +++ b/manual/en/improving-performance.txt @@ -4,14 +4,14 @@ It is highly recommended to make use of a bytecode cache like APC. A bytecode cache removes the need for parsing PHP code on every request and can greatly improve performance. > **NOTE** -> "If you care about performance and don’t use a bytecode cache then you don’t really care +> "If you care about performance and don't use a bytecode cache then you don't really care > about performance. Please get one and start using it." (Stas Malyshev, Core Contributor > to PHP and Zend Employee). ++ Metadata and Query caches -As already mentioned earlier in the chapter about configuring Doctrine, it is strongly discouraged to use Doctrine without a Metadata and Query cache (preferrably with APC or Memcache as the cache driver). Operating Doctrine without these caches means Doctrine will need to load your mapping information on every single request and has to parse each DQL query on every single request. This is a waste of resources. +As already mentioned earlier in the chapter about configuring Doctrine, it is strongly discouraged to use Doctrine without a Metadata and Query cache (preferably with APC or Memcache as the cache driver). Operating Doctrine without these caches means Doctrine will need to load your mapping information on every single request and has to parse each DQL query on every single request. This is a waste of resources. ++ Alternative Query Result Formats diff --git a/manual/en/inheritance-mapping.txt b/manual/en/inheritance-mapping.txt index b6a4b49ef..3b3ab68f7 100644 --- a/manual/en/inheritance-mapping.txt +++ b/manual/en/inheritance-mapping.txt @@ -9,7 +9,7 @@ mapped inheritance hierarchy (through Single Table Inheritance or Class Table In > **NOTE** > -> A mapped superclass cannot be an entity, it is not queryable and persistent relationships defined by a mapped +> A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped > superclass must be unidirectional. For further support of inheritance, the single or joined table inheritance > features have to be used. diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt index 23540c10e..9439ae1b2 100644 --- a/manual/en/limitations-and-known-issues.txt +++ b/manual/en/limitations-and-known-issues.txt @@ -138,7 +138,7 @@ as this is a pretty important feature we plan to add support for it in the futur +++ Custom Persisters -A Perister in Doctrine is an object that is responsible for the hydration and write operations of an entity against the database. +A Persister 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: diff --git a/manual/en/metadata-drivers.txt b/manual/en/metadata-drivers.txt index b07310346..9ba308cf5 100644 --- a/manual/en/metadata-drivers.txt +++ b/manual/en/metadata-drivers.txt @@ -14,7 +14,7 @@ Doctrine provides a few different ways for you to specify your metadata: Something important to note about the above drivers is they are all an intermediate step to the same end result. The mapping information is populated to `Doctrine\ORM\Mapping\ClassMetadata` instances. So in the end, Doctrine -only ever has to work with the api of the `ClassMetadata` class to get mapping +only ever has to work with the API of the `ClassMetadata` class to get mapping information for an entity. > **TIP** diff --git a/manual/en/native-sql.txt b/manual/en/native-sql.txt index f6dd12096..39de3ec68 100644 --- a/manual/en/native-sql.txt +++ b/manual/en/native-sql.txt @@ -18,7 +18,7 @@ A Doctrine result can contain the following components: * Entity results. These represent root result elements. * Joined entity results. These represent joined entities in associations of root entity results. * Field results. These represent a column in the result set that maps to a field of an entity. A field result always belongs to an entity result or joined entity result. - * Scalar results. These represent scalar values in the result set that will appear in each result row. Adding scalar results to a ResultSetMapping can also cause the overall result to becomed **mixed** (see DQL - Doctrine Query Language) if the same ResultSetMapping also contains entity results. + * Scalar results. These represent scalar values in the result set that will appear in each result row. Adding scalar results to a ResultSetMapping can also cause the overall result to become **mixed** (see DQL - Doctrine Query Language) if the same ResultSetMapping also contains entity results. * Meta results. These represent columns that contain meta-information, such as foreign keys and discriminator columns. When querying for objects (`getResult()`), all meta columns of root entities or joined entities must be present in the SQL query and mapped accordingly using `ResultSetMapping#addMetaResult`. diff --git a/manual/en/partial-objects.txt b/manual/en/partial-objects.txt index bb8f914b8..68e23dce9 100644 --- a/manual/en/partial-objects.txt +++ b/manual/en/partial-objects.txt @@ -21,7 +21,7 @@ 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 +the associated field is NULL, you don't 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. diff --git a/manual/en/query-builder.txt b/manual/en/query-builder.txt index b07bf6d79..0ad69b3e3 100644 --- a/manual/en/query-builder.txt +++ b/manual/en/query-builder.txt @@ -3,7 +3,7 @@ A `QueryBuilder` provides an API that is designed for conditionally constructing a DQL query in several steps. -It provides a set of classes and methods that is able to programatically build you queries, and also provides a fluent API. +It provides a set of classes and methods that is able to programmatically build queries, and also provides a fluent API. This means that you can change between one methodology to the other as you want, and also pick one if you prefer. +++ Constructing a new QueryBuilder object @@ -17,7 +17,7 @@ Here is an example how to build a `QueryBuilder` object: // example1: creating a QueryBuilder instance $qb = $em->createQueryBuilder(); -Once you created an instance of QueryBuilder, it provides a set of useful informative functions that you can use. +Once you have created an instance of QueryBuilder, it provides a set of useful informative functions that you can use. One good example is to inspect what type of object the `QueryBuilder` is. [php] @@ -46,16 +46,16 @@ It is possible to retrieve the associated `EntityManager` of the current `QueryB // example5: retrieve the associated Query object with the processed DQL $q = $qb->getQuery(); -Internally, `QueryBuilder` works with a DQL cache, which prevents multiple processment if called multiple times. Any changes that may affect the generated DQL actually modifies the state of `QueryBuilder` to a stage we call as STATE_DIRTY. -One `QueryBuilder`can be in two different state: +Internally, `QueryBuilder` works with a DQL cache to increase performance. Any changes that may affect the generated DQL actually modifies the state of `QueryBuilder` to a stage we call STATE_DIRTY. +One `QueryBuilder` can be in two different states: * `QueryBuilder::STATE_CLEAN`, which means DQL haven't been altered since last retrieval or nothing were added since its instantiation * `QueryBuilder::STATE_DIRTY`, means DQL query must (and will) be processed on next retrieval +++ Working with QueryBuilder -All helper methods in `QueryBuilder` relies actually on a single one: `add()`. -This method is the responsable to build every piece of DQL. It takes 3 parameters: `$dqlPartName`, `$dqlPart` and `$append` (default=false) +All helper methods in `QueryBuilder` actually rely on a single one: `add()`. +This method is responsible of building every piece of DQL. It takes 3 parameters: `$dqlPartName`, `$dqlPart` and `$append` (default=false) * `$dqlPartName`: Where the `$dqlPart` should be placed. Possible values: select, from, where, groupBy, having, orderBy * `$dqlPart`: What should be placed in `$dqlPartName`. Accepts a string or any instance of `Doctrine\ORM\Query\Expr\*` @@ -110,7 +110,7 @@ If you've got several parameters to bind to your query, you can also use setPara $qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2')); -Getting already bound parameters is easy - simply use the abovementioned syntax with "getParameter()" or "getParameters()": +Getting already bound parameters is easy - simply use the above mentioned syntax with "getParameter()" or "getParameters()": [php] // $qb instanceof QueryBuilder @@ -155,7 +155,7 @@ This class is called `Expr`, which provides a set of useful static methods to he )) ->add('orderBy', $qb->expr()->orderBy('u.surname', 'ASC')); -Although it still sounds complex, the ability to programatically create conditions are the main feature of `Expr`. +Although it still sounds complex, the ability to programmatically create conditions are the main feature of `Expr`. Here it is a complete list of supported helper methods available: [php] @@ -305,9 +305,9 @@ Here it is a complete list of supported helper methods available: ++++ Helper methods -Until now it was described the hardcore level of creating queries. It may be useful to work that way for optimization purposes, but most of the time it is preferred to work higher level. -To simplify even more the way you build a query in Doctrine, we can take advantage of what we call as helper methods. For all base code, it has a set of useful methods to simplify programmer's life. -Illustrating how to work with it, here is the same example 6 written now using `QueryBuilder` helper methods: +Until now we have described the lowest level (thought of as the hardcore method) of creating queries. It may be useful to work at this level for optimization purposes, but most of the time it is preferred to work at a higher level of abstraction. +To simplify even more the way you build a query in Doctrine, we can take advantage of what we call Helper methods. For all base code, there is a set of useful methods to simplify a programmer's life. +To illustrate how to work with them, here is the same example 6 re-written using `QueryBuilder` helper methods: [php] // $qb instanceof QueryBuilder @@ -333,7 +333,7 @@ Here is a converted example 8 to suggested standard way to build queries: )) ->orderBy('u.surname', 'ASC')); -Here is a complete list of helper methods in `QueryBuilder`: +Here is a complete list of helper methods available in `QueryBuilder`: [php] class QueryBuilder @@ -378,7 +378,7 @@ Here is a complete list of helper methods in `QueryBuilder`: // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10)); public function orWhere($where); - // NOTE: -> groupBy() overrides all previously set grouping items + // NOTE: -> groupBy() overrides all previously set grouping conditions // // Example - $qb->groupBy('u.id') public function groupBy($groupBy); @@ -398,7 +398,7 @@ Here is a complete list of helper methods in `QueryBuilder`: // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100')) public function orHaving($having); - // NOTE: -> orderBy() overrides all previously set ordering items + // NOTE: -> orderBy() overrides all previously set ordering conditions // // Example - $qb->orderBy('u.surname', 'DESC') public function orderBy($sort, $order = null); diff --git a/manual/en/tools.txt b/manual/en/tools.txt index 152788c7a..d461bafcf 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -38,7 +38,7 @@ When dealing with the ORM package, the EntityManagerHelper is required: )); $cli->setHelperSet($helperSet); -The HelperSet instance has to be generated in a separate file (ie. `cli-config.php`) that contains typical Doctrine +The HelperSet instance has to be generated in a separate file (i.e. `cli-config.php`) that contains typical Doctrine bootstrap code and predefines the needed HelperSet attributes mentioned above. A typical `cli-config.php` file looks as follows: [php] @@ -140,7 +140,7 @@ To drop the schema you can use the `dropSchema()` method. $tool->dropSchema($classes); This drops all the tables that are currently used by your metadata model. -When you are changing your metadata alot during development you might want +When you are changing your metadata a lot during development you might want to drop the complete database instead of only the tables of the current model to clean up with orphaned tables. @@ -185,7 +185,7 @@ Before using the orm:schema-tool commands, remember to configure your cli-config > > When using the Annotation Mapping Driver you have to either setup your autoloader in the cli-config.php > correctly to find all the entities, or you can use the second argument of the `EntityManagerHelper` to -> specifiy all the paths of your entities (or mapping files), i.e. +> specify all the paths of your entities (or mapping files), i.e. > `new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);` ++ Convert Mapping Information diff --git a/manual/en/transactions-and-concurrency.txt b/manual/en/transactions-and-concurrency.txt index 9e85a9cba..7d49539ed 100644 --- a/manual/en/transactions-and-concurrency.txt +++ b/manual/en/transactions-and-concurrency.txt @@ -10,7 +10,7 @@ For the most part, Doctrine 2 already takes care of proper transaction demarcati operations (INSERT/UPDATE/DELETE) are queued until `EntityManager#flush()` is invoked which wraps all of these changes in a single transaction. -However, Doctrine 2 also allows (and ecourages) you to take over and control transaction demarcation yourself. +However, Doctrine 2 also allows (and encourages) you to take over and control transaction demarcation yourself. These are two ways to deal with transactions when using the Doctrine ORM and are now described in more detail. @@ -85,10 +85,10 @@ is automatically rolled back and the `EntityManager` closed. When using explicit transaction demarcation and an exception occurs, the transaction should be rolled back immediately and the `EntityManager` closed by invoking `EntityManager#close()` and subsequently discarded, as demonstrated in the example above. This can be handled elegantly by the control abstractions shown earlier. -Note that when catching `Exception` you should generally rethrow the exception. If you intend to +Note that when catching `Exception` you should generally re-throw the exception. If you intend to recover from some exceptions, catch them explicitly in earlier catch blocks (but do not forget to rollback the transaction and close the `EntityManager` there as well). All other best practices of exception handling apply -similarly (i.e. either log or rethrow, not both, etc.). +similarly (i.e. either log or re-throw, not both, etc.). As a result of this procedure, all previously managed or removed instances of the `EntityManager` become detached. The state of the detached objects will be the state at the point at which the transaction was rolled back. @@ -96,7 +96,7 @@ The state of the objects is in no way rolled back and thus the objects are now o The application can continue to use the detached objects, knowing that their state is potentially no longer accurate. -If you intend to start another unit of work after an exception has occured you should do that with a new `EntityManager`. +If you intend to start another unit of work after an exception has occurred you should do that with a new `EntityManager`. ++ Locking Support @@ -115,7 +115,7 @@ Doctrine has integrated support for automatic optimistic locking via a version f that should be protected against concurrent modifications during long-running business transactions gets a version field that is either a simple number (mapping type: integer) or a timestamp (mapping type: datetime). When changes to such an entity are persisted at the end of a long-running conversation the version of the entity is compared to -the version in the database and if they dont match, an `OptimisticLockException` is thrown, indicating that the +the version in the database and if they don't match, an `OptimisticLockException` is thrown, indicating that the entity has been modified by someone else already. You designate a version field in an entity as follows. In this example we'll use an integer. @@ -229,12 +229,12 @@ And the change headline action (POST Request): +++ Pessimistic Locking Doctrine 2 supports Pessimistic Locking at the database level. No attempt is being made to implement pessimistic locking -inside Doctrine, rather vendor-specific and ANSI-SQL commands are used to aquire row-level locks. Every Entity can +inside Doctrine, rather vendor-specific and ANSI-SQL commands are used to acquire row-level locks. Every Entity can be part of a pessimistic lock, there is no special metadata required to use this feature. However for Pessimistic Locking to work you have to disable the Auto-Commit Mode of your Database and start a transaction around your pessimistic lock use-case using the "Approach 2: Explicit Transaction Demarcation" described -above. Doctrine 2 will throw an Exception if you attempt to aquire an pessimistic lock and no transaction is running. +above. Doctrine 2 will throw an Exception if you attempt to acquire an pessimistic lock and no transaction is running. Doctrine 2 currently supports two pessimistic lock modes: diff --git a/manual/en/working-with-associations.txt b/manual/en/working-with-associations.txt index 06dd1f767..fe4786df2 100644 --- a/manual/en/working-with-associations.txt +++ b/manual/en/working-with-associations.txt @@ -325,7 +325,7 @@ Using the User-Comment entities from above, a very simple example can show the p // not calling $favoriteComment->getUserFavorites()->add($user); $user->getFavorites()->contains($favoriteComment); // TRUE - $favoriteComment->getUerFavorites()->contains($user); // FALSE + $favoriteComment->getUserFavorites()->contains($user); // FALSE There are to approaches to handle this problem in your code: diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 5fa6455c5..0decce091 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -1,7 +1,7 @@ In this chapter we will help you understand the `EntityManager` and the `UnitOfWork`. A Unit of Work is similar to an object-level transaction. A new Unit of Work is -implicity started when an EntityManager is initially created or after +implicitly started when an EntityManager is initially created or after `EntityManager#flush()` has been invoked. A Unit of Work is committed (and a new one started) by invoking `EntityManager#flush()`. @@ -213,7 +213,7 @@ The semantics of the remove operation, applied to an entity X are as follows: After an entity has been removed its in-memory state is the same as before the removal, except for generated identifiers. -Removing an entity will also automatically delete any exisiting records in many-to-many +Removing an entity will also automatically delete any existing records in many-to-many join tables that link this entity. The action taken depends on the value of the `@joinColumn` mapping attribute "onDelete". Either Doctrine issues a dedicated `DELETE` statement for records of each join table or it depends on the foreign key semantics of @@ -234,7 +234,7 @@ ways with very different performance impacts. 3. Using foreign key semantics `onDelete="CASCADE"` can force the database to remove all associated objects internally. This strategy is a bit tricky to get right but can be very powerful and fast. You should be aware - however that using strategy 1 (`CASCADE=REMOVE`) completly by-passes + however that using strategy 1 (`CASCADE=REMOVE`) completely by-passes any foreign key `onDelete=CASCADE` option, because Doctrine will fetch and remove all associated entities explicitly nevertheless. @@ -300,7 +300,7 @@ MERGE or ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' The `merge` operation will throw an `OptimisticLockException` if the entity being merged uses optimistic locking through a version field and the versions -of the entity being merged and the managed copy dont match. This usually means +of the entity being merged and the managed copy don't match. This usually means that the entity has been modified while being detached. The `merge` operation is usually not as frequently needed and used as `persist` @@ -448,7 +448,7 @@ Essentially, `EntityManager#find()` is just a shortcut for the following: // $em instanceof EntityManager $user = $em->getRepository('MyProject\Domain\User')->find($id); -`EntityManager#getRepository($entityName)` returns a repository object which provides many ways to retreive entities of the specified type. By default, the repository instance is of type `Doctrine\ORM\EntityRepository`. You can also use custom repository classes as shown later. +`EntityManager#getRepository($entityName)` returns a repository object which provides many ways to retrieve entities of the specified type. By default, the repository instance is of type `Doctrine\ORM\EntityRepository`. You can also use custom repository classes as shown later. +++ By Simple Conditions @@ -489,7 +489,7 @@ Whenever you have a managed entity instance at hand, you can traverse and use an +++ By DQL The most powerful and flexible method to query for persistent objects is the Doctrine Query Language, an object query language. DQL enables you to query for persistent objects in the language of objects. DQL understands classes, fields, inheritance and associations. -DQL is syntactically very similar to the familar SQL but *it is not SQL*. +DQL is syntactically very similar to the familiar SQL but *it is not SQL*. A DQL query is represented by an instance of the `Doctrine\ORM\Query` class. You create a query using `EntityManager#createQuery($dql)`. Here is a simple example: diff --git a/manual/en/xml-mapping.txt b/manual/en/xml-mapping.txt index b96420147..0ebdd863f 100644 --- a/manual/en/xml-mapping.txt +++ b/manual/en/xml-mapping.txt @@ -230,7 +230,7 @@ Optional attributes for ``: > **NOTE** > > If you want to implement a cross-vendor compatible application you have to specify and -> additionaly define the element, if Doctrine chooses the sequence strategy for a platform. +> additionally define the element, if Doctrine chooses the sequence strategy for a platform. +++ Defining a Mapped Superclass @@ -256,7 +256,7 @@ a mapped superclass. +++ Defining Inheritance Mappings There are currently two inheritance persistence strategies that you can choose from when defining entities that -inherit from each other. Single Table inheritance saves the fields of the complete inheritance hierachy in a single table, +inherit from each other. Single Table inheritance saves the fields of the complete inheritance hierarchy in a single table, joined table inheritance creates a table for each entity combining the fields using join conditions. You can specify the inheritance type in the `` element and then use the `` and @@ -276,7 +276,7 @@ The allowed values for inheritance-type attribute are `JOINED` or `SINGLE_TABLE` > **NOTE** > -> All inheritance related definitions have to be defined on the root entity of the hierachy. +> All inheritance related definitions have to be defined on the root entity of the hierarchy. +++ Defining Lifecycle Callbacks @@ -292,7 +292,7 @@ You can define the lifecycle callback methods on your entities using the `` element. The required +You can define One-To-One Relations/Associations using the `` element. The required and optional attributes depend on the associations being on the inverse or owning side. For the inverse side the mapping is as simple as: @@ -304,7 +304,7 @@ For the inverse side the mapping is as simple as: Required attributes for inverse One-To-One: -* field - Name of the property/field on the entitys PHP class. +* field - Name of the property/field on the entity's PHP class. * target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. * mapped-by - Name of the field on the owning side (here Address entity) that contains the owning side association. @@ -317,7 +317,7 @@ For the owning side this mapping would look like: Required attributes for owning One-to-One: -* field - Name of the property/field on the entitys PHP class. +* field - Name of the property/field on the entity's PHP class. * target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. Optional attributes for owning One-to-One: @@ -331,7 +331,7 @@ Without the nested `` element Doctrine assumes to foreign key to Entities table. This is because the `MyProject\Address` entity is the owning side of this association, which means it contains the foreign key. -The completed explictly defined mapping is: +The completed explicitly defined mapping is: [xml] @@ -352,7 +352,7 @@ compared to the one-to-one case. The minimal mapping for this association looks Required attributes: -* field - Name of the property/field on the entitys PHP class. +* field - Name of the property/field on the entity's PHP class. * target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. Optional attributes: @@ -362,7 +362,7 @@ Optional attributes: * fetch - Either LAZY or FETCH, defaults to LAZY. This definition relies on a bunch of mapping defaults with regards to the naming of the join-column/foreign key. The -explictly defined mapping includes a `` tag nested inside the many-to-one association tag: +explicitly defined mapping includes a `` tag nested inside the many-to-one association tag: [xml] @@ -387,7 +387,7 @@ uni-directional one-to-many association, which means this association only ever Required attributes: -* field - Name of the property/field on the entitys PHP class. +* field - Name of the property/field on the entity's PHP class. * target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. * mapped-by - Name of the field on the owning side (here Phonenumber entity) that contains the owning side association. @@ -407,7 +407,7 @@ you can omit many definitions and rely on their implicit values. Required attributes: -* field - Name of the property/field on the entitys PHP class. +* field - Name of the property/field on the entity's PHP class. * target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. Optional attributes: @@ -439,7 +439,7 @@ the table name of the many-to-many join-table. +++ Cascade Element -Doctrine allows cascading of several UnitOfWork operations to related entities. You can specifiy the cascade +Doctrine allows cascading of several UnitOfWork operations to related entities. You can specify the cascade operations in the `` element inside any of the association mapping tags. [xml] @@ -451,7 +451,7 @@ operations in the `` element inside any of the association mapping ta -Besides `` the following operations can be specifed by their respective tags: +Besides `` the following operations can be specified by their respective tags: * `` * `` From 9c63363ccc2752807ae6f4a229ea35ba60ac75b8 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 11 Oct 2010 20:36:52 +0200 Subject: [PATCH 121/430] Add details on the negative performance impact of STI and CTI in many-to-one or one-to-one scenarios --- manual/en/inheritance-mapping.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/manual/en/inheritance-mapping.txt b/manual/en/inheritance-mapping.txt index b6a4b49ef..255cd435b 100644 --- a/manual/en/inheritance-mapping.txt +++ b/manual/en/inheritance-mapping.txt @@ -51,6 +51,7 @@ The DDL for the corresponding database schema would look something like this (th As you can see from this DDL snippet, there is only a single table for the entity subclass. All the mappings from the mapped superclass were inherited to the subclass as if they had been defined on that class directly. + ++ Single Table Inheritance [Single Table Inheritance](http://martinfowler.com/eaaCatalog/singleTableInheritance.html) is an inheritance mapping strategy where all classes of a hierarchy are mapped to a single database table. In order to distinguish which row represents which type in the hierarchy a so-called discriminator column is used. @@ -93,6 +94,7 @@ This mapping approach works well when the type hierarchy is fairly simple and st This strategy is very efficient for querying across all types in the hierarchy or for specific types. No table joins are required, only a WHERE clause listing the type identifiers. In particular, relationships involving types that employ this mapping strategy are very performant. +There is a general performance consideration with Single Table Inheritance: If you use a STI entity as a many-to-one or one-to-one entity you should never use one of the classes at the upper levels of the inheritance hierachy as "targetEntity", only those that have no subclasses. Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly. ++ Class Table Inheritance @@ -138,4 +140,6 @@ Introducing a new type to the hierarchy, at any level, simply involves interject +++ Performance impact -This strategy inherently requires multiple JOIN operations to perform just about any query which can have a negative impact on performance, especially with large tables and/or large hierarchies. When partial objects are allowed, either globally or on the specific query, then querying for any type will not cause the tables of subtypes to be OUTER JOINed which can increase performance but the resulting partial objects will not fully load themselves on access of any subtype fields, so accessing fields of subtypes after such a query is not safe. \ No newline at end of file +This strategy inherently requires multiple JOIN operations to perform just about any query which can have a negative impact on performance, especially with large tables and/or large hierarchies. When partial objects are allowed, either globally or on the specific query, then querying for any type will not cause the tables of subtypes to be OUTER JOINed which can increase performance but the resulting partial objects will not fully load themselves on access of any subtype fields, so accessing fields of subtypes after such a query is not safe. + +There is a general performance consideration with Class Table Inheritance: If you use a CTI entity as a many-to-one or one-to-one entity you should never use one of the classes at the upper levels of the inheritance hierachy as "targetEntity", only those that have no subclasses. Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly. \ No newline at end of file From 3182f150c1c989eb12dedc681e93c500719d92ec Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 29 Oct 2010 13:30:02 +0200 Subject: [PATCH 122/430] Fix a little render glitch --- manual/en/working-with-associations.txt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/manual/en/working-with-associations.txt b/manual/en/working-with-associations.txt index fe4786df2..af322bdd1 100644 --- a/manual/en/working-with-associations.txt +++ b/manual/en/working-with-associations.txt @@ -224,15 +224,13 @@ that has O(n) complexity using `array_search`, where n is the size of the map. > for write operations that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. > This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. -> **NOTE** -> -> You can also clear the contents of a whole collection using the `Collections::clear()` method. You -> should be aware that using this method can lead to a straight and optimized database delete or update call -> during the flush operation that is not aware of entities that have been re-added to the collection. -> -> Say you clear a collection of tags by calling `$post->getTags()->clear();` and then call -> `$post->getTags()->add($tag)`. This will not recognize tag being already added before and issue -> two database calls. +You can also clear the contents of a whole collection using the `Collections::clear()` method. You +should be aware that using this method can lead to a straight and optimized database delete or update call +during the flush operation that is not aware of entities that have been re-added to the collection. + +Say you clear a collection of tags by calling `$post->getTags()->clear();` and then call +`$post->getTags()->add($tag)`. This will not recognize tag being already added before and issue +two database calls. ++ Association Management Methods From 833a4a93191597017f43b0924ea07226d71efca1 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 29 Oct 2010 14:12:29 +0200 Subject: [PATCH 123/430] Clarify not to use prefix blacklash in targetEntity --- manual/en/annotations-reference.txt | 8 ++++---- manual/en/association-mapping.txt | 8 +++++++- manual/en/xml-mapping.txt | 10 +++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/manual/en/annotations-reference.txt b/manual/en/annotations-reference.txt index 37b035c0e..7b162a77c 100644 --- a/manual/en/annotations-reference.txt +++ b/manual/en/annotations-reference.txt @@ -339,7 +339,7 @@ Defines that the annotated instance variable holds a reference that describes a Required attributes: -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! Optional attributes: @@ -364,7 +364,7 @@ and names of the two related entities. Required attributes: -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! Optional attributes: @@ -418,7 +418,7 @@ apply here too. Required attributes: -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! Optional attributes: @@ -442,7 +442,7 @@ Example: Required attributes: -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. +* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! Optional attributes: diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index 52ba3200f..7e35e4566 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -161,7 +161,7 @@ Now the following code will be working even if the Entity hasn't been associated $user = new User(); $user->getGroups()->add($group); -++ Association Runtime vs Development Validation +++ Runtime vs Development Mapping Validation For performance reasons Doctrine 2 has to skip some of the necessary validation of association mappings. You have to execute this validation in your development workflow to verify the associations are correctly @@ -185,6 +185,12 @@ Or you can trigger the validation manually: If the mapping is invalid the errors array contains a positive number of elements with error messages. +> **NOTE** +> +> One common error is to use a backlash in front of the fully-qualified class-name. Whenever a FQCN is represented +> inside a string (such as in your mapping definitions) you have to drop the prefix backslash. PHP does this with +> `get_class()` or Reflection methods for backwards compatibility reasons. + ++ One-To-One, Unidirectional A unidirectional one-to-one association is very common. Here is an example of a `Product` that has one `Shipping` object associated to it. The `Shipping` side does not reference back to the `Product` so it is unidirectional. diff --git a/manual/en/xml-mapping.txt b/manual/en/xml-mapping.txt index 0ebdd863f..086484406 100644 --- a/manual/en/xml-mapping.txt +++ b/manual/en/xml-mapping.txt @@ -305,7 +305,7 @@ For the inverse side the mapping is as simple as: Required attributes for inverse One-To-One: * field - Name of the property/field on the entity's PHP class. -* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. +* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! * mapped-by - Name of the field on the owning side (here Address entity) that contains the owning side association. For the owning side this mapping would look like: @@ -318,7 +318,7 @@ For the owning side this mapping would look like: Required attributes for owning One-to-One: * field - Name of the property/field on the entity's PHP class. -* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. +* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! Optional attributes for owning One-to-One: @@ -353,7 +353,7 @@ compared to the one-to-one case. The minimal mapping for this association looks Required attributes: * field - Name of the property/field on the entity's PHP class. -* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. +* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! Optional attributes: @@ -388,7 +388,7 @@ uni-directional one-to-many association, which means this association only ever Required attributes: * field - Name of the property/field on the entity's PHP class. -* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. +* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! * mapped-by - Name of the field on the owning side (here Phonenumber entity) that contains the owning side association. Optional attributes: @@ -408,7 +408,7 @@ you can omit many definitions and rely on their implicit values. Required attributes: * field - Name of the property/field on the entity's PHP class. -* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. +* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! Optional attributes: From 61640ef7ce076955e07fae0c23b7db0fe64b79d0 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 30 Oct 2010 12:08:28 +0200 Subject: [PATCH 124/430] Add Note that this is not a holy-grail but may need adjustments for other versions --- cookbook/en/integrating-with-codeigniter.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cookbook/en/integrating-with-codeigniter.txt b/cookbook/en/integrating-with-codeigniter.txt index 23165176a..e058ec6b9 100644 --- a/cookbook/en/integrating-with-codeigniter.txt +++ b/cookbook/en/integrating-with-codeigniter.txt @@ -1,5 +1,9 @@ This is recipe for using Doctrine 2 in your [CodeIgniter](http://www.codeigniter.com) framework. +> **NOTE** +> +> This might not work for all CodeIgniter versions and may require slight adjustments. + Here is how to set it up: Make a CodeIgniter library that is both a wrapper and a bootstrap for Doctrine 2. From 05f5ae151917a1fc3ac855499984764b9c97482d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 30 Oct 2010 18:16:13 +0200 Subject: [PATCH 125/430] DDC-732 - Document sql schema requirements for inheritance strategies --- manual/en/inheritance-mapping.txt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/manual/en/inheritance-mapping.txt b/manual/en/inheritance-mapping.txt index b7639c3c9..48c9d91a2 100644 --- a/manual/en/inheritance-mapping.txt +++ b/manual/en/inheritance-mapping.txt @@ -96,6 +96,13 @@ This strategy is very efficient for querying across all types in the hierarchy o There is a general performance consideration with Single Table Inheritance: If you use a STI entity as a many-to-one or one-to-one entity you should never use one of the classes at the upper levels of the inheritance hierachy as "targetEntity", only those that have no subclasses. Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly. ++++ SQL Schema considerations + +For Single-Table-Inheritance to work in scenarios where you are using either a legacy database schema or a +self-written database schema you have to make sure that all columns that are not in the root entity but in any +of the different sub-entities has to allows null values. Columns that have NOT NULL constraints have to be on the +root entity of the single-table inheritance hierarchy. + ++ Class Table Inheritance [Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html) is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. @@ -142,4 +149,11 @@ Introducing a new type to the hierarchy, at any level, simply involves interject This strategy inherently requires multiple JOIN operations to perform just about any query which can have a negative impact on performance, especially with large tables and/or large hierarchies. When partial objects are allowed, either globally or on the specific query, then querying for any type will not cause the tables of subtypes to be OUTER JOINed which can increase performance but the resulting partial objects will not fully load themselves on access of any subtype fields, so accessing fields of subtypes after such a query is not safe. -There is a general performance consideration with Class Table Inheritance: If you use a CTI entity as a many-to-one or one-to-one entity you should never use one of the classes at the upper levels of the inheritance hierachy as "targetEntity", only those that have no subclasses. Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly. \ No newline at end of file +There is a general performance consideration with Class Table Inheritance: If you use a CTI entity as a many-to-one or one-to-one entity you should never use one of the classes at the upper levels of the inheritance hierachy as "targetEntity", only those that have no subclasses. Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly. + ++++ SQL Schema considerations + +For each entity in the Class-Table Inheritance hierarchy all the mapped fields have to be columns on the +table of this entity. Additionally each child table has to have an id column that matches the id column +definition on the root table (except for any sequence or auto-increment details). Furthermore each child table has +to have a foreign key pointing from the id column to the root table id column and cascading on delete. From 468c878c9255336cfd51c55180d74da318d8a755 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 31 Oct 2010 07:34:43 +0100 Subject: [PATCH 126/430] Add hint about multiple directories in Metadata drivers to configuration section --- manual/en/configuration.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index 8b23bbfe2..73f9ec783 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -170,7 +170,9 @@ The annotation driver can be configured with a factory method on the `Doctrine\O $config->setMetadataDriverImpl($driverImpl); The path information to the entities is required for the annotation driver, because otherwise -mass-operations on all entities through the console could not work correctly. +mass-operations on all entities through the console could not work correctly. All of metadata +drivers accept either a single directory as a string or an array of directories. With this feature a +single driver can support multiple directories of Entities. +++ Metadata Cache (***RECOMMENDED***) From 6985b671d86166e2ca8c9c3cfc966f245a60f6a7 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 31 Oct 2010 15:13:51 +0100 Subject: [PATCH 127/430] Add note about float type --- manual/en/basic-mapping.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 39120851f..09dd391b6 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -73,6 +73,7 @@ For example, the Doctrine Mapping Type `string` defines the mapping from a PHP s * `text`: Type that maps an SQL CLOB to a PHP string. * `object`: Type that maps a SQL CLOB to a PHP object using `serialize()` and `unserialize()` * `array`: Type that maps a SQL CLOB to a PHP object using `serialize()` and `unserialize()` +* `float`: Type that maps a SQL Float (Double Precision) to a PHP double. *IMPORTANT*: Works only with locale settings that use decimal points as separator. > **NOTE** > Doctrine Mapping Types are NOT SQL types and NOT PHP types! They are mapping types From 1bfeaf3eafe8af3f621c88bbee541093e4364df5 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 1 Nov 2010 21:16:12 +0100 Subject: [PATCH 128/430] Initial conversion from Markdown to ReST - Finalized Cookbook --- convert.sh | 14 + cookbook/en.rst | 34 + cookbook/en.txt | 22 +- cookbook/en/Makefile | 89 + cookbook/en/aggregate-fields.rst | 374 +++++ cookbook/en/aggregate-fields.txt | 318 ---- cookbook/en/conf.py | 194 +++ cookbook/en/dql-custom-walkers.rst | 216 +++ cookbook/en/dql-custom-walkers.txt | 174 -- cookbook/en/dql-user-defined-functions.rst | 239 +++ cookbook/en/dql-user-defined-functions.txt | 198 --- cookbook/en/getting-started-xml-edition.rst | 1037 ++++++++++++ cookbook/en/getting-started-xml-edition.txt | 784 --------- ...enting-arrayaccess-for-domain-objects.rst} | 77 +- ...ting-the-notify-changetracking-policy.rst} | 42 +- cookbook/en/implementing-wakeup-or-clone.rst | 76 + cookbook/en/implementing-wakeup-or-clone.txt | 63 - cookbook/en/index.rst | 32 + ...r.txt => integrating-with-codeigniter.rst} | 104 +- cookbook/en/make.bat | 113 ++ cookbook/en/sql-table-prefixes.rst | 73 + cookbook/en/sql-table-prefixes.txt | 51 - ...txt => strategy-cookbook-introduction.rst} | 144 +- cookbook/en/validation-of-entities.rst | 141 ++ cookbook/en/validation-of-entities.txt | 114 -- generate-docs.sh | 2 + manual/en.rst | 79 + manual/en.txt | 52 +- manual/en/annotations-reference.rst | 769 +++++++++ manual/en/annotations-reference.txt | 104 +- manual/en/architecture.rst | 125 ++ manual/en/architecture.txt | 14 +- manual/en/association-mapping.rst | 969 +++++++++++ manual/en/association-mapping.txt | 74 +- manual/en/basic-mapping.rst | 483 ++++++ manual/en/basic-mapping.txt | 46 +- manual/en/batch-processing.rst | 166 ++ manual/en/batch-processing.txt | 28 +- manual/en/best-practices.rst | 124 ++ manual/en/best-practices.txt | 22 +- manual/en/caching.rst | 479 ++++++ manual/en/caching.txt | 96 +- manual/en/change-tracking-policies.rst | 151 ++ manual/en/change-tracking-policies.txt | 14 +- manual/en/configuration.rst | 465 ++++++ manual/en/configuration.txt | 68 +- manual/en/dql-doctrine-query-language.rst | 1488 +++++++++++++++++ manual/en/dql-doctrine-query-language.txt | 212 +-- manual/en/events.rst | 603 +++++++ manual/en/events.txt | 58 +- manual/en/improving-performance.rst | 38 + manual/en/improving-performance.txt | 8 +- manual/en/inheritance-mapping.rst | 255 +++ manual/en/inheritance-mapping.txt | 24 +- manual/en/introduction.rst | 401 +++++ manual/en/introduction.txt | 38 +- manual/en/limitations-and-known-issues.rst | 267 +++ manual/en/limitations-and-known-issues.txt | 26 +- manual/en/metadata-drivers.rst | 187 +++ manual/en/metadata-drivers.txt | 22 +- manual/en/native-sql.rst | 353 ++++ manual/en/native-sql.txt | 38 +- manual/en/partial-objects.rst | 65 + manual/en/partial-objects.txt | 6 +- manual/en/php-mapping.rst | 248 +++ manual/en/php-mapping.txt | 42 +- manual/en/query-builder.rst | 490 ++++++ manual/en/query-builder.txt | 42 +- manual/en/tools.rst | 376 +++++ manual/en/tools.txt | 44 +- manual/en/transactions-and-concurrency.rst | 351 ++++ manual/en/transactions-and-concurrency.txt | 34 +- manual/en/working-with-associations.rst | 521 ++++++ manual/en/working-with-associations.txt | 34 +- manual/en/working-with-objects.rst | 765 +++++++++ manual/en/working-with-objects.txt | 80 +- manual/en/xml-mapping.rst | 716 ++++++++ manual/en/xml-mapping.txt | 36 +- manual/en/yaml-mapping.rst | 90 + manual/en/yaml-mapping.txt | 6 +- 80 files changed, 14545 insertions(+), 2472 deletions(-) create mode 100755 convert.sh create mode 100644 cookbook/en.rst create mode 100644 cookbook/en/Makefile create mode 100644 cookbook/en/aggregate-fields.rst delete mode 100644 cookbook/en/aggregate-fields.txt create mode 100644 cookbook/en/conf.py create mode 100644 cookbook/en/dql-custom-walkers.rst delete mode 100644 cookbook/en/dql-custom-walkers.txt create mode 100644 cookbook/en/dql-user-defined-functions.rst delete mode 100644 cookbook/en/dql-user-defined-functions.txt create mode 100644 cookbook/en/getting-started-xml-edition.rst delete mode 100644 cookbook/en/getting-started-xml-edition.txt rename cookbook/en/{implementing-arrayaccess-for-domain-objects.txt => implementing-arrayaccess-for-domain-objects.rst} (60%) rename cookbook/en/{implementing-the-notify-changetracking-policy.txt => implementing-the-notify-changetracking-policy.rst} (55%) create mode 100644 cookbook/en/implementing-wakeup-or-clone.rst delete mode 100644 cookbook/en/implementing-wakeup-or-clone.txt create mode 100644 cookbook/en/index.rst rename cookbook/en/{integrating-with-codeigniter.txt => integrating-with-codeigniter.rst} (56%) create mode 100644 cookbook/en/make.bat create mode 100644 cookbook/en/sql-table-prefixes.rst delete mode 100644 cookbook/en/sql-table-prefixes.txt rename cookbook/en/{strategy-cookbook-introduction.txt => strategy-cookbook-introduction.rst} (64%) create mode 100644 cookbook/en/validation-of-entities.rst delete mode 100644 cookbook/en/validation-of-entities.txt create mode 100755 generate-docs.sh create mode 100644 manual/en.rst create mode 100644 manual/en/annotations-reference.rst create mode 100644 manual/en/architecture.rst create mode 100644 manual/en/association-mapping.rst create mode 100644 manual/en/basic-mapping.rst create mode 100644 manual/en/batch-processing.rst create mode 100644 manual/en/best-practices.rst create mode 100644 manual/en/caching.rst create mode 100644 manual/en/change-tracking-policies.rst create mode 100644 manual/en/configuration.rst create mode 100644 manual/en/dql-doctrine-query-language.rst create mode 100644 manual/en/events.rst create mode 100644 manual/en/improving-performance.rst create mode 100644 manual/en/inheritance-mapping.rst create mode 100644 manual/en/introduction.rst create mode 100644 manual/en/limitations-and-known-issues.rst create mode 100644 manual/en/metadata-drivers.rst create mode 100644 manual/en/native-sql.rst create mode 100644 manual/en/partial-objects.rst create mode 100644 manual/en/php-mapping.rst create mode 100644 manual/en/query-builder.rst create mode 100644 manual/en/tools.rst create mode 100644 manual/en/transactions-and-concurrency.rst create mode 100644 manual/en/working-with-associations.rst create mode 100644 manual/en/working-with-objects.rst create mode 100644 manual/en/xml-mapping.rst create mode 100644 manual/en/yaml-mapping.rst diff --git a/convert.sh b/convert.sh new file mode 100755 index 000000000..27e4a5b85 --- /dev/null +++ b/convert.sh @@ -0,0 +1,14 @@ +#!/bin/bash +FILES=`find -iname *.txt -print` +for FILE in $FILES +do + # replace the + to # chars + sed -i -r 's/^([+]{4})\s/#### /' $FILE + sed -i -r 's/^([+]{3})\s/### /' $FILE + sed -i -r 's/^([+]{2})\s/## /' $FILE + sed -i -r 's/^([+]{1})\s/# /' $FILE + sed -i -r 's/(\[php\])/ ${FILE%.txt}.rst +done \ No newline at end of file diff --git a/cookbook/en.rst b/cookbook/en.rst new file mode 100644 index 000000000..2c5786b1d --- /dev/null +++ b/cookbook/en.rst @@ -0,0 +1,34 @@ +Getting Started XML-Edition +=========================== + +Implementing ArrayAccess for domain objects +=========================================== + +Implementing the NOTIFY change-tracking policy +============================================== + +Validation of Entities +====================== + +Implementing wakeup or clone +============================ + +Integrating with CodeIgniter +============================ + +DQL Custom Walkers +================== + +DQL User Defined Functions +========================== + +SQL Table Prefixes +================== + +Strategy Cookbook Introduction +============================== + +Aggregate Fields +================ + + diff --git a/cookbook/en.txt b/cookbook/en.txt index e7ee1629b..6a77ba99d 100644 --- a/cookbook/en.txt +++ b/cookbook/en.txt @@ -1,11 +1,11 @@ -+ Getting Started XML-Edition -+ Implementing ArrayAccess for domain objects -+ Implementing the NOTIFY change-tracking policy -+ Validation of Entities -+ Implementing wakeup or clone -+ Integrating with CodeIgniter -+ DQL Custom Walkers -+ DQL User Defined Functions -+ SQL Table Prefixes -+ Strategy Cookbook Introduction -+ Aggregate Fields \ No newline at end of file +# Getting Started XML-Edition +# Implementing ArrayAccess for domain objects +# Implementing the NOTIFY change-tracking policy +# Validation of Entities +# Implementing wakeup or clone +# Integrating with CodeIgniter +# DQL Custom Walkers +# DQL User Defined Functions +# SQL Table Prefixes +# Strategy Cookbook Introduction +# Aggregate Fields \ No newline at end of file diff --git a/cookbook/en/Makefile b/cookbook/en/Makefile new file mode 100644 index 000000000..987598317 --- /dev/null +++ b/cookbook/en/Makefile @@ -0,0 +1,89 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORMCookbook.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORMCookbook.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/cookbook/en/aggregate-fields.rst b/cookbook/en/aggregate-fields.rst new file mode 100644 index 000000000..ef91a4d62 --- /dev/null +++ b/cookbook/en/aggregate-fields.rst @@ -0,0 +1,374 @@ +Aggregate Fields +================ + +You will often come across the requirement to display aggregate +values of data that can be computed by using the MIN, MAX, COUNT or +SUM SQL functions. For any ORM this is a tricky issue +traditionally. Doctrine 2 offers several ways to get access to +these values and this article will describe all of them from +different perspectives. + +You will see that aggregate fields can become very explicit +features in your domain model and how this potentially complex +business rules can be easily tested. + +An example model +---------------- + +Say you want to model a bank account and all their entries. Entries +into the account can either be of positive or negative money +values. Each account has a credit limit and the account is never +allowed to have a balance below that value. + +For simplicity we live in a world were money is composed of +integers only. Also we omit the receiver/sender name, stated reason +for transfer and the execution date. These all would have to be +added on the ``Entry`` object. + +Our entities look like: + +:: + + no = $no; + $this->maxCredit = $maxCredit; + $this->entries = new \Doctrine\Common\Collections\ArrayCollection(); + } + } + + /** + * @Entity + */ + class Entry + { + /** @Id @GeneratedValue @Column(type="integer") */ + private $id; + + /** + * @ManyToOne(targetEntity="Account", inversedBy="entries") + */ + private $account; + + /** + * @Column(type="integer") + */ + private $amount; + + public function __construct($account, $amount) + { + $this->account = $account; + $this->amount = $amount; + // more stuff here, from/to whom, stated reason, execution date and such + } + + public function getAmount() + { + return $this->amount; + } + } + +Using DQL +--------- + +The Doctrine Query Language allows you to select for aggregate +values computed from fields of your Domain Model. You can select +the current balance of your account by calling: + +:: + + createQuery($dql) + ->setParameter(1, $myAccountId) + ->getSingleScalarResult(); + +The ``$em`` variable in this (and forthcoming) example holds the +Doctrine ``EntityManager``. We create a query for the SUM of all +amounts (negative amounts are withdraws) and retrieve them as a +single scalar result, essentially return only the first column of +the first row. + +This approach is simple and powerful, however it has a serious +drawback. We have to execute a specific query for the balance +whenever we need it. + +To implement a powerful domain model we would rather have access to +the balance from our ``Account`` entity during all times (even if +the Account was not persisted in the database before!). + +Also an additional requirement is the max credit per ``Account`` +rule. + +We cannot reliably enforce this rule in our ``Account`` entity with +the DQL retrieval of the balance. There are many different ways to +retrieve accounts. We cannot guarantee that we can execute the +aggregation query for all these use-cases, let alone that a +userland programmer checks this balance against newly added +entries. + +Using your Domain Model +----------------------- + +``Account`` and all the ``Entry`` instances are connected through a +collection, which means we can compute this value at runtime: + +:: + + entries AS $entry) { + $balance += $entry->getAmount(); + } + return $balance; + } + } + +Now we can always call ``Account::getBalance()`` to access the +current account balance. + +To enforce the max credit rule we have to implement the "Aggregate +Root" pattern as described in Eric Evans book on Domain Driven +Design. Described with one sentence, an aggregate root controls the +instance creation, access and manipulation of its children. + +In our case we want to enforce that new entries can only added to +the ``Account`` by using a designated method. The ``Account`` is +the aggregate root of this relation. We can also enforce the +correctness of the bi-directional ``Account`` <-> ``Entry`` +relation with this method: + +:: + + assertAcceptEntryAllowed($amount); + + $e = new Entry($this, $amount); + $this->entries[] = $e; + return $e; + } + } + +Now look at the following test-code for our entities: + +:: + + assertEquals(0, $account->getBalance()); + + $account->addEntry(500); + $this->assertEquals(500, $account->getBalance()); + + $account->addEntry(-700); + $this->assertEquals(-200, $account->getBalance()); + } + + public function testExceedMaxLimit() + { + $account = new Account("123456", $maxCredit = 200); + + $this->setExpectedException("Exception"); + $account->addEntry(-1000); + } + } + +To enforce our rule we can now implement the assertion in +``Account::addEntry``: + +:: + + getBalance() + $amount; + $allowedMinimalBalance = ($this->maxCredit * -1); + if ($futureBalance < $allowedMinimalBalance) { + throw new Exception("Credit Limit exceeded, entry is not allowed!"); + } + } + } + +We haven't talked to the entity manager for persistence of our +account example before. You can call +``EntityManager::persist($account)`` and then +``EntityManager::flush()`` at any point to save the account to the +database. All the nested ``Entry`` objects are automatically +flushed to the database also. + +:: + + addEntry(500); + $account->addEntry(-200); + $em->persist($account); + $em->flush(); + +The current implementation has a considerable drawback. To get the +balance, we have to initialize the complete ``Account::$entries`` +collection, possibly a very large one. This can considerably hurt +the performance of your application. + +Using an Aggregate Field +------------------------ + +To overcome the previously mentioned issue (initializing the whole +entries collection) we want to add an aggregate field called +"balance" on the Account and adjust the code in +``Account::getBalance()`` and ``Account:addEntry()``: + +:: + + balance; + } + + public function addEntry($amount) + { + $this->assertAcceptEntryAllowed($amount); + + $e = new Entry($this, $amount); + $this->entries[] = $e; + $this->balance += $amount; + return $e; + } + } + +This is a very simple change, but all the tests still pass. Our +account entities return the correct balance. Now calling the +``Account::getBalance()`` method will not occur the overhead of +loading all entries anymore. Adding a new Entry to the +``Account::$entities`` will also not initialize the collection +internally. + +Adding a new entry is therefore very performant and explicitly +hooked into the domain model. It will only update the account with +the current balance and insert the new entry into the database. + +Tackling Race Conditions with Aggregate Fields +---------------------------------------------- + +Whenever you denormalize your database schema race-conditions can +potentially lead to inconsistent state. See this example: + +:: + + find('Bank\Entities\Account', $accId); + + // request 2 account + $account2 = $em->find('Bank\Entities\Account', $accId); + + $account1->addEntry(-200); + $account2->addEntry(-200); + + // now request 1 and 2 both flush the changes. + +The aggregate field ``Account::$balance`` is now -200, however the +SUM over all entries amounts yields -400. A violation of our max +credit rule. + +You can use both optimistic or pessimistic locking to save-guard +your aggregate fields against this kind of race-conditions. Reading +Eric Evans DDD carefully he mentions that the "Aggregate Root" +(Account in our example) needs a locking mechanism. + +Optimistic locking is as easy as adding a version column: + +:: + + find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ); + +Keeping Updates and Deletes in Sync +----------------------------------- + +The example shown in this article does not allow changes to the +value in ``Entry``, which considerably simplifies the effort to +keep ``Account::$balance`` in sync. If your use-case allows fields +to be updated or related entities to be removed you have to +encapsulate this logic in your "Aggregate Root" entity and adjust +the aggregate field accordingly. + +Conclusion +---------- + +This article described how to obtain aggregate values using DQL or +your domain model. It showed how you can easily add an aggregate +field that offers serious performance benefits over iterating all +the related objects that make up an aggregate value. Finally I +showed how you can ensure that your aggregate fields do not get out +of sync due to race-conditions and concurrent access. + + diff --git a/cookbook/en/aggregate-fields.txt b/cookbook/en/aggregate-fields.txt deleted file mode 100644 index 18e445034..000000000 --- a/cookbook/en/aggregate-fields.txt +++ /dev/null @@ -1,318 +0,0 @@ -# Aggregate Fields - -You will often come across the requirement to display aggregate values of data that -can be computed by using the MIN, MAX, COUNT or SUM SQL functions. For any ORM this is a tricky issue traditionally. Doctrine 2 offers several ways to get access to these values and this article will describe all of them from different perspectives. - -You will see that aggregate fields can become very explicit -features in your domain model and how this potentially complex business rules -can be easily tested. - -## An example model - -Say you want to model a bank account and all their entries. Entries -into the account can either be of positive or negative money values. -Each account has a credit limit and the account is never allowed -to have a balance below that value. - -For simplicity we live in a world were money is composed of integers -only. Also we omit the receiver/sender name, stated reason for transfer -and the execution date. These all would have to be added on the `Entry` -object. - -Our entities look like: - - [php] - namespace Bank\Entities; - - /** - * @Entity - */ - class Account - { - /** @Id @GeneratedValue @Column(type="integer") */ - private $id; - - /** @Column(type="string", unique=true) */ - private $no; - - /** - * @OneToMany(targetEntity="Entry", mappedBy="entries", cascade={"persist"}) - */ - private $entries; - - /** - * @Column(type="integer") - */ - private $maxCredit = 0; - - public function __construct($no, $maxCredit = 0) - { - $this->no = $no; - $this->maxCredit = $maxCredit; - $this->entries = new \Doctrine\Common\Collections\ArrayCollection(); - } - } - - /** - * @Entity - */ - class Entry - { - /** @Id @GeneratedValue @Column(type="integer") */ - private $id; - - /** - * @ManyToOne(targetEntity="Account", inversedBy="entries") - */ - private $account; - - /** - * @Column(type="integer") - */ - private $amount; - - public function __construct($account, $amount) - { - $this->account = $account; - $this->amount = $amount; - // more stuff here, from/to whom, stated reason, execution date and such - } - - public function getAmount() - { - return $this->amount; - } - } - -## Using DQL - -The Doctrine Query Language allows you to select for aggregate values computed from -fields of your Domain Model. You can select the current balance of your account by -calling: - - [php] - $dql = "SELECT SUM(e.amount) AS balance FROM Bank\Entities\Entry e " . - "WHERE e.account = ?1"; - $balance = $em->createQuery($dql) - ->setParameter(1, $myAccountId) - ->getSingleScalarResult(); - -The `$em` variable in this (and forthcoming) example holds the Doctrine `EntityManager`. -We create a query for the SUM of all amounts (negative amounts are withdraws) and -retrieve them as a single scalar result, essentially return only the first column -of the first row. - -This approach is simple and powerful, however it has a serious drawback. We have -to execute a specific query for the balance whenever we need it. - -To implement a powerful domain model we would rather have access to the balance from -our `Account` entity during all times (even if the Account was not persisted -in the database before!). - -Also an additional requirement is the max credit per `Account` rule. - -We cannot reliably enforce this rule in our `Account` entity with the DQL retrieval -of the balance. There are many different ways to retrieve accounts. We cannot -guarantee that we can execute the aggregation query for all these use-cases, -let alone that a userland programmer checks this balance against newly added -entries. - -## Using your Domain Model - -`Account` and all the `Entry` instances are connected through a collection, -which means we can compute this value at runtime: - - [php] - class Account - { - // .. previous code - public function getBalance() - { - $balance = 0; - foreach ($this->entries AS $entry) { - $balance += $entry->getAmount(); - } - return $balance; - } - } - -Now we can always call `Account::getBalance()` to access the current account balance. - -To enforce the max credit rule we have to implement the "Aggregate Root" pattern as -described in Eric Evans book on Domain Driven Design. Described with one sentence, -an aggregate root controls the instance creation, access and manipulation of its children. - -In our case we want to enforce that new entries can only added to the `Account` by -using a designated method. The `Account` is the aggregate root of this relation. -We can also enforce the correctness of the bi-directional `Account` <-> `Entry` -relation with this method: - - [php] - class Account - { - public function addEntry($amount) - { - $this->assertAcceptEntryAllowed($amount); - - $e = new Entry($this, $amount); - $this->entries[] = $e; - return $e; - } - } - -Now look at the following test-code for our entities: - - [php] - class AccountTest extends \PHPUnit_Framework_TestCase - { - public function testAddEntry() - { - $account = new Account("123456", $maxCredit = 200); - $this->assertEquals(0, $account->getBalance()); - - $account->addEntry(500); - $this->assertEquals(500, $account->getBalance()); - - $account->addEntry(-700); - $this->assertEquals(-200, $account->getBalance()); - } - - public function testExceedMaxLimit() - { - $account = new Account("123456", $maxCredit = 200); - - $this->setExpectedException("Exception"); - $account->addEntry(-1000); - } - } - -To enforce our rule we can now implement the assertion in `Account::addEntry`: - - [php] - class Account - { - private function assertAcceptEntryAllowed($amount) - { - $futureBalance = $this->getBalance() + $amount; - $allowedMinimalBalance = ($this->maxCredit * -1); - if ($futureBalance < $allowedMinimalBalance) { - throw new Exception("Credit Limit exceeded, entry is not allowed!"); - } - } - } - -We haven't talked to the entity manager for persistence of our account example before. -You can call `EntityManager::persist($account)` and then `EntityManager::flush()` -at any point to save the account to the database. All the nested `Entry` objects -are automatically flushed to the database also. - - [php] - $account = new Account("123456", 200); - $account->addEntry(500); - $account->addEntry(-200); - $em->persist($account); - $em->flush(); - -The current implementation has a considerable drawback. To get the balance, we -have to initialize the complete `Account::$entries` collection, possibly a very -large one. This can considerably hurt the performance of your application. - -## Using an Aggregate Field - -To overcome the previously mentioned issue (initializing the whole entries collection) -we want to add an aggregate field called "balance" on the Account and adjust the -code in `Account::getBalance()` and `Account:addEntry()`: - - [php] - class Account - { - /** - * @Column(type="integer") - */ - private $balance = 0; - - public function getBalance() - { - return $this->balance; - } - - public function addEntry($amount) - { - $this->assertAcceptEntryAllowed($amount); - - $e = new Entry($this, $amount); - $this->entries[] = $e; - $this->balance += $amount; - return $e; - } - } - -This is a very simple change, but all the tests still pass. Our account entities return -the correct balance. Now calling the `Account::getBalance()` method will not occur the -overhead of loading all entries anymore. Adding a new Entry to the `Account::$entities` -will also not initialize the collection internally. - -Adding a new entry is therefore very performant and explicitly hooked into the domain model. -It will only update the account with the current balance and insert the new entry into the database. - -## Tackling Race Conditions with Aggregate Fields - -Whenever you denormalize your database schema race-conditions can potentially lead to -inconsistent state. See this example: - - [php] - // The Account $accId has a balance of 0 and a max credit limit of 200: - // request 1 account - $account1 = $em->find('Bank\Entities\Account', $accId); - - // request 2 account - $account2 = $em->find('Bank\Entities\Account', $accId); - - $account1->addEntry(-200); - $account2->addEntry(-200); - - // now request 1 and 2 both flush the changes. - -The aggregate field `Account::$balance` is now -200, however the SUM over all -entries amounts yields -400. A violation of our max credit rule. - -You can use both optimistic or pessimistic locking to save-guard -your aggregate fields against this kind of race-conditions. Reading Eric Evans -DDD carefully he mentions that the "Aggregate Root" (Account in our example) -needs a locking mechanism. - -Optimistic locking is as easy as adding a version column: - - [php] - class Amount - { - /** @Column(type="integer") @Version */ - private $version; - } - -The previous example would then throw an exception in the face of whatever request -saves the entity last (and would create the inconsistent state). - -Pessimistic locking requires an additional flag set on the `EntityManager::find()` -call, enabling write locking directly in the database using a FOR UPDATE. - - [php] - use Doctrine\DBAL\LockMode; - - $account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ); - -## Keeping Updates and Deletes in Sync - -The example shown in this article does not allow changes to the value in `Entry`, -which considerably simplifies the effort to keep `Account::$balance` in sync. -If your use-case allows fields to be updated or related entities to be removed -you have to encapsulate this logic in your "Aggregate Root" entity and adjust -the aggregate field accordingly. - -## Conclusion - -This article described how to obtain aggregate values using DQL or your domain model. -It showed how you can easily add an aggregate field that offers serious performance -benefits over iterating all the related objects that make up an aggregate value. -Finally I showed how you can ensure that your aggregate fields do not get out -of sync due to race-conditions and concurrent access. \ No newline at end of file diff --git a/cookbook/en/conf.py b/cookbook/en/conf.py new file mode 100644 index 000000000..58e36054e --- /dev/null +++ b/cookbook/en/conf.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +# +# Doctrine 2 ORM Cookbook documentation build configuration file, created by +# sphinx-quickstart on Mon Nov 1 21:13:13 2010. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Doctrine 2 ORM Cookbook' +copyright = u'2010, Doctrine Project Team' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '2.0' +# The full version, including alpha/beta/rc tags. +release = '2.0.0-BETA4' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Doctrine2ORMCookbookdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Doctrine2ORMCookbook.tex', u'Doctrine 2 ORM Cookbook Documentation', + u'Doctrine Project Team', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/cookbook/en/dql-custom-walkers.rst b/cookbook/en/dql-custom-walkers.rst new file mode 100644 index 000000000..c92447dba --- /dev/null +++ b/cookbook/en/dql-custom-walkers.rst @@ -0,0 +1,216 @@ +Extending DQL in Doctrine 2: Custom AST Walkers +=============================================== + +The Doctrine Query Language (DQL) is a proprietary sql-dialect that +substitutes tables and columns for Entity names and their fields. +Using DQL you write a query against the database using your +entities. With the help of the metadata you can write very concise, +compact and powerful queries that are then translated into SQL by +the Doctrine ORM. + +In Doctrine 1 the DQL language was not implemented using a real +parser. This made modifications of the DQL by the user impossible. +Doctrine 2 in contrast has a real parser for the DQL language, +which transforms the DQL statement into an +`Abstract Syntax Tree `_ +and generates the appropriate SQL statement for it. Since this +process is deterministic Doctrine heavily caches the SQL that is +generated from any given DQL query, which reduces the performance +overhead of the parsing process to zero. + +You can modify the Abstract syntax tree by hooking into DQL parsing +process by adding a Custom Tree Walker. A walker is an interface +that walks each node of the Abstract syntax tree, thereby +generating the SQL statement. + +There are two types of custom tree walkers that you can hook into +the DQL parser: + + +- An output walker. This one actually generates the SQL, and there + is only ever one of them. We implemented the default SqlWalker + implementation for it. +- A tree walker. There can be many tree walkers, they cannot + generate the sql, however they can modify the AST before its + rendered to sql. + +Now this is all awfully technical, so let me come to some use-cases +fast to keep you motivated. Using walker implementation you can for +example: + + +- Modify the AST to generate a Count Query to be used with a + paginator for any given DQL query. +- Modify the Output Walker to generate vendor-specific SQL + (instead of ANSI). +- Modify the AST to add additional where clauses for specific + entities (example ACL, country-specific content...) +- Modify the Output walker to pretty print the SQL for debugging + purposes. + +In this cookbook-entry I will show examples on the first two +points. There are probably much more use-cases. + +Generic count query for pagination +---------------------------------- + +Say you have a blog and posts all with one category and one author. +A query for the front-page or any archive page might look something +like: + +.. code-clock:: sql + + SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... + +Now in this query the blog post is the root entity, meaning its the +one that is hydrated directly from the query and returned as an +array of blog posts. In contrast the comment and author are loaded +for deeper use in the object tree. + +A pagination for this query would want to approximate the number of +posts that match the WHERE clause of this query to be able to +predict the number of pages to show to the user. A draft of the DQL +query for pagination would look like: + +.. code-clock:: sql + + SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... + +Now you could go and write each of these queries by hand, or you +can use a tree walker to modify the AST for you. Lets see how the +API would look for this use-case: + +:: + + createQuery($dql); + $query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20); + + $totalResults = Paginate::count($query); + $results = $query->getResult(); + +The ``Paginate::count(Query $query)`` looks like: + +:: + + setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker')); + $countQuery->setFirstResult(null)->setMaxResults(null); + + return $countQuery->getSingleScalarResult(); + } + } + +It clones the query, resets the limit clause first and max results +and registers the ``CountSqlWalker`` customer tree walker which +will modify the AST to execute a count query. The walkers +implementation is: + +:: + + _getQueryComponents() AS $dqlAlias => $qComp) { + if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { + $parent = $qComp; + $parentName = $dqlAlias; + break; + } + } + + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, array( + $parent['metadata']->getSingleIdentifierFieldName()) + ); + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + + $AST->selectClause->selectExpressions = array( + new SelectExpression( + new AggregateExpression('count', $pathExpression, true), null + ) + ); + } + } + +This will delete any given select expressions and replace them with +a distinct count query for the root entities primary key. This will +only work if your entity has only one identifier field (composite +keys won't work). + +Modify the Output Walker to generate Vendor specific SQL +-------------------------------------------------------- + +Most RMDBS have vendor-specific features for optimizing select +query execution plans. You can write your own output walker to +introduce certain keywords using the Query Hint API. A query hint +can be set via ``Query::setHint($name, $value)`` as shown in the +previous example with the ``HINT_CUSTOM_TREE_WALKERS`` query hint. + +We will implement a custom Output Walker that allows to specify the +SQL\_NO\_CACHE query hint. + +:: + + createQuery($dql); + $query->setQueryHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker'); + $query->setQueryHint("mysqlWalker.sqlNoCache", true); + $results = $query->getResult(); + +Our ``MysqlWalker`` will extend the default ``SqlWalker``. We will +modify the generation of the SELECT clause, adding the +SQL\_NO\_CACHE on those queries that need it: + +:: + + getQuery()->getHint('mysqlWalker.sqlNoCache') === true) { + if ($selectClause->isDistinct) { + $sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql); + } else { + $sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql); + } + } + + return $sql; + } + } + +Writing extensions to the Output Walker requires a very deep +understanding of the DQL Parser and Walkers, but may offer your +huge benefits with using vendor specific features. This would still +allow you write DQL queries instead of NativeQueries to make use of +vendor specific features. + + diff --git a/cookbook/en/dql-custom-walkers.txt b/cookbook/en/dql-custom-walkers.txt deleted file mode 100644 index 2f314cd78..000000000 --- a/cookbook/en/dql-custom-walkers.txt +++ /dev/null @@ -1,174 +0,0 @@ -# Extending DQL in Doctrine 2: Custom AST Walkers - -The Doctrine Query Language (DQL) is a proprietary sql-dialect that substitutes -tables and columns for Entity names and their fields. Using DQL you write a query -against the database using your entities. With the help of the metadata you -can write very concise, compact and powerful queries that are then translated -into SQL by the Doctrine ORM. - -In Doctrine 1 the DQL language was not implemented using a real parser. This -made modifications of the DQL by the user impossible. Doctrine 2 in contrast -has a real parser for the DQL language, which transforms the DQL statement -into an [Abstract Syntax Tree](http://en.wikipedia.org/wiki/Abstract_syntax_tree) -and generates the appropriate SQL statement for it. Since this process is -deterministic Doctrine heavily caches the SQL that is generated from any given DQL query, -which reduces the performance overhead of the parsing process to zero. - -You can modify the Abstract syntax tree by hooking into DQL parsing process -by adding a Custom Tree Walker. A walker is an interface that walks each -node of the Abstract syntax tree, thereby generating the SQL statement. - -There are two types of custom tree walkers that you can hook into the DQL parser: - -- An output walker. This one actually generates the SQL, and there is only ever one of them. We implemented the default SqlWalker implementation for it. -- A tree walker. There can be many tree walkers, they cannot generate the sql, however they can modify the AST before its rendered to sql. - -Now this is all awfully technical, so let me come to some use-cases fast -to keep you motivated. Using walker implementation you can for example: - -* Modify the AST to generate a Count Query to be used with a paginator for any given DQL query. -* Modify the Output Walker to generate vendor-specific SQL (instead of ANSI). -* Modify the AST to add additional where clauses for specific entities (example ACL, country-specific content...) -* Modify the Output walker to pretty print the SQL for debugging purposes. - -In this cookbook-entry I will show examples on the first two points. There -are probably much more use-cases. - -## Generic count query for pagination - -Say you have a blog and posts all with one category and one author. A query -for the front-page or any archive page might look something like: - - [sql] - SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... - -Now in this query the blog post is the root entity, meaning its the one that -is hydrated directly from the query and returned as an array of blog posts. -In contrast the comment and author are loaded for deeper use in the object tree. - -A pagination for this query would want to approximate the number of posts that -match the WHERE clause of this query to be able to predict the number of pages -to show to the user. A draft of the DQL query for pagination would look like: - - [sql] - SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... - -Now you could go and write each of these queries by hand, or you can use a tree -walker to modify the AST for you. Lets see how the API would look for this use-case: - - [php] - $pageNum = 1; - $query = $em->createQuery($dql); - $query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20); - - $totalResults = Paginate::count($query); - $results = $query->getResult(); - -The `Paginate::count(Query $query)` looks like: - - [php] - class Paginate - { - static public function count(Query $query) - { - /* @var $countQuery Query */ - $countQuery = clone $query; - - $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker')); - $countQuery->setFirstResult(null)->setMaxResults(null); - - return $countQuery->getSingleScalarResult(); - } - } - -It clones the query, resets the limit clause first and max results and registers the `CountSqlWalker` -customer tree walker which will modify the AST to execute a count query. The walkers -implementation is: - - [php] - class CountSqlWalker extends TreeWalkerAdapter - { - /** - * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. - * - * @return string The SQL. - */ - public function walkSelectStatement(SelectStatement $AST) - { - $parent = null; - $parentName = null; - foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) { - if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { - $parent = $qComp; - $parentName = $dqlAlias; - break; - } - } - - $pathExpression = new PathExpression( - PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, array( - $parent['metadata']->getSingleIdentifierFieldName()) - ); - $pathExpression->type = PathExpression::TYPE_STATE_FIELD; - - $AST->selectClause->selectExpressions = array( - new SelectExpression( - new AggregateExpression('count', $pathExpression, true), null - ) - ); - } - } - -This will delete any given select expressions and replace them with a distinct count -query for the root entities primary key. This will only work if your entity has -only one identifier field (composite keys won't work). - -## Modify the Output Walker to generate Vendor specific SQL - -Most RMDBS have vendor-specific features for optimizing select query -execution plans. You can write your own output walker to introduce certain -keywords using the Query Hint API. A query hint can be set via `Query::setHint($name, $value)` -as shown in the previous example with the `HINT_CUSTOM_TREE_WALKERS` query hint. - -We will implement a custom Output Walker that allows to specify the SQL_NO_CACHE -query hint. - - [php] - $dql = "SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ..."; - $query = $m->createQuery($dql); - $query->setQueryHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker'); - $query->setQueryHint("mysqlWalker.sqlNoCache", true); - $results = $query->getResult(); - -Our `MysqlWalker` will extend the default `SqlWalker`. We will modify the generation -of the SELECT clause, adding the SQL_NO_CACHE on those queries that need it: - - [php] - class MysqlWalker extends SqlWalker - { - /** - * Walks down a SelectClause AST node, thereby generating the appropriate SQL. - * - * @param $selectClause - * @return string The SQL. - */ - public function walkSelectClause($selectClause) - { - $sql = parent::walkSelectClause($selectClause); - - if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) { - if ($selectClause->isDistinct) { - $sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql); - } else { - $sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql); - } - } - - return $sql; - } - } - -Writing extensions to the Output Walker requires a very deep understanding -of the DQL Parser and Walkers, but may offer your huge benefits with using vendor specific -features. This would still allow you write DQL queries instead of NativeQueries -to make use of vendor specific features. diff --git a/cookbook/en/dql-user-defined-functions.rst b/cookbook/en/dql-user-defined-functions.rst new file mode 100644 index 000000000..d39cda9f2 --- /dev/null +++ b/cookbook/en/dql-user-defined-functions.rst @@ -0,0 +1,239 @@ +DQL User Defined Functions +========================== + +By default DQL supports a limited subset of all the vendor-specific +SQL functions common between all the vendors. However in many cases +once you have decided on a specific database vendor, you will never +change it during the life of your project. This decision for a +specific vendor potentially allows you to make use of powerful SQL +features that are unique to the vendor. + + **Note** + + It is worth to mention that Doctrine 2 also allows you to handwrite + your SQL instead of extending the DQL parser, which is sort of an + advanced extension point. You can map arbitrary SQL to your objects + and gain access to vendor specific functionalities using the + ``EntityManager#createNativeQuery()`` API. + + +The DQL Parser has hooks to register functions that can then be +used in your DQL queries and transformed into SQL, allowing to +extend Doctrines Query capabilities to the vendors strength. This +post explains the Used-Defined Functions API (UDF) of the Dql +Parser and shows some examples to give you some hints how you would +extend DQL. + +There are three types of functions in DQL, those that return a +numerical value, those that return a string and those that return a +Date. Your custom method has to be registered as either one of +those. The return type information is used by the DQL parser to +check possible syntax errors during the parsing process, for +example using a string function return value in a math expression. + +Registering your own DQL functions +---------------------------------- + +You can register your functions adding them to the ORM +configuration: + +:: + + addCustomStringFunction($name, $class); + $config->addCustomNumericFunction($name, $class); + $config->addCustomDatetimeFunction($name, $class); + + $em = EntityManager::create($dbParams, $config); + +The ``$name`` is the name the function will be referred to in the +DQL query. ``$class`` is a string of a class-name which has to +extend ``Doctrine\ORM\Query\Node\FunctionNode``. This is a class +that offers all the necessary API and methods to implement a UDF. + +In this post we will implement some MySql specific Date calculation +methods, which are quite handy in my opinion: + +Date Diff +--------- + +`Mysql's DateDiff function `_ +takes two dates as argument and calculates the difference in days +with ``date1-date2``. + +The DQL parser is a top-down recursive descent parser to generate +the Abstract-Syntax Tree (AST) and uses a TreeWalker approach to +generate the appropriate SQL from the AST. This makes reading the +Parser/TreeWalker code manageable in a finite amount of time. + +The ``FunctionNode`` class I referred to earlier requires you to +implement two methods, one for the parsing process (obviously) +called ``parse`` and one for the TreeWalker process called +``getSql()``. I show you the code for the DateDiff method and +discuss it step by step: + +:: + + match(Lexer::T_IDENTIFIER); // (2) + $parser->match(Lexer::T_OPEN_PARENTHESIS); // (3) + $this->firstDateExpression = $parser->ArithmeticPrimary(); // (4) + $parser->match(Lexer::T_COMMA); // (5) + $this->secondDateExpression = $parser->ArithmeticPrimary(); // (6) + $parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3) + } + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'DATEDIFF(' . + $this->firstDateExpression->dispatch($sqlWalker) . ', ' . + $this->secondDateExpression->dispatch($sqlWalker) . + ')'; // (7) + } + } + +The Parsing process of the DATEDIFF function is going to find two +expressions the date1 and the date2 values, whose AST Node +representations will be saved in the variables of the DateDiff +FunctionNode instance at (1). + +The parse() method has to cut the function call "DATEDIFF" and its +argument into pieces. Since the parser detects the function using a +lookahead the T\_IDENTIFIER of the function name has to be taken +from the stack (2), followed by a detection of the arguments in +(4)-(6). The opening and closing parenthesis have to be detected +also. This happens during the Parsing process and leads to the +generation of a DateDiff FunctionNode somewhere in the AST of the +dql statement. + +The ``ArithmeticPrimary`` method call is the most common +denominator of valid EBNF tokens taken from the +`DQL EBNF grammar `_ +that matches our requirements for valid input into the DateDiff Dql +function. Picking the right tokens for your methods is a tricky +business, but the EBNF grammar is pretty helpful finding it, as is +looking at the Parser source code. + +Now in the TreeWalker process we have to pick up this node and +generate SQL from it, which apparently is quite easy looking at the +code in (7). Since we don't know which type of AST Node the first +and second Date expression are we are just dispatching them back to +the SQL Walker to generate SQL from and then wrap our DATEDIFF +function call around this output. + +Now registering this DateDiff FunctionNode with the ORM using: + +:: + + addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff'); + +We can do fancy stuff like: + +.. code-clock:: sql + + SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7 + +Date Add +-------- + +Often useful it the ability to do some simple date calculations in +your DQL query using +`MySql's DATE\_ADD function `_. + +I'll skip the blah and show the code for this function: + +:: + + match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->firstDateExpression = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_COMMA); + $parser->match(Lexer::T_IDENTIFIER); + + $this->intervalExpression = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_IDENTIFIER); + + /* @var $lexer Lexer */ + $lexer = $parser->getLexer(); + $this->unit = $lexer->token['value']; + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'DATE_ADD(' . + $this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' . + $this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit . + ')'; + } + } + +The only difference compared to the DATEDIFF here is, we +additionally need the ``Lexer`` to access the value of the +``T_IDENTIFIER`` token for the Date Interval unit, for example the +MONTH in: + +.. code-clock:: sql + + SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created + +The above method now only supports the specification using +``INTERVAL``, to also allow a real date in DATE\_ADD we need to add +some decision logic to the parsing process (makes up for a nice +exercise). + +Now as you see, the Parsing process doesn't catch all the possible +SQL errors, here we don't match for all the valid inputs for the +interval unit. However where necessary we rely on the database +vendors SQL parser to show us further errors in the parsing +process, for example if the Unit would not be one of the supported +values by MySql. + +Conclusion +---------- + +Now that you all know how you can implement vendor specific SQL +functionalities in DQL, we would be excited to see user extensions +that add vendor specific function packages, for example more math +functions, XML + GIS Support, Hashing functions and so on. + +For 2.0 we will come with the current set of functions, however for +a future version we will re-evaluate if we can abstract even more +vendor sql functions and extend the DQL languages scope. + +Code for this Extension to DQL and other Doctrine Extensions can be +found +`in my Github DoctrineExtensions repository `_. + + diff --git a/cookbook/en/dql-user-defined-functions.txt b/cookbook/en/dql-user-defined-functions.txt deleted file mode 100644 index 811a5cf36..000000000 --- a/cookbook/en/dql-user-defined-functions.txt +++ /dev/null @@ -1,198 +0,0 @@ -By default DQL supports a limited subset of all the vendor-specific SQL functions -common between all the vendors. However in many cases once you have decided on a -specific database vendor, you will never change it during the life of your project. -This decision for a specific vendor potentially allows you to make use of powerful -SQL features that are unique to the vendor. - -> **Note** -> -> It is worth to mention that Doctrine 2 also allows you to handwrite your SQL instead of extending -> the DQL parser, which is sort of an advanced extension point. You can map arbitrary SQL to your -> objects and gain access to vendor specific functionalities using the `EntityManager#createNativeQuery()` API. - -The DQL Parser has hooks to register functions that can then be used in your DQL queries and transformed into SQL, -allowing to extend Doctrines Query capabilities to the vendors strength. This post explains the -Used-Defined Functions API (UDF) of the Dql Parser and shows some examples to give you -some hints how you would extend DQL. - -There are three types of functions in DQL, those that return a numerical value, -those that return a string and those that return a Date. Your custom method -has to be registered as either one of those. The return type information -is used by the DQL parser to check possible syntax errors during the parsing -process, for example using a string function return value in a math expression. - -## Registering your own DQL functions - -You can register your functions adding them to the ORM configuration: - - [php] - $config = new \Doctrine\ORM\Configuration(); - $config->addCustomStringFunction($name, $class); - $config->addCustomNumericFunction($name, $class); - $config->addCustomDatetimeFunction($name, $class); - - $em = EntityManager::create($dbParams, $config); - -The `$name` is the name the function will be referred to in the DQL query. `$class` is a -string of a class-name which has to extend `Doctrine\ORM\Query\Node\FunctionNode`. -This is a class that offers all the necessary API and methods to implement -a UDF. - -In this post we will implement some MySql specific Date calculation methods, -which are quite handy in my opinion: - -## Date Diff - -[Mysql's DateDiff function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff) -takes two dates as argument and calculates the difference in days with `date1-date2`. - -The DQL parser is a top-down recursive descent parser to generate the -Abstract-Syntax Tree (AST) and uses a TreeWalker approach to generate the appropriate -SQL from the AST. This makes reading the Parser/TreeWalker code manageable -in a finite amount of time. - -The `FunctionNode` class I referred to earlier requires you to implement -two methods, one for the parsing process (obviously) called `parse` and -one for the TreeWalker process called `getSql()`. I show you the code for -the DateDiff method and discuss it step by step: - - [php] - /** - * DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" - */ - class DateDiff extends FunctionNode - { - // (1) - public $firstDateExpression = null; - public $secondDateExpression = null; - - public function parse(\Doctrine\ORM\Query\Parser $parser) - { - $parser->match(Lexer::T_IDENTIFIER); // (2) - $parser->match(Lexer::T_OPEN_PARENTHESIS); // (3) - $this->firstDateExpression = $parser->ArithmeticPrimary(); // (4) - $parser->match(Lexer::T_COMMA); // (5) - $this->secondDateExpression = $parser->ArithmeticPrimary(); // (6) - $parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3) - } - - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) - { - return 'DATEDIFF(' . - $this->firstDateExpression->dispatch($sqlWalker) . ', ' . - $this->secondDateExpression->dispatch($sqlWalker) . - ')'; // (7) - } - } - -The Parsing process of the DATEDIFF function is going to find two expressions -the date1 and the date2 values, whose AST Node representations will be saved -in the variables of the DateDiff FunctionNode instance at (1). - -The parse() method has to cut the function call "DATEDIFF" and its argument -into pieces. Since the parser detects the function using a lookahead the -T_IDENTIFIER of the function name has to be taken from the stack (2), followed -by a detection of the arguments in (4)-(6). The opening and closing parenthesis -have to be detected also. This happens during the Parsing process and leads -to the generation of a DateDiff FunctionNode somewhere in the AST of the -dql statement. - -The `ArithmeticPrimary` method call is the most common denominator of valid -EBNF tokens taken from the [DQL EBNF grammar](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf) -that matches our requirements for valid input into the DateDiff Dql function. -Picking the right tokens for your methods is a tricky business, but the EBNF -grammar is pretty helpful finding it, as is looking at the Parser source code. - -Now in the TreeWalker process we have to pick up this node and generate SQL -from it, which apparently is quite easy looking at the code in (7). Since -we don't know which type of AST Node the first and second Date expression -are we are just dispatching them back to the SQL Walker to generate SQL from -and then wrap our DATEDIFF function call around this output. - -Now registering this DateDiff FunctionNode with the ORM using: - - [php] - $config = new \Doctrine\ORM\Configuration(); - $config->addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff'); - -We can do fancy stuff like: - - [sql] - SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7 - -## Date Add - -Often useful it the ability to do some simple date calculations in your DQL query -using [MySql's DATE_ADD function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add). - -I'll skip the blah and show the code for this function: - - [php] - /** - * DateAddFunction ::= - * "DATE_ADD" "(" ArithmeticPrimary ", INTERVAL" ArithmeticPrimary Identifier ")" - */ - class DateAdd extends FunctionNode - { - public $firstDateExpression = null; - public $intervalExpression = null; - public $unit = null; - - public function parse(\Doctrine\ORM\Query\Parser $parser) - { - $parser->match(Lexer::T_IDENTIFIER); - $parser->match(Lexer::T_OPEN_PARENTHESIS); - - $this->firstDateExpression = $parser->ArithmeticPrimary(); - - $parser->match(Lexer::T_COMMA); - $parser->match(Lexer::T_IDENTIFIER); - - $this->intervalExpression = $parser->ArithmeticPrimary(); - - $parser->match(Lexer::T_IDENTIFIER); - - /* @var $lexer Lexer */ - $lexer = $parser->getLexer(); - $this->unit = $lexer->token['value']; - - $parser->match(Lexer::T_CLOSE_PARENTHESIS); - } - - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) - { - return 'DATE_ADD(' . - $this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' . - $this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit . - ')'; - } - } - -The only difference compared to the DATEDIFF here is, we additionally need the `Lexer` to access -the value of the `T_IDENTIFIER` token for the Date Interval unit, for example the MONTH in: - - [sql] - SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created - -The above method now only supports the specification using `INTERVAL`, to also -allow a real date in DATE_ADD we need to add some decision logic to the parsing -process (makes up for a nice exercise). - -Now as you see, the Parsing process doesn't catch all the possible SQL errors, -here we don't match for all the valid inputs for the interval unit. -However where necessary we rely on the database vendors SQL parser to show us further errors -in the parsing process, for example if the Unit would not be one of the supported values -by MySql. - -## Conclusion - -Now that you all know how you can implement vendor specific SQL functionalities in DQL, -we would be excited to see user extensions that add vendor specific function packages, -for example more math functions, XML + GIS Support, Hashing functions and so on. - -For 2.0 we will come with the current set of functions, however for a future -version we will re-evaluate if we can abstract even more vendor sql functions -and extend the DQL languages scope. - -Code for this Extension to DQL and other Doctrine Extensions can be found -[in my Github DoctrineExtensions repository](http://github.com/beberlei/DoctrineExtensions). \ No newline at end of file diff --git a/cookbook/en/getting-started-xml-edition.rst b/cookbook/en/getting-started-xml-edition.rst new file mode 100644 index 000000000..2a9d845bf --- /dev/null +++ b/cookbook/en/getting-started-xml-edition.rst @@ -0,0 +1,1037 @@ +Getting Started (XML Edition) +============================= + +Doctrine 2 is a project that aims to handle the persistence of the +domain model in a non-interfering way. The Data Mapper pattern is +at the heart of this project, aiming for a complete separation of +the domain/business logic from the persistence in a relational +database management system. The benefit of Doctrine for the +programmer is the possibility can focus solely on the business and +worry about persistence only as a secondary task. This doesn't mean +persistence is not important to Doctrine 2, however it is our +belief that there are considerable benefits for object-oriented +programming, if persistence and entities are kept perfectly +separated. + +What are Entities? +------------------ + +Entities are lightweight PHP Objects that don't need to extend any +abstract base class or interface. An entity class must not be final +or contain final methods. Additionally it must not implement +**clone nor**wakeup or +`do so safely `_. + +An entity contains persistable properties. A persistable property +is an instance variable of the entity that contains the data which +is persisted and retrieved by Doctrine's data mapping +capabilities. + +An Example Model: Bug Tracker +----------------------------- + +For this Getting Started Guide for Doctrine we will implement the +Bug Tracker domain model from the +`Zend\_Db\_Table `_ +documentation. Reading their documentation we can extract the +requirements to be: + + +- A Bugs has a description, creation date, status, reporter and + engineer +- A bug can occur on different products (platforms) +- Products have a name. +- Bug Reporter and Engineers are both Users of the System. +- A user can create new bugs. +- The assigned engineer can close a bug. +- A user can see all his reported or assigned bugs. +- Bugs can be paginated through a list-view. + +.. warning:: + + This tutorial is incrementally building up your Doctrine 2 + knowledge and even lets you make some mistakes, to show some common + pitfalls in mapping Entities to a database. Don't blindly + copy-paste the examples here, it is not production ready without + the additional comments and knowledge this tutorial teaches. + + +A first prototype +----------------- + +A first simplified design for this domain model might look like the +following set of classes: + +:: + + products = new ArrayCollection(); + } + } + + class User + { + public $reportedBugs = null; + public $assignedBugs = null; + + public function __construct() + { + $this->reportedBugs = new ArrayCollection(); + $this->assignedBugs = new ArrayCollection(); + } + } + +Whenever an entity is recreated from the database, an Collection +implementation of the type Doctrine is injected into your entity +instead of an array. Compared to the ArrayCollection this +implementation helps the Doctrine ORM understand the changes that +have happened to the collection which are noteworthy for +persistence. + +.. warning:: + + Lazy load proxies always contain an instance of + Doctrine's EntityManager and all its dependencies. Therefore a + var\_dump() will possibly dump a very large recursive structure + which is impossible to render and read. You have to use + ``Doctrine\Common\Util\Debug::dump()`` to restrict the dumping to a + human readable level. Additionally you should be aware that dumping + the EntityManager to a Browser may take several minutes, and the + Debug::dump() method just ignores any occurrences of it in Proxy + instances. + + +Because we only work with collections for the references we must be +careful to implement a bidirectional reference in the domain model. +The concept of owning or inverse side of a relation is central to +this notion and should always be kept in mind. The following +assumptions are made about relations and have to be followed to be +able to work with Doctrine 2. These assumptions are not unique to +Doctrine 2 but are best practices in handling database relations +and Object-Relational Mapping. + + +- Changes to Collections are saved or updated, when the entity on + the *owning* side of the collection is saved or updated. +- Saving an Entity at the inverse side of a relation never + triggers a persist operation to changes to the collection. +- In a one-to-one relation the entity holding the foreign key of + the related entity on its own database table is *always* the owning + side of the relation. +- In a many-to-many relation, both sides can be the owning side of + the relation. However in a bi-directional many-to-many relation + only one is allowed to be. +- In a many-to-one relation the Many-side is the owning side by + default, because it holds the foreign key. +- The OneToMany side of a relation is inverse by default, since + the foreign key is saved on the Many side. A OneToMany relation can + only be the owning side, if its implemented using a ManyToMany + relation with join table and restricting the one side to allow only + UNIQUE values per database constraint. + +.. note:: + + Consistency of bi-directional references on the inverse side of a + relation have to be managed in userland application code. Doctrine + cannot magically update your collections to be consistent. + + +In the case of Users and Bugs we have references back and forth to +the assigned and reported bugs from a user, making this relation +bi-directional. We have to change the code to ensure consistency of +the bi-directional reference: + +:: + + assignedToBug($this); + $this->engineer = $engineer; + } + + public function setReporter($reporter) + { + $reporter->addReportedBug($this); + $this->reporter = $reporter; + } + + public function getEngineer() + { + return $this->engineer; + } + + public function getReporter() + { + return $this->reporter; + } + } + class User + { + public function addReportedBug($bug) + { + $this->reportedBugs[] = $bug; + } + + public function assignedToBug($bug) + { + $this->assignedBugs[] = $bug; + } + } + +I chose to name the inverse methods in past-tense, which should +indicate that the actual assigning has already taken place and the +methods are only used for ensuring consistency of the references. +You can see from ``User::addReportedBug()`` and +``User::assignedToBug()`` that using this method in userland alone +would not add the Bug to the collection of the owning side in +Bug:::math:`$reporter or Bug::$`engineer. Using these methods and +calling Doctrine for persistence would not update the collections +representation in the database. + +Only using ``Bug::setEngineer()`` or ``Bug::setReporter()`` +correctly saves the relation information. We also set both +collection instance variables to protected, however with PHP 5.3's +new features Doctrine is still able to use Reflection to set and +get values from protected and private properties. + +The ``Bug::$reporter`` and ``Bug::$engineer`` properties are +Many-To-One relations, which point to a User. In a normalized +relational model the foreign key is saved on the Bug's table, hence +in our object-relation model the Bug is at the owning side of the +relation. You should always make sure that the use-cases of your +domain model should drive which side is an inverse or owning one in +your Doctrine mapping. In our example, whenever a new bug is saved +or an engineer is assigned to the bug, we don't want to update the +User to persist the reference, but the Bug. This is the case with +the Bug being at the owning side of the relation. + +Bugs reference Products by a uni-directional ManyToMany relation in +the database that points from from Bugs to Products. + +:: + + products[] = $product; + } + + public function getProducts() + { + return $this->products; + } + } + +We are now finished with the domain model given the requirements. +From the simple model with public properties only we had to do +quite some work to get to a model where we encapsulated the +references between the objects to make sure we don't break its +consistent state when using Doctrine. + +However up to now the assumptions Doctrine imposed on our business +objects have not restricting us much in our domain modelling +capabilities. Actually we would have encapsulated access to all the +properties anyways by using object-oriented best-practices. + +Metadata Mappings for our Entities +---------------------------------- + +Up to now we have only implemented our Entities as Data-Structures +without actually telling Doctrine how to persist them in the +database. If perfect in-memory databases would exist, we could now +finish the application using these entities by implementing code to +fulfil all the requirements. However the world isn't perfect and we +have to persist our entities in some storage to make sure we don't +loose their state. Doctrine currently serves Relational Database +Management Systems. In the future we are thinking to support NoSQL +vendors like CouchDb or MongoDb, however this is still far in the +future. + +The next step for persistence with Doctrine is to describe the +structure of our domain model entities to Doctrine using a metadata +language. The metadata language describes how entities, their +properties and references should be persisted and what constraints +should be applied to them. + +Metadata for entities are loaded using a +``Doctrine\ORM\Mapping\Driver\Driver`` implementation and Doctrine +2 already comes with XML, YAML and Annotations Drivers. In this +Getting Started Guide I will use the XML Mapping Driver. I think +XML beats YAML because of schema validation, and my favorite IDE +netbeans offers me auto-completion for the XML mapping files which +is awesome to work with and you don't have to look up all the +different metadata mapping commands all the time. + +Since we haven't namespaced our three entities, we have to +implement three mapping files called Bug.dcm.xml, Product.dcm.xml +and User.dcm.xml and put them into a distinct folder for mapping +configurations. + +The first discussed definition will be for the Product, since it is +the most simple one: + +:: + + [xml] + + + + + + + + + + + + +The top-level ``entity`` definition tag specifies information about +the class and table-name. The primitive type ``Product::$name`` is +defined as ``field`` attributes. The Id property is defined with +the ``id`` tag. The id has a ``generator`` tag nested inside which +defines that the primary key generation mechanism automatically +uses the database platforms native id generation strategy, for +example AUTO INCREMENT in the case of MySql or Sequences in the +case of PostgreSql and Oracle. + +We then go on specifying the definition of a Bug: + +:: + + [xml] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Here again we have the entity, id and primitive type definitions. +The column names are used from the Zend\_Db\_Table examples and +have different names than the properties on the Bug class. +Additionally for the "created" field it is specified that it is of +the Type "DATETIME", which translates the YYYY-mm-dd HH:mm:ss +Database format into a PHP DateTime instance and back. + +After the field definitions the two qualified references to the +user entity are defined. They are created by the ``many-to-one`` +tag. The class name of the related entity has to be specified with +the ``target-entity`` attribute, which is enough information for +the database mapper to access the foreign-table. The +``join-column`` tags are used to specify how the foreign and +referenced columns are named, an information Doctrine needs to +construct joins between those two entities correctly. Since +``reporter`` and ``engineer`` are on the owning side of a +bi-directional relation we also have to specify the ``inversed-by`` +attribute. They have to point to the field names on the inverse +side of the relationship. + +The last missing property is the ``Bug::$products`` collection. It +holds all products where the specific bug is occurring in. Again +you have to define the ``target-entity`` and ``field`` attributes +on the ``many-to-many`` tag. Furthermore you have to specify the +details of the many-to-many join-table and its foreign key columns. +The definition is rather complex, however relying on the XML +auto-completion I got it working easily, although I forget the +schema details all the time. + +The last missing definition is that of the User entity: + +:: + + [xml] + + + + + + + + + + + + + + + + +Here are some new things to mention about the ``one-to-many`` tags. +Remember that we discussed about the inverse and owning side. Now +both reportedBugs and assignedBugs are inverse relations, which +means the join details have already been defined on the owning +side. Therefore we only have to specify the property on the Bug +class that holds the owning sides. + +This example has a fair overview of the most basic features of the +metadata definition language. + +Obtaining the EntityManager +--------------------------- + +Doctrine's public interface is the EntityManager, it provides the +access point to the complete lifecycle management of your entities +and transforms entities from and back to persistence. You have to +configure and create it to use your entities with Doctrine 2. I +will show the configuration steps and then discuss them step by +step: + +:: + + register(); + + $config = new Doctrine\ORM\Configuration(); // (2) + + // Proxy Configuration (3) + $config->setProxyDir(__DIR__.'/lib/MyProject/Proxies'); + $config->setProxyNamespace('MyProject\Proxies'); + $config->setAutoGenerateProxyClasses((APPLICATION_ENV == "development")); + + // Mapping Configuration (4) + $driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings"); + $config->setMetadataDriverImpl($driverImpl); + + // Caching Configuration (5) + if (APPLICATION_ENV == "development") { + $cache = new \Doctrine\Common\Cache\ArrayCache(); + } else { + $cache = new \Doctrine\Common\Cache\ApcCache(); + } + $config->setMetadataCacheImpl($cache); + $config->setQueryCacheImpl($cache); + + // database configuration parameters (6) + $conn = array( + 'driver' => 'pdo_sqlite', + 'path' => __DIR__ . '/db.sqlite', + ); + + // obtaining the entity manager (7) + $evm = new Doctrine\Common\EventManager() + $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config, $evm); + +The first block sets up the autoloading capabilities of Doctrine. I +am registering the Doctrine namespace to the given path. To add +your own namespace you can instantiate another ``ClassLoader`` with +different namespace and path arguments. There is no requirement to +use the Doctrine ``ClassLoader`` for your autoloading needs, you +can use whatever suits you best. + +The second block contains of the instantiation of the ORM +Configuration object. Besides the configuration shown in the next +blocks there are several others with are all explained in the +`Configuration section of the manual `_. + +The Proxy Configuration is a required block for your application, +you have to specify where Doctrine writes the PHP code for Proxy +Generation. Proxies are children of your entities generated by +Doctrine to allow for type-safe lazy loading. We will see in a +later chapter how exactly this works. Besides the path to the +proxies we also specify which namespace they will reside under as +well as a flag ``autoGenerateProxyClasses`` indicating that proxies +should be re-generated on each request, which is recommended for +development. In production this should be prevented at all costs, +the proxy class generation can be quite costly. + +The fourth block contains the mapping driver details. We will use +XML Mapping in this example, so we configure the ``XmlDriver`` +instance with a path to mappings configuration folder where we put +the Bug.dcm.xml, Product.dcm.xml and User.dcm.xml. + +In the 5th block the caching configuration is set. In production we +use caching only on a per request-basis using the ArrayCache. In +production it is literally required to use Apc, Memcache or XCache +to get the full speed out of Doctrine. Internally Doctrine uses +caching heavily for the Metadata and DQL Query Language so make +sure you use a caching mechanism. + +The 6th block shows the configuration options required to connect +to a database, in my case a file-based sqlite database. All the +configuration options for all the shipped drivers are given in the +`DBAL Configuration section of the manual `_. + +The last block shows how the ``EntityManager`` is obtained from a +factory method, Here we also pass in an ``EventManager`` instance +which is optional. However using the EventManager you can hook in +to the lifecycle of entities, which is a common use-case, so you +know how to configure it already. + +Generating the Database Schema +------------------------------ + +Now that we have defined the Metadata Mappings and bootstrapped the +EntityManager we want to generate the relational database schema +from it. Doctrine has a Command-Line-Interface that allows you to +access the SchemaTool, a component that generates the required +tables to work with the metadata. + +For the command-line tool to work a cli-config.php file has to be +present in the project root directory, where you will execute the +doctrine command. Its a fairly simple file: + +:: + + new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager) + )); + $cli->setHelperSet($helperSet); + +You can then change into your project directory and call the +Doctrine command-line tool: + +:: + + [console] + doctrine@my-desktop> cd myproject/ + doctrine@my-desktop> doctrine orm:schema-tool:create + +.. note:: + + The ``doctrine`` command will only be present if you installed + Doctrine from PEAR. Otherwise you will have to dig into the + ``bin/doctrine.php`` code of your Doctrine 2 directory to setup + your doctrine command-line client. + + See the + `Tools section of the manual `_ + on how to setup the Doctrine console correctly. + + +During the development you probably need to re-create the database +several times when changing the Entity metadata. You can then +either re-create the database: + +:: + + [console] + doctrine@my-desktop> doctrine orm:schema-tool:drop + doctrine@my-desktop> doctrine orm:schema-tool:create + +Or use the update functionality: + +:: + + [console] + doctrine@my-desktop> doctrine orm:schema-tool:update + +The updating of databases uses a Diff Algorithm for a given +Database Schema, a cornerstone of the ``Doctrine\DBAL`` package, +which can even be used without the Doctrine ORM package. However +its not available in SQLite since it does not support ALTER TABLE. + +Writing Entities into the Database +---------------------------------- + +Having created the schema we can now start and save entities in the +database. For starters we need a create user use-case: + +:: + + name = $newUsername; + + $entityManager->persist($user); + $entityManager->flush(); + +Products can also be created: + +:: + + name = $newProductName; + + $entityManager->persist($product); + $entityManager->flush(); + +So what is happening in those two snippets? In both examples the +class creation is pretty standard, the interesting bits are the +communication with the ``EntityManager``. To notify the +EntityManager that a new entity should be inserted into the +database you have to call ``persist()``. However the EntityManager +does not act on this, its merely notified. You have to explicitly +call ``flush()`` to have the EntityManager write those two entities +to the database. + +You might wonder why does this distinction between persist +notification and flush exist? Doctrine 2 uses the UnitOfWork +pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one +single fast transaction, which is executed when flush is called. +Using this approach the write-performance is significantly faster +than in a scenario where updates are done for each entity in +isolation. In more complex scenarios than the previous two, you are +free to request updates on many different entities and all flush +them at once. + +Doctrine's UnitOfWork detects entities that have changed after +retrieval from the database automatically when the flush operation +is called, so that you only have to keep track of those entities +that are new or to be removed and pass them to +``EntityManager#persist()`` and ``EntityManager#remove()`` +respectively. This comparison to find dirty entities that need +updating is using a very efficient algorithm that has almost no +additional memory overhead and can even save you computing power by +only updating those database columns that really changed. + +We are now getting to the "Create a New Bug" requirement and the +code for this scenario may look like this: + +:: + + find("User", $theReporterId); + $engineer = $entityManager->find("User", $theDefaultEngineerId); + + $bug = new Bug(); + $bug->description = "Something does not work!"; + $bug->created = new DateTime("now"); + $bug->status = "NEW"; + + foreach ($productIds AS $productId) { + $product = $entityManager->find("Product", $productId); + $bug->assignToProduct($product); + } + + $bug->setReporter($reporter); + $bug->setEngineer($engineer); + + $entityManager->persist($bug); + $entityManager->flush(); + + echo "Your new Bug Id: ".$bug->id."\n"; + +This is the first contact with the read API of the EntityManager, +showing that a call to ``EntityManager#find($name, $id)`` returns a +single instance of an entity queried by primary key. Besides this +we see the persist + flush pattern again to save the Bug into the +database. + +See how simple relating Bug, Reporter, Engineer and Products is +done by using the discussed methods in the "A first prototype" +section. The UnitOfWork will detect this relations when flush is +called and relate them in the database appropriately. + +Queries for Application Use-Cases +--------------------------------- + +List of Bugs +~~~~~~~~~~~~ + +Using the previous examples we can fill up the database quite a +bit, however we now need to discuss how to query the underlying +mapper for the required view representations. When opening the +application, bugs can be paginated through a list-view, which is +the first read-only use-case: + +:: + + createQuery($dql); + $query->setMaxResults(30); + $bugs = $query->getResult(); + + foreach($bugs AS $bug) { + echo $bug->description." - ".$bug->created->format('d.m.Y')."\n"; + echo " Reported by: ".$bug->getReporter()->name."\n"; + echo " Assigned to: ".$bug->getEngineer()->name."\n"; + foreach($bug->getProducts() AS $product) { + echo " Platform: ".$product->name."\n"; + } + echo "\n"; + } + +The DQL Query in this example fetches the 30 most recent bugs with +their respective engineer and reporter in one single SQL statement. +The console output of this script is then: + +:: + + Something does not work! - 02.04.2010 + Reported by: beberlei + Assigned to: beberlei + Platform: My Product + +.. note:: + + **Dql is not Sql** + + You may wonder why we start writing SQL at the beginning of this + use-case. Don't we use an ORM to get rid of all the endless + hand-writing of SQL? Doctrine introduces DQL which is best + described as **object-query-language** and is a dialect of + `OQL `_ and + similar to `HQL `_ or + `JPQL `_. + It does not know the concept of columns and tables, but only those + of Entity-Class and property. Using the Metadata we defined before + it allows for very short distinctive and powerful queries. + + An important reason why DQL is favourable to the Query API of most + ORMs is its similarity to SQL. The DQL language allows query + constructs that most ORMs don't, GROUP BY even with HAVING, + Sub-selects, Fetch-Joins of nested classes, mixed results with + entities and scalar data such as COUNT() results and much more. + Using DQL you should seldom come to the point where you want to + throw your ORM into the dumpster, because it doesn't support some + the more powerful SQL concepts. + + Besides handwriting DQL you can however also use the + ``QueryBuilder`` retrieved by calling + ``$entityManager->createQueryBuilder()`` which is a Query Object + around the DQL language. + + As a last resort you can however also use Native SQL and a + description of the result set to retrieve entities from the + database. DQL boils down to a Native SQL statement and a + ``ResultSetMapping`` instance itself. Using Native SQL you could + even use stored procedures for data retrieval, or make use of + advanced non-portable database queries like PostgreSql's recursive + queries. + + +Array Hydration of the Bug List +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the previous use-case we retrieved the result as their +respective object instances. We are not limited to retrieving +objects only from Doctrine however. For a simple list view like the +previous one we only need read access to our entities and can +switch the hydration from objects to simple PHP arrays instead. +This can obviously yield considerable performance benefits for +read-only requests. + +Implementing the same list view as before using array hydration we +can rewrite our code: + +:: + + createQuery($dql); + $bugs = $query->getArrayResult(); + + foreach ($bugs AS $bug) { + echo $bug['description'] . " - " . $bug['created']->format('d.m.Y')."\n"; + echo " Reported by: ".$bug['reporter']['name']."\n"; + echo " Assigned to: ".$bug['engineer']['name']."\n"; + foreach($bug['products'] AS $product) { + echo " Platform: ".$product['name']."\n"; + } + echo "\n"; + } + +There is one significant difference in the DQL query however, we +have to add an additional fetch-join for the products connected to +a bug. The resulting SQL query for this single select statement is +pretty large, however still more efficient to retrieve compared to +hydrating objects. + +Find by Primary Key +~~~~~~~~~~~~~~~~~~~ + +The next Use-Case is displaying a Bug by primary key. This could be +done using DQL as in the previous example with a where clause, +however there is a convenience method on the Entity Manager that +handles loading by primary key, which we have already seen in the +write scenarios: + +:: + + find("Bug", (int)$theBugId); + +However we will soon see another problem with our entities using +this approach. Try displaying the engineer's name: + +:: + + description."\n"; + echo "Engineer: ".$bug->getEngineer()->name."\n"; + +It will be null! What is happening? It worked in the previous +example, so it can't be a problem with the persistence code of +Doctrine. What is it then? You walked in the public property trap. + +Since we only retrieved the bug by primary key both the engineer +and reporter are not immediately loaded from the database but are +replaced by LazyLoading proxies. Sample code of this proxy +generated code can be found in the specified Proxy Directory, it +looks like: + +:: + + _load(); + return parent::addReportedBug($bug); + } + + public function assignedToBug($bug) + { + $this->_load(); + return parent::assignedToBug($bug); + } + } + +See how upon each method call the proxy is lazily loaded from the +database? Using public properties however we never call a method +and Doctrine has no way to hook into the PHP Engine to detect a +direct access to a public property and trigger the lazy load. We +need to rewrite our entities, make all the properties private or +protected and add getters and setters to get a working example: + +:: + + getDescription()."\n"; + echo "Engineer: ".$bug->getEngineer()->getName()."\n"; + + /** + Bug: Something does not work! + Engineer: beberlei + */ + +Being required to use private or protected properties Doctrine 2 +actually enforces you to encapsulate your objects according to +object-oriented best-practices. + +Dashboard of the User +--------------------- + +For the next use-case we want to retrieve the dashboard view, a +list of all open bugs the user reported or was assigned to. This +will be achieved using DQL again, this time with some WHERE clauses +and usage of bound parameters: + +:: + + createQuery($dql) + ->setParameter(1, $theUserId) + ->setMaxResults(15) + ->getResult(); + + foreach ($myBugs AS $bug) { + echo $bug->getDescription()."\n"; + } + +That is it for the read-scenarios of this example, we will continue +with the last missing bit, engineers being able to close a bug. + +Number of Bugs +-------------- + +Until now we only retrieved entities or their array representation. +Doctrine also supports the retrieval of non-entities through DQL. +These values are called "scalar result values" and may even be +aggregate values using COUNT, SUM, MIN, MAX or AVG functions. + +We will need this knowledge to retrieve the number of open bugs +grouped by product: + +:: + + createQuery($dql)->getScalarResult(); + + foreach($productBugs as $productBug) { + echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n"; + } + +Updating Entities +----------------- + +There is a single use-case missing from the requirements, Engineers +should be able to close a bug. This looks like: + +:: + + find("Bug", (int)$theBugId); + $bug->close(); + + $entityManager->flush(); + +When retrieving the Bug from the database it is inserted into the +IdentityMap inside the UnitOfWork of Doctrine. This means your Bug +with exactly this id can only exist once during the whole request +no matter how often you call ``EntityManager#find()``. It even +detects entities that are hydrated using DQL and are already +present in the Identity Map. + +When flush is called the EntityManager loops over all the entities +in the identity map and performs a comparison between the values +originally retrieved from the database and those values the entity +currently has. If at least one of these properties is different the +entity is scheduled for an UPDATE against the database. Only the +changed columns are updated, which offers a pretty good performance +improvement compared to updating all the properties. + +This tutorial is over here, I hope you had fun. Additional content +will be added to this tutorial incrementally, topics will include: + +:: + + * Entity Repositories + * More on Association Mappings + * Lifecycle Events triggered in the UnitOfWork + * Ordering of Collections + +Additional details on all the topics discussed here can be found in +the respective manual chapters. + + diff --git a/cookbook/en/getting-started-xml-edition.txt b/cookbook/en/getting-started-xml-edition.txt deleted file mode 100644 index 74e637b0c..000000000 --- a/cookbook/en/getting-started-xml-edition.txt +++ /dev/null @@ -1,784 +0,0 @@ -Doctrine 2 is a project that aims to handle the persistence of the domain model in a non-interfering way. -The Data Mapper pattern is at the heart of this project, aiming for a complete separation of the domain/business logic -from the persistence in a relational database management system. The benefit of Doctrine for the programmer is the -possibility can focus solely on the business and worry about persistence only as a secondary task. This doesn't mean -persistence is not important to Doctrine 2, however it is our belief that there are considerable benefits for object-oriented -programming, if persistence and entities are kept perfectly separated. - -## What are Entities? - -Entities are lightweight PHP Objects that don't need to extend any abstract base class or interface. -An entity class must not be final or contain final methods. Additionally it must not implement __clone -nor __wakeup or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). - -An entity contains persistable properties. A persistable property is an instance variable of the entity -that contains the data which is persisted and retrieved by Doctrine's data mapping capabilities. - -## An Example Model: Bug Tracker - -For this Getting Started Guide for Doctrine we will implement the Bug Tracker domain model from the [Zend_Db_Table](http://framework.zend.com/manual/en/zend.db.table.html) -documentation. Reading their documentation we can extract the requirements to be: - -* A Bugs has a description, creation date, status, reporter and engineer -* A bug can occur on different products (platforms) -* Products have a name. -* Bug Reporter and Engineers are both Users of the System. -* A user can create new bugs. -* The assigned engineer can close a bug. -* A user can see all his reported or assigned bugs. -* Bugs can be paginated through a list-view. - -> **WARNING** -> -> This tutorial is incrementally building up your Doctrine 2 knowledge and even lets you make some mistakes, to -> show some common pitfalls in mapping Entities to a database. Don't blindly copy-paste the examples here, it -> is not production ready without the additional comments and knowledge this tutorial teaches. - -## A first prototype - -A first simplified design for this domain model might look like the following set of classes: - - [php] - class Bug - { - public $id; - public $description; - public $created; - public $status; - public $products = array(); - public $reporter; - public $engineer; - } - class Product - { - public $id; - public $name; - } - class User - { - public $id; - public $name; - public $reportedBugs = array(); - public $assignedBugs = array(); - } - -> **WARNING** -> -> This is only a prototype, please don't use public properties with Doctrine 2 at all, -> the "Queries for Application Use-Cases" section shows you why. In combination with proxies -> public properties can make up for pretty nasty bugs. - -Because we will focus on the mapping aspect, no effort is being made to encapsulate the business logic in this example. -All persistable properties are public in visibility. We will soon see that this is not the best solution in combination -with Doctrine 2, one restriction that actually forces you to encapsulate your properties. For persistence Doctrine 2 -actually uses Reflection to access the values in all your entities properties. - -Many of the fields are single scalar values, for example the 3 ID fields of the entities, their names, description, -status and change dates. Doctrine 2 can easily handle these single values as can any other ORM. From a point of our -domain model they are ready to be used right now and we will see at a later stage how they are mapped to the database. - -There are also several references between objects in this domain model, whose semantics are discussed case by case at this point -to explain how Doctrine handles them. In general each OneToOne or ManyToOne Relation in the Database is replaced by an -instance of the related object in the domain model. Each OneToMany or ManyToMany Relation is replaced by a collection -of instances in the domain model. - -If you think this through carefully you realize Doctrine 2 will load up the complete database in memory if you access -one object. However by default Doctrine generates Lazy Load proxies of entities or collections of all the relations -that haven't been explicitly retrieved from the database yet. - -To be able to use lazyload with collections, simple PHP arrays have to be replaced by a generic collection -interface Doctrine\Common\Collections\Collection which tries to act as array as much as possible using ArrayAccess, -IteratorAggregate and Countable interfaces. The class \Doctrine\Common\Collections\ArrayCollection is the most simple -implementation of this interface. - -Now that we know this, we have to clear up our domain model to cope with the assumptions about related collections: - - [php] - use Doctrine\Common\Collections\ArrayCollection; - - class Bug - { - public $products = null; - - public function __construct() - { - $this->products = new ArrayCollection(); - } - } - - class User - { - public $reportedBugs = null; - public $assignedBugs = null; - - public function __construct() - { - $this->reportedBugs = new ArrayCollection(); - $this->assignedBugs = new ArrayCollection(); - } - } - -Whenever an entity is recreated from the database, an Collection implementation of the type -Doctrine\ORM\PersistantCollection is injected into your entity instead of an array. Compared -to the ArrayCollection this implementation helps the Doctrine ORM understand the changes that -have happened to the collection which are noteworthy for persistence. - -> **Warning** -> Lazy load proxies always contain an instance of Doctrine's EntityManager and all its dependencies. Therefore a var_dump() -> will possibly dump a very large recursive structure which is impossible to render and read. You have to use -> `Doctrine\Common\Util\Debug::dump()` to restrict the dumping to a human readable level. Additionally you should be aware -> that dumping the EntityManager to a Browser may take several minutes, and the Debug::dump() method just ignores any -> occurrences of it in Proxy instances. - -Because we only work with collections for the references we must be careful to implement a bidirectional reference in -the domain model. The concept of owning or inverse side of a relation is central to this notion and should always -be kept in mind. The following assumptions are made about relations and have to be followed to be able to work with Doctrine 2. -These assumptions are not unique to Doctrine 2 but are best practices in handling database relations and Object-Relational Mapping. - -* Changes to Collections are saved or updated, when the entity on the *owning* side of the collection is saved or updated. -* Saving an Entity at the inverse side of a relation never triggers a persist operation to changes to the collection. -* In a one-to-one relation the entity holding the foreign key of the related entity on its own database table is *always* the owning side of the relation. -* In a many-to-many relation, both sides can be the owning side of the relation. However in a bi-directional many-to-many relation only one is allowed to be. -* In a many-to-one relation the Many-side is the owning side by default, because it holds the foreign key. -* The OneToMany side of a relation is inverse by default, since the foreign key is saved on the Many side. A OneToMany relation can only be the owning side, if its implemented using a ManyToMany relation with join table and restricting the one side to allow only UNIQUE values per database constraint. - -> **Important** -> -> Consistency of bi-directional references on the inverse side of a relation have to be managed in userland application code. -> Doctrine cannot magically update your collections to be consistent. - -In the case of Users and Bugs we have references back and forth to the assigned and reported bugs from a user, -making this relation bi-directional. We have to change the code to ensure consistency of the bi-directional reference: - - [php] - class Bug - { - protected $engineer; - protected $reporter; - - public function setEngineer($engineer) - { - $engineer->assignedToBug($this); - $this->engineer = $engineer; - } - - public function setReporter($reporter) - { - $reporter->addReportedBug($this); - $this->reporter = $reporter; - } - - public function getEngineer() - { - return $this->engineer; - } - - public function getReporter() - { - return $this->reporter; - } - } - class User - { - public function addReportedBug($bug) - { - $this->reportedBugs[] = $bug; - } - - public function assignedToBug($bug) - { - $this->assignedBugs[] = $bug; - } - } - -I chose to name the inverse methods in past-tense, which should indicate that the actual assigning has already taken -place and the methods are only used for ensuring consistency of the references. You can see from `User::addReportedBug()` -and `User::assignedToBug()` that using this method in userland alone would not add the Bug to the collection of the owning -side in Bug::$reporter or Bug::$engineer. Using these methods and calling Doctrine for persistence would not update -the collections representation in the database. - -Only using `Bug::setEngineer()` or `Bug::setReporter()` correctly saves the relation information. We also set both collection -instance variables to protected, however with PHP 5.3's new features Doctrine is still able to use Reflection to set and get values -from protected and private properties. - -The `Bug::$reporter` and `Bug::$engineer` properties are Many-To-One relations, which point to a User. In a normalized -relational model the foreign key is saved on the Bug's table, hence in our object-relation model the Bug is at the owning -side of the relation. You should always make sure that the use-cases of your domain model should drive which side -is an inverse or owning one in your Doctrine mapping. In our example, whenever a new bug is saved or an engineer is assigned -to the bug, we don't want to update the User to persist the reference, but the Bug. -This is the case with the Bug being at the owning side of the relation. - -Bugs reference Products by a uni-directional ManyToMany relation in the database that points from from Bugs to Products. - - [php] - class Bug - { - protected $products = null; // Set protected for encapsulation - - public function assignToProduct($product) - { - $this->products[] = $product; - } - - public function getProducts() - { - return $this->products; - } - } - -We are now finished with the domain model given the requirements. From the simple model with public properties only -we had to do quite some work to get to a model where we encapsulated the references between the objects to make sure -we don't break its consistent state when using Doctrine. - -However up to now the assumptions Doctrine imposed on our business objects have not restricting us much in our domain -modelling capabilities. Actually we would have encapsulated access to all the properties anyways by using -object-oriented best-practices. - -## Metadata Mappings for our Entities - -Up to now we have only implemented our Entities as Data-Structures without actually telling Doctrine how to persist -them in the database. If perfect in-memory databases would exist, we could now finish the application using these entities -by implementing code to fulfil all the requirements. However the world isn't perfect and we have to persist our -entities in some storage to make sure we don't loose their state. Doctrine currently serves Relational Database Management Systems. -In the future we are thinking to support NoSQL vendors like CouchDb or MongoDb, however this is still far in the future. - -The next step for persistence with Doctrine is to describe the structure of our domain model entities to Doctrine -using a metadata language. The metadata language describes how entities, their properties and references should be -persisted and what constraints should be applied to them. - -Metadata for entities are loaded using a `Doctrine\ORM\Mapping\Driver\Driver` implementation and Doctrine 2 already comes -with XML, YAML and Annotations Drivers. In this Getting Started Guide I will use the XML Mapping Driver. I think XML -beats YAML because of schema validation, and my favorite IDE netbeans offers me auto-completion for the XML mapping files -which is awesome to work with and you don't have to look up all the different metadata mapping commands all the time. - -Since we haven't namespaced our three entities, we have to implement three mapping files called Bug.dcm.xml, -Product.dcm.xml and User.dcm.xml and put them into a distinct folder for mapping configurations. - -The first discussed definition will be for the Product, since it is the most simple one: - - [xml] - - - - - - - - - - - - -The top-level `entity` definition tag specifies information about the class and table-name. The -primitive type `Product::$name` is defined as `field` attributes. The Id property is defined with the `id` tag. -The id has a `generator` tag nested inside which defines that the primary key generation mechanism -automatically uses the database platforms native id generation strategy, for example AUTO INCREMENT -in the case of MySql or Sequences in the case of PostgreSql and Oracle. - -We then go on specifying the definition of a Bug: - - [xml] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Here again we have the entity, id and primitive type definitions. -The column names are used from the Zend_Db_Table examples and have different names than the properties -on the Bug class. Additionally for the "created" field it is specified that it is of the Type "DATETIME", -which translates the YYYY-mm-dd HH:mm:ss Database format into a PHP DateTime instance and back. - -After the field definitions the two qualified references to the user entity are defined. They are created by -the `many-to-one` tag. The class name of the related entity has to be specified with the `target-entity` -attribute, which is enough information for the database mapper to access the foreign-table. The -`join-column` tags are used to specify how the foreign and referenced columns are named, an information -Doctrine needs to construct joins between those two entities correctly. Since `reporter` and `engineer` -are on the owning side of a bi-directional relation we also have to specify the `inversed-by` attribute. -They have to point to the field names on the inverse side of the relationship. - -The last missing property is the `Bug::$products` collection. It holds all products where the specific -bug is occurring in. Again you have to define the `target-entity` and `field` attributes on the `many-to-many` -tag. Furthermore you have to specify the details of the many-to-many join-table and its foreign key columns. -The definition is rather complex, however relying on the XML auto-completion I got it working easily, although -I forget the schema details all the time. - -The last missing definition is that of the User entity: - - [xml] - - - - - - - - - - - - - - - - -Here are some new things to mention about the `one-to-many` tags. Remember that we discussed about -the inverse and owning side. Now both reportedBugs and assignedBugs are inverse relations, -which means the join details have already been defined on the owning side. Therefore we only -have to specify the property on the Bug class that holds the owning sides. - -This example has a fair overview of the most basic features of the metadata definition language. - -## Obtaining the EntityManager - -Doctrine's public interface is the EntityManager, it provides the access point to the complete -lifecycle management of your entities and transforms entities from and back to persistence. You -have to configure and create it to use your entities with Doctrine 2. I will show the configuration -steps and then discuss them step by step: - - [php] - // Setup Autoloader (1) - require '/path/to/lib/Doctrine/Common/ClassLoader.php'; - $loader = new Doctrine\Common\ClassLoader("Doctrine", '/path/to/Doctrine/trunk/lib/'); - $loader->register(); - - $config = new Doctrine\ORM\Configuration(); // (2) - - // Proxy Configuration (3) - $config->setProxyDir(__DIR__.'/lib/MyProject/Proxies'); - $config->setProxyNamespace('MyProject\Proxies'); - $config->setAutoGenerateProxyClasses((APPLICATION_ENV == "development")); - - // Mapping Configuration (4) - $driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings"); - $config->setMetadataDriverImpl($driverImpl); - - // Caching Configuration (5) - if (APPLICATION_ENV == "development") { - $cache = new \Doctrine\Common\Cache\ArrayCache(); - } else { - $cache = new \Doctrine\Common\Cache\ApcCache(); - } - $config->setMetadataCacheImpl($cache); - $config->setQueryCacheImpl($cache); - - // database configuration parameters (6) - $conn = array( - 'driver' => 'pdo_sqlite', - 'path' => __DIR__ . '/db.sqlite', - ); - - // obtaining the entity manager (7) - $evm = new Doctrine\Common\EventManager() - $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config, $evm); - -The first block sets up the autoloading capabilities of Doctrine. I am registering the Doctrine -namespace to the given path. To add your own namespace you can instantiate another `ClassLoader` -with different namespace and path arguments. There is no requirement to use the Doctrine `ClassLoader` -for your autoloading needs, you can use whatever suits you best. - -The second block contains of the instantiation of the ORM Configuration object. Besides the -configuration shown in the next blocks there are several others with are all explained -in the [Configuration section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). - -The Proxy Configuration is a required block for your application, you have to specify where -Doctrine writes the PHP code for Proxy Generation. Proxies are children of your entities generated -by Doctrine to allow for type-safe lazy loading. We will see in a later chapter how exactly this works. -Besides the path to the proxies we also specify which namespace they will reside under as well as -a flag `autoGenerateProxyClasses` indicating that proxies should be re-generated on each request, -which is recommended for development. In production this should be prevented at all costs, -the proxy class generation can be quite costly. - -The fourth block contains the mapping driver details. We will use XML Mapping in this example, so -we configure the `XmlDriver` instance with a path to mappings configuration folder where we put -the Bug.dcm.xml, Product.dcm.xml and User.dcm.xml. - -In the 5th block the caching configuration is set. In production we use caching only on a per request-basis -using the ArrayCache. In production it is literally required to use Apc, Memcache or XCache to get the full -speed out of Doctrine. Internally Doctrine uses caching heavily for the Metadata and DQL Query Language -so make sure you use a caching mechanism. - -The 6th block shows the configuration options required to connect to a database, in my case a file-based -sqlite database. All the configuration options for all the shipped drivers are given in the [DBAL Configuration -section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/dbal). - -The last block shows how the `EntityManager` is obtained from a factory method, Here we also pass -in an `EventManager` instance which is optional. However using the EventManager you can hook in to the lifecycle -of entities, which is a common use-case, so you know how to configure it already. - -## Generating the Database Schema - -Now that we have defined the Metadata Mappings and bootstrapped the EntityManager -we want to generate the relational database schema from it. -Doctrine has a Command-Line-Interface that allows you to access the SchemaTool, a component that generates -the required tables to work with the metadata. - -For the command-line tool to work a cli-config.php file has to be present in the project root directory, -where you will execute the doctrine command. Its a fairly simple file: - - [php] - $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( - 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager) - )); - $cli->setHelperSet($helperSet); - -You can then change into your project directory and call the Doctrine command-line tool: - - [console] - doctrine@my-desktop> cd myproject/ - doctrine@my-desktop> doctrine orm:schema-tool:create - -> **NOTE** -> -> The `doctrine` command will only be present if you installed Doctrine from PEAR. -> Otherwise you will have to dig into the `bin/doctrine.php` code of your Doctrine 2 -> directory to setup your doctrine command-line client. -> -> See the [Tools section of the manual](http://www.doctrine-project.org/projects/orm/2.0/docs/reference/tools/en) -> on how to setup the Doctrine console correctly. - -During the development you probably need to re-create the database several times when changing the Entity -metadata. You can then either re-create the database: - - [console] - doctrine@my-desktop> doctrine orm:schema-tool:drop - doctrine@my-desktop> doctrine orm:schema-tool:create - -Or use the update functionality: - - [console] - doctrine@my-desktop> doctrine orm:schema-tool:update - -The updating of databases uses a Diff Algorithm for a given Database Schema, a cornerstone of the `Doctrine\DBAL` -package, which can even be used without the Doctrine ORM package. However its not available in SQLite since -it does not support ALTER TABLE. - -## Writing Entities into the Database - -Having created the schema we can now start and save entities in the database. For starters we need a create user use-case: - - [php] - $newUsername = "beberlei"; - - $user = new User(); - $user->name = $newUsername; - - $entityManager->persist($user); - $entityManager->flush(); - -Products can also be created: - - [php] - $newProductName = "My Product"; - - $product = new Product(); - $product->name = $newProductName; - - $entityManager->persist($product); - $entityManager->flush(); - -So what is happening in those two snippets? In both examples the class creation is pretty standard, the interesting -bits are the communication with the `EntityManager`. To notify the EntityManager that a new entity should be inserted -into the database you have to call `persist()`. However the EntityManager does not act on this, its merely notified. -You have to explicitly call `flush()` to have the EntityManager write those two entities to the database. - -You might wonder why does this distinction between persist notification and flush exist? Doctrine 2 uses the -UnitOfWork pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one single fast transaction, which -is executed when flush is called. Using this approach the write-performance is significantly faster than -in a scenario where updates are done for each entity in isolation. In more complex scenarios than the -previous two, you are free to request updates on many different entities and all flush them at once. - -Doctrine's UnitOfWork detects entities that have changed after retrieval from the database automatically when -the flush operation is called, so that you only have to keep track of those entities that are new or to be removed and pass them to -`EntityManager#persist()` and `EntityManager#remove()` respectively. This comparison to find dirty -entities that need updating is using a very efficient algorithm that has almost no additional -memory overhead and can even save you computing power by only updating those database columns -that really changed. - -We are now getting to the "Create a New Bug" requirement and the code for this scenario may look like this: - - [php] - $reporter = $entityManager->find("User", $theReporterId); - $engineer = $entityManager->find("User", $theDefaultEngineerId); - - $bug = new Bug(); - $bug->description = "Something does not work!"; - $bug->created = new DateTime("now"); - $bug->status = "NEW"; - - foreach ($productIds AS $productId) { - $product = $entityManager->find("Product", $productId); - $bug->assignToProduct($product); - } - - $bug->setReporter($reporter); - $bug->setEngineer($engineer); - - $entityManager->persist($bug); - $entityManager->flush(); - - echo "Your new Bug Id: ".$bug->id."\n"; - -This is the first contact with the read API of the EntityManager, showing that a call to -`EntityManager#find($name, $id)` returns a single instance of an entity queried by primary key. -Besides this we see the persist + flush pattern again to save the Bug into the database. - -See how simple relating Bug, Reporter, Engineer and Products is done by using the discussed methods -in the "A first prototype" section. The UnitOfWork will detect this relations when flush -is called and relate them in the database appropriately. - -## Queries for Application Use-Cases - -### List of Bugs - -Using the previous examples we can fill up the database quite a bit, however we now need to discuss how to query the underlying -mapper for the required view representations. When opening the application, bugs can be paginated through a list-view, which is the first -read-only use-case: - - [php] - $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC"; - - $query = $entityManager->createQuery($dql); - $query->setMaxResults(30); - $bugs = $query->getResult(); - - foreach($bugs AS $bug) { - echo $bug->description." - ".$bug->created->format('d.m.Y')."\n"; - echo " Reported by: ".$bug->getReporter()->name."\n"; - echo " Assigned to: ".$bug->getEngineer()->name."\n"; - foreach($bug->getProducts() AS $product) { - echo " Platform: ".$product->name."\n"; - } - echo "\n"; - } - -The DQL Query in this example fetches the 30 most recent bugs with their respective engineer and reporter -in one single SQL statement. The console output of this script is then: - - Something does not work! - 02.04.2010 - Reported by: beberlei - Assigned to: beberlei - Platform: My Product - -> **NOTE** -> -> **Dql is not Sql** -> -> You may wonder why we start writing SQL at the beginning of this use-case. Don't we use an ORM to get rid -> of all the endless hand-writing of SQL? Doctrine introduces DQL which is best described -> as **object-query-language** and is a dialect of [OQL](http://en.wikipedia.org/wiki/Object_Query_Language) and -> similar to [HQL](http://www.hibernate.org) or [JPQL](http://en.wikipedia.org/wiki/Java_Persistence_Query_Language). -> It does not know the concept of columns and tables, but only those -> of Entity-Class and property. Using the Metadata we defined before it allows for very short distinctive -> and powerful queries. -> -> An important reason why DQL is favourable to the Query API of most ORMs is its similarity to SQL. The DQL language -> allows query constructs that most ORMs don't, GROUP BY even with HAVING, Sub-selects, Fetch-Joins of nested -> classes, mixed results with entities and scalar data such as COUNT() results and much more. Using -> DQL you should seldom come to the point where you want to throw your ORM into the dumpster, because it -> doesn't support some the more powerful SQL concepts. -> -> Besides handwriting DQL you can however also use the `QueryBuilder` retrieved by calling `$entityManager->createQueryBuilder()` -> which is a Query Object around the DQL language. -> -> As a last resort you can however also use Native SQL and a description of the result set to retrieve -> entities from the database. DQL boils down to a Native SQL statement and a `ResultSetMapping` instance itself. -> Using Native SQL you could even use stored procedures for data retrieval, or make use of advanced non-portable -> database queries like PostgreSql's recursive queries. - -### Array Hydration of the Bug List - -In the previous use-case we retrieved the result as their respective object instances. -We are not limited to retrieving objects only from Doctrine however. For a simple list view -like the previous one we only need read access to our entities and can switch the hydration -from objects to simple PHP arrays instead. This can obviously yield considerable performance benefits for read-only requests. - -Implementing the same list view as before using array hydration we can rewrite our code: - - [php] - $dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ". - "JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC"; - $query = $em->createQuery($dql); - $bugs = $query->getArrayResult(); - - foreach ($bugs AS $bug) { - echo $bug['description'] . " - " . $bug['created']->format('d.m.Y')."\n"; - echo " Reported by: ".$bug['reporter']['name']."\n"; - echo " Assigned to: ".$bug['engineer']['name']."\n"; - foreach($bug['products'] AS $product) { - echo " Platform: ".$product['name']."\n"; - } - echo "\n"; - } - -There is one significant difference in the DQL query however, we have -to add an additional fetch-join for the products connected to a bug. The resulting -SQL query for this single select statement is pretty large, however still -more efficient to retrieve compared to hydrating objects. - -### Find by Primary Key - -The next Use-Case is displaying a Bug by primary key. This could be done using DQL as in the previous example with a where clause, -however there is a convenience method on the Entity Manager that handles loading by primary key, which we have already -seen in the write scenarios: - - [php] - $bug = $entityManager->find("Bug", (int)$theBugId); - -However we will soon see another problem with our entities using this approach. Try displaying the engineer's name: - - [php] - echo "Bug: ".$bug->description."\n"; - echo "Engineer: ".$bug->getEngineer()->name."\n"; - -It will be null! What is happening? It worked in the previous example, so it can't be a problem with the persistence -code of Doctrine. What is it then? You walked in the public property trap. - -Since we only retrieved the bug by primary key both the engineer and reporter are not immediately loaded -from the database but are replaced by LazyLoading proxies. Sample -code of this proxy generated code can be found in the specified Proxy Directory, it looks like: - - [php] - namespace MyProject\Proxies; - - /** - * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. - */ - class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy - { - // .. lazy load code here - - public function addReportedBug($bug) - { - $this->_load(); - return parent::addReportedBug($bug); - } - - public function assignedToBug($bug) - { - $this->_load(); - return parent::assignedToBug($bug); - } - } - -See how upon each method call the proxy is lazily loaded from the database? Using public properties however -we never call a method and Doctrine has no way to hook into the PHP Engine to detect a direct access to a public property -and trigger the lazy load. We need to rewrite our entities, make all the properties private or protected and add getters -and setters to get a working example: - - [php] - echo "Bug: ".$bug->getDescription()."\n"; - echo "Engineer: ".$bug->getEngineer()->getName()."\n"; - - /** - Bug: Something does not work! - Engineer: beberlei - */ - -Being required to use private or protected properties Doctrine 2 actually enforces you to encapsulate -your objects according to object-oriented best-practices. - -## Dashboard of the User - -For the next use-case we want to retrieve the dashboard view, a list of all open bugs the user reported or -was assigned to. This will be achieved using DQL again, this time with some WHERE clauses and usage of bound parameters: - - [php] - $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ". - "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC"; - - $myBugs = $entityManager->createQuery($dql) - ->setParameter(1, $theUserId) - ->setMaxResults(15) - ->getResult(); - - foreach ($myBugs AS $bug) { - echo $bug->getDescription()."\n"; - } - -That is it for the read-scenarios of this example, we will continue with the last missing bit, engineers -being able to close a bug. - -## Number of Bugs - -Until now we only retrieved entities or their array representation. Doctrine also supports the retrieval -of non-entities through DQL. These values are called "scalar result values" and may even be aggregate -values using COUNT, SUM, MIN, MAX or AVG functions. - -We will need this knowledge to retrieve the number of open bugs grouped by product: - - [php] - $dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ". - "JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id"; - $productBugs = $em->createQuery($dql)->getScalarResult(); - - foreach($productBugs as $productBug) { - echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n"; - } - -## Updating Entities - -There is a single use-case missing from the requirements, Engineers should be able to close a bug. This looks like: - - [php] - $bug = $entityManager->find("Bug", (int)$theBugId); - $bug->close(); - - $entityManager->flush(); - -When retrieving the Bug from the database it is inserted into the IdentityMap inside the UnitOfWork of Doctrine. -This means your Bug with exactly this id can only exist once during the whole request no matter how often you -call `EntityManager#find()`. It even detects entities that are hydrated using DQL and are already present in -the Identity Map. - -When flush is called the EntityManager loops over all the entities in the identity map and performs a comparison -between the values originally retrieved from the database and those values the entity currently has. If at -least one of these properties is different the entity is scheduled for an UPDATE against the database. -Only the changed columns are updated, which offers a pretty good performance improvement compared to updating -all the properties. - -This tutorial is over here, I hope you had fun. Additional content will be added to this tutorial -incrementally, topics will include: - - * Entity Repositories - * More on Association Mappings - * Lifecycle Events triggered in the UnitOfWork - * Ordering of Collections - -Additional details on all the topics discussed here can be found in the respective manual chapters. diff --git a/cookbook/en/implementing-arrayaccess-for-domain-objects.txt b/cookbook/en/implementing-arrayaccess-for-domain-objects.rst similarity index 60% rename from cookbook/en/implementing-arrayaccess-for-domain-objects.txt rename to cookbook/en/implementing-arrayaccess-for-domain-objects.rst index 4f295420d..aeb9a7ada 100644 --- a/cookbook/en/implementing-arrayaccess-for-domain-objects.txt +++ b/cookbook/en/implementing-arrayaccess-for-domain-objects.rst @@ -1,47 +1,61 @@ +Implementing ArrayAccess for Domain Objects +=========================================== -This recipe will show you how to implement ArrayAccess for your domain objects in order to allow more uniform access, for example in templates. In these examples we will implement ArrayAccess on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. +This recipe will show you how to implement ArrayAccess for your +domain objects in order to allow more uniform access, for example +in templates. In these examples we will implement ArrayAccess on a +`Layer Supertype `_ +for all our domain objects. -++ Option 1 +Option 1 +-------- -In this implementation we will make use of PHPs highly dynamic nature to dynamically access properties of a subtype in a supertype at runtime. Note that this implementation has 2 main caveats: +In this implementation we will make use of PHPs highly dynamic +nature to dynamically access properties of a subtype in a supertype +at runtime. Note that this implementation has 2 main caveats: -* It will not work with private fields -* It will not go through any getters/setters -- +- It will not work with private fields +- It will not go through any getters/setters - [php] +:: + + $offset); } - + public function offsetSet($offset, $value) { $this->$offset = $value; } - + public function offsetGet($offset) { return $this->$offset; } - + public function offsetUnset($offset) { $this->$offset = null; } } +Option 2 +-------- -++ Option 2 +In this implementation we will dynamically invoke getters/setters. +Again we use PHPs dynamic nature to invoke methods on a subtype +from a supertype at runtime. This implementation has the following +caveats: -In this implementation we will dynamically invoke getters/setters. Again we use PHPs dynamic nature to invoke methods on a subtype from a supertype at runtime. This implementation has the following caveats: -* It relies on a naming convention -* The semantics of offsetExists can differ -* offsetUnset will not work with typehinted setters +- It relies on a naming convention +- The semantics of offsetExists can differ +- offsetUnset will not work with typehinted setters -- +:: - [php] + {"get$offset"}(); return $value !== null; } - + public function offsetSet($offset, $value) { $this->{"set$offset"}($value); } - + public function offsetGet($offset) { return $this->{"get$offset"}(); } - + public function offsetUnset($offset) { $this->{"set$offset"}(null); } } - -++ Read-only -You can slightly tweak option 1 or option 2 in order to make array access read-only. This will also circumvent some of the caveats of each option. Simply make offsetSet and offsetUnset throw an exception (i.e. BadMethodCallException). +Read-only +--------- - [php] +You can slightly tweak option 1 or option 2 in order to make array +access read-only. This will also circumvent some of the caveats of +each option. Simply make offsetSet and offsetUnset throw an +exception (i.e. BadMethodCallException). + +:: + + `_ +for all our domain objects. -++ Implementing NotifyPropertyChanged +Implementing NotifyPropertyChanged +---------------------------------- -The NOTIFY 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. +The NOTIFY 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. - [php] +:: + + _listeners[] = $listener; } - + /** Notifies listeners of a change. */ protected function _onPropertyChanged($propName, $oldValue, $newValue) { if ($this->_listeners) { @@ -27,15 +41,19 @@ The NOTIFY policy is based on the assumption that the entities notify interested } } -Then, in each property setter of concrete, derived domain classes, you need to invoke _onPropertyChanged as follows to notify listeners: +Then, in each property setter of concrete, derived domain classes, +you need to invoke \_onPropertyChanged as follows to notify +listeners: - [php] +:: + + data) { // check: is it actually modified? $this->_onPropertyChanged('data', $this->data, $data); @@ -44,5 +62,9 @@ Then, in each property setter of concrete, derived domain classes, you need to i } } -The check whether the new value is different from the old one is not mandatory but recommended. That way you can avoid unnecessary updates and also have full control over when you consider a property changed. +The check whether the new value is different from the old one is +not mandatory but recommended. That way you can avoid unnecessary +updates and also have full control over when you consider a +property changed. + diff --git a/cookbook/en/implementing-wakeup-or-clone.rst b/cookbook/en/implementing-wakeup-or-clone.rst new file mode 100644 index 000000000..f93fbfe93 --- /dev/null +++ b/cookbook/en/implementing-wakeup-or-clone.rst @@ -0,0 +1,76 @@ +Implementing Wakeup or Clone +============================ + +As explained in the +`restrictions for entity classes in the manual `_, +it is usually not allowed for an entity to implement ``__wakeup`` +or ``__clone``, because Doctrine makes special use of them. +However, it is quite easy to make use of these methods in a safe +way by guarding the custom wakeup or clone code with an entity +identity check, as demonstrated in the following sections. + +Safely implementing \_\_wakeup +------------------------------ + +To safely implement ``__wakeup``, simply enclose your +implementation code in an identity check as follows: + +:: + + id) { + // ... Your code here as normal ... + } + // otherwise do nothing, do NOT throw an exception! + } + + //... + } + +Safely implementing \_\_clone +----------------------------- + +Safely implementing ``__clone`` is pretty much the same: + +:: + + id) { + // ... Your code here as normal ... + } + // otherwise do nothing, do NOT throw an exception! + } + + //... + } + +Summary +------- + +As you have seen, it is quite easy to safely make use of +``__wakeup`` and ``__clone`` in your entities without adding any +really Doctrine-specific or Doctrine-dependant code. + +These implementations are possible and safe because when Doctrine +invokes these methods, the entities never have an identity (yet). +Furthermore, it is possibly a good idea to check for the identity +in your code anyway, since it's rarely the case that you want to +unserialize or clone an entity with no identity. + + diff --git a/cookbook/en/implementing-wakeup-or-clone.txt b/cookbook/en/implementing-wakeup-or-clone.txt deleted file mode 100644 index efdefa7c3..000000000 --- a/cookbook/en/implementing-wakeup-or-clone.txt +++ /dev/null @@ -1,63 +0,0 @@ - -As explained in the [restrictions for entity classes in the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities), -it is usually not allowed for an entity to implement `__wakeup` or `__clone`, because Doctrine -makes special use of them. However, it is quite easy to make use of these methods in a safe way -by guarding the custom wakeup or clone code with an entity identity check, as demonstrated in the following sections. - -++ Safely implementing __wakeup - -To safely implement `__wakeup`, simply enclose your implementation code in an identity check -as follows: - - [php] - class MyEntity - { - private $id; // This is the identifier of the entity. - //... - - public function __wakeup() - { - // If the entity has an identity, proceed as normal. - if ($this->id) { - // ... Your code here as normal ... - } - // otherwise do nothing, do NOT throw an exception! - } - - //... - } - -++ Safely implementing __clone - -Safely implementing `__clone` is pretty much the same: - - [php] - class MyEntity - { - private $id; // This is the identifier of the entity. - //... - - public function __clone() - { - // If the entity has an identity, proceed as normal. - if ($this->id) { - // ... Your code here as normal ... - } - // otherwise do nothing, do NOT throw an exception! - } - - //... - } - -++ Summary - -As you have seen, it is quite easy to safely make use of `__wakeup` and `__clone` in your entities -without adding any really Doctrine-specific or Doctrine-dependant code. - -These implementations are possible and safe because when Doctrine invokes these methods, -the entities never have an identity (yet). Furthermore, it is possibly a good idea to check -for the identity in your code anyway, since it's rarely the case that you want to unserialize -or clone an entity with no identity. - - - diff --git a/cookbook/en/index.rst b/cookbook/en/index.rst new file mode 100644 index 000000000..4c4be6adc --- /dev/null +++ b/cookbook/en/index.rst @@ -0,0 +1,32 @@ +.. Doctrine 2 ORM Cookbook documentation master file, created by + sphinx-quickstart on Mon Nov 1 21:13:13 2010. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Doctrine 2 ORM Cookbook's documentation! +=================================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + getting-started-xml-edition + implementing-arrayaccess-for-domain-objects + implementing-the-notify-changetracking-policy + validations-of-entities + implementing-wakeup-or-clone + integrating-with-codeigniter + dql-custom-walkers + dql-user-defined-functions + sql-table-prefixes + strategy-cookbook-introdution + aggregates-fields + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/cookbook/en/integrating-with-codeigniter.txt b/cookbook/en/integrating-with-codeigniter.rst similarity index 56% rename from cookbook/en/integrating-with-codeigniter.txt rename to cookbook/en/integrating-with-codeigniter.rst index e058ec6b9..105df85f9 100644 --- a/cookbook/en/integrating-with-codeigniter.txt +++ b/cookbook/en/integrating-with-codeigniter.rst @@ -1,78 +1,92 @@ -This is recipe for using Doctrine 2 in your [CodeIgniter](http://www.codeigniter.com) framework. +Integrating with CodeIgniter +============================ + +This is recipe for using Doctrine 2 in your +`CodeIgniter `_ framework. + +.. note:: + + This might not work for all CodeIgniter versions and may require + slight adjustments. -> **NOTE** -> -> This might not work for all CodeIgniter versions and may require slight adjustments. Here is how to set it up: -Make a CodeIgniter library that is both a wrapper and a bootstrap for Doctrine 2. +Make a CodeIgniter library that is both a wrapper and a bootstrap +for Doctrine 2. -++ Setting up the file structure +Setting up the file structure +----------------------------- Here are the steps: -* Add a php file to your system/application/libraries folder called Doctrine.php. This is going to be your wrapper/bootstrap for the D2 entity manager. -* Put the Doctrine folder (the one that contains Common, DBAL, and ORM) inside that same libraries folder. -* Your system/application/libraries folder now looks like this: - system/applications/libraries - -Doctrine - -Doctrine.php - -index.html +- Add a php file to your system/application/libraries folder + called Doctrine.php. This is going to be your wrapper/bootstrap for + the D2 entity manager. +- Put the Doctrine folder (the one that contains Common, DBAL, and + ORM) inside that same libraries folder. +- Your system/application/libraries folder now looks like this: -* If you want, open your config/autoload.php file and autoload your Doctrine library. + system/applications/libraries -Doctrine -Doctrine.php -index.html - [php] - $autoload['libraries'] = array('doctrine'); +- If you want, open your config/autoload.php file and autoload + your Doctrine library. -++ Creating your Doctrine CodeIgniter library + register(); $entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" )); $entitiesClassLoader->register(); $proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies'); $proxiesClassLoader->register(); - + // Set up caches $config = new Configuration; $cache = new ArrayCache; $config->setMetadataCacheImpl($cache); $config->setQueryCacheImpl($cache); - + // Proxy configuration $config->setProxyDir(APPPATH.'/models/proxies'); $config->setProxyNamespace('Proxies'); - + // Set up logger $logger = new EchoSqlLogger; $config->setSqlLogger($logger); - + $config->setAutoGenerateProxyClasses( TRUE ); - + // Database connection information $connectionOptions = array( 'driver' => 'pdo_mysql', @@ -81,28 +95,42 @@ Now, here is what your Doctrine.php file should look like. Customize it to your 'host' => $db['default']['hostname'], 'dbname' => $db['default']['database'] ); - + // Create EntityManager $this->em = EntityManager::create($connectionOptions, $config); } } -Please note that this is a development configuration; for a production system you'll want to use a real caching system like APC, get rid of EchoSqlLogger, and turn off autoGenerateProxyClasses. +Please note that this is a development configuration; for a +production system you'll want to use a real caching system like +APC, get rid of EchoSqlLogger, and turn off +autoGenerateProxyClasses. -For more details, consult the [Doctrine 2 Configuration documentation](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). +For more details, consult the +`Doctrine 2 Configuration documentation `_. -++ Now to use it +Now to use it +------------- -Whenever you need a reference to the entity manager inside one of your controllers, views, or models you can do this: +Whenever you need a reference to the entity manager inside one of +your controllers, views, or models you can do this: - [php] +:: + + doctrine->em; -That's all there is to it. Once you get the reference to your EntityManager do your Doctrine 2.0 voodoo as normal. +That's all there is to it. Once you get the reference to your +EntityManager do your Doctrine 2.0 voodoo as normal. -Note: If you do not choose to autoload the Doctrine library, you will need to put this line before you get a reference to it: +Note: If you do not choose to autoload the Doctrine library, you +will need to put this line before you get a reference to it: - [php] +:: + + load->library('doctrine'); Good luck! + + diff --git a/cookbook/en/make.bat b/cookbook/en/make.bat new file mode 100644 index 000000000..9f1a938d9 --- /dev/null +++ b/cookbook/en/make.bat @@ -0,0 +1,113 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +set SPHINXBUILD=sphinx-build +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORMCookbook.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORMCookbook.ghc + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/cookbook/en/sql-table-prefixes.rst b/cookbook/en/sql-table-prefixes.rst new file mode 100644 index 000000000..8f3ee3809 --- /dev/null +++ b/cookbook/en/sql-table-prefixes.rst @@ -0,0 +1,73 @@ +SQL-Table Prefixes +================== + +This recipe is intended as an example of implementing a +loadClassMetadata listener to provide a Table Prefix option for +your application. The method used below is not a hack, but fully +integrates into the Doctrine system, all SQL generated will include +the appropriate table prefix. + +In most circumstances it is desirable to separate different +applications into individual databases, but in certain cases, it +may be beneficial to have a table prefix for your Entities to +separate them from other vendor products in the same database. + +Implementing the listener +------------------------- + +The listener in this example has been set up with the +DoctrineExtensions namespace. You create this file in your +library/DoctrineExtensions directory, but will need to set up +appropriate autoloaders. + +:: + + _prefix = (string) $prefix; + } + + public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) + { + $classMetadata = $eventArgs->getClassMetadata(); + $classMetadata->setTableName($this->_prefix . $classMetadata->getTableName()); + } + } + +Telling the EntityManager about our listener +-------------------------------------------- + +A listener of this type must be set up before the EntityManager has +been initialised, otherwise an Entity might be created or cached +before the prefix has been set. + +.. note:: + + If you set this listener up, be aware that you will need + to clear your caches and drop then recreate your database schema. + + +:: + + addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix); + + $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm); + + diff --git a/cookbook/en/sql-table-prefixes.txt b/cookbook/en/sql-table-prefixes.txt deleted file mode 100644 index eb7929959..000000000 --- a/cookbook/en/sql-table-prefixes.txt +++ /dev/null @@ -1,51 +0,0 @@ -This recipe is intended as an example of implementing a loadClassMetadata listener to provide a Table Prefix option for your application. The method used below is not a hack, but fully integrates into the Doctrine system, all SQL generated will include the appropriate table prefix. - -In most circumstances it is desirable to separate different applications into individual databases, but in certain cases, it may be beneficial to have a table prefix for your Entities to separate them from other vendor products in the same database. - -++ Implementing the listener - -The listener in this example has been set up with the DoctrineExtensions namespace. You create this file in your library/DoctrineExtensions directory, but will need to set up appropriate autoloaders. - - [php] - _prefix = (string) $prefix; - } - - public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) - { - $classMetadata = $eventArgs->getClassMetadata(); - $classMetadata->setTableName($this->_prefix . $classMetadata->getTableName()); - } - } - -++ Telling the EntityManager about our listener - -A listener of this type must be set up before the EntityManager has been initialised, otherwise an Entity might be created or cached before the prefix has been set. - -> **Note** -> If you set this listener up, be aware that you will need to clear your caches -> and drop then recreate your database schema. - - [php] - addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix); - - $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm); - diff --git a/cookbook/en/strategy-cookbook-introduction.txt b/cookbook/en/strategy-cookbook-introduction.rst similarity index 64% rename from cookbook/en/strategy-cookbook-introduction.txt rename to cookbook/en/strategy-cookbook-introduction.rst index 18cd0fe58..48a2a5366 100644 --- a/cookbook/en/strategy-cookbook-introduction.txt +++ b/cookbook/en/strategy-cookbook-introduction.rst @@ -1,39 +1,75 @@ +Strategy-Pattern +================ -This recipe will give you a short introduction on how to design similar entities without using expensive (i.e. slow) inheritance but with not more than -* the well-known strategy pattern -* event listeners +This recipe will give you a short introduction on how to design +similar entities without using expensive (i.e. slow) inheritance +but with not more than \* the well-known strategy pattern \* event +listeners -++ Scenario / Problem +Scenario / Problem +------------------ -Given a Content-Management-System, we probably want to add / edit some so-called "blocks" and "panels". What are they for? +Given a Content-Management-System, we probably want to add / edit +some so-called "blocks" and "panels". What are they for? -* A block might be a registration form, some text content, a table with information. A good example might also be a small calendar. -* A panel is by definition a block that can itself contain blocks. A good example for a panel might be a sidebar box: You could easily add a small calendar into it. -So, in this scenario, when building your CMS, you will surely add lots of blocks and panels to your pages and you will find yourself highly uncomfortable because of the following: +- A block might be a registration form, some text content, a table + with information. A good example might also be a small calendar. +- A panel is by definition a block that can itself contain blocks. + A good example for a panel might be a sidebar box: You could easily + add a small calendar into it. -* Every existing page needs to know about the panels it contains - therefore, you'll have an association to your panels. But if you've got several types of panels - what do you do? Add an association to -every panel-type? This wouldn't be flexible. You might be tempted to add an AbstractPanelEntity and an AbstractBlockEntity that use class inheritance. Your page could then only confer to the AbstractPanelType and Doctrine 2 would do the rest for you, i.e. load the right entities. But - you'll for sure have lots of panels and blocks, and even worse, you'd have to edit the discriminator map *manually* every time you or another developer implements a new block / entity. This would tear down any effort of modular programming. +So, in this scenario, when building your CMS, you will surely add +lots of blocks and panels to your pages and you will find yourself +highly uncomfortable because of the following: + + +- Every existing page needs to know about the panels it contains - + therefore, you'll have an association to your panels. But if you've + got several types of panels - what do you do? Add an association to + every panel-type? This wouldn't be flexible. You might be tempted + to add an AbstractPanelEntity and an AbstractBlockEntity that use + class inheritance. Your page could then only confer to the + AbstractPanelType and Doctrine 2 would do the rest for you, i.e. + load the right entities. But - you'll for sure have lots of panels + and blocks, and even worse, you'd have to edit the discriminator + map *manually* every time you or another developer implements a new + block / entity. This would tear down any effort of modular + programming. Therefore, we need something thats far more flexible. -++ Solution +Solution +-------- -The solution itself is pretty easy. We will have one base class that will be loaded via the page and that has specific behaviour - a Block class might render the front-end and even the backend, for example. Now, every block that you'll write might look different or need different data - therefore, we'll offer an API to these methods but internally, we use a strategy that exactly knows what to do. +The solution itself is pretty easy. We will have one base class +that will be loaded via the page and that has specific behaviour - +a Block class might render the front-end and even the backend, for +example. Now, every block that you'll write might look different or +need different data - therefore, we'll offer an API to these +methods but internally, we use a strategy that exactly knows what +to do. -First of all, we need to make sure that we have an interface that contains every needed action. Such actions would be rendering the front-end or the backend, solving dependencies (blocks that are supposed to be placed in the sidebar could refuse to be placed in the middle of your page, for example). +First of all, we need to make sure that we have an interface that +contains every needed action. Such actions would be rendering the +front-end or the backend, solving dependencies (blocks that are +supposed to be placed in the sidebar could refuse to be placed in +the middle of your page, for example). Such an interface could look like this: - [php] - /** - * This interface defines the basic actions that a block / panel needs to support. - * - * Every blockstrategy is *only* responsible for rendering a block and declaring some basic - * support, but *not* for updating its configuration etc. For this purpose, use controllers - * and models. - */ - interface BlockStrategyInterface { + +:: + + blockStrategy. Will not be persisted by Doctrine 2 + * This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2. + * * @var BlockStrategyInterface */ protected $strategyInstance; @@ -139,7 +175,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg public function getStrategyClassName() { return $this->strategyClassName; } - + /** * Returns the instantiated strategy * @@ -148,7 +184,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg public function getStrategyInstance() { return $this->strategyInstance; } - + /** * Sets the strategy this block / panel should work as. Make sure that you've used * this method before persisting the block! @@ -161,35 +197,42 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg $strategy->setBlockEntity($this); } -Now, the important point is that $strategyClassName is a Doctrine 2 field, i.e. Doctrine will persist this value. This is only the class name of your strategy and not an instance! +Now, the important point is that $strategyClassName is a Doctrine 2 +field, i.e. Doctrine will persist this value. This is only the +class name of your strategy and not an instance! -Finishing your strategy pattern, we hook into the Doctrine postLoad event and check whether a block has been loaded. If so, you will initialize it - i.e. get the strategies classname, create an instance of it and set it via setStrategyBlock(). +Finishing your strategy pattern, we hook into the Doctrine postLoad +event and check whether a block has been loaded. If so, you will +initialize it - i.e. get the strategies classname, create an +instance of it and set it via setStrategyBlock(). This might look like this: - [php] +:: + + view = $view; } - + public function getSubscribedEvents() { return array(ORM\Events::postLoad); } - + public function postLoad(ORM\Event\LifecycleEventArgs $args) { $blockItem = $args->getEntity(); - + // Both blocks and panels are instances of Block\AbstractBlock if ($blockItem instanceof Block\AbstractBlock) { $strategy = $blockItem->getStrategyClassName(); @@ -203,4 +246,7 @@ This might look like this: } } -In this example, even some variables are set - like a view object or a specific configuration object. +In this example, even some variables are set - like a view object +or a specific configuration object. + + diff --git a/cookbook/en/validation-of-entities.rst b/cookbook/en/validation-of-entities.rst new file mode 100644 index 000000000..ab15f58e0 --- /dev/null +++ b/cookbook/en/validation-of-entities.rst @@ -0,0 +1,141 @@ +Validation of Entities +====================== + +Doctrine 2 does not ship with any internal validators, the reason +being that we think all the frameworks out there already ship with +quite decent ones that can be integrated into your Domain easily. +What we offer are hooks to execute any kind of validation. + +.. note:: + + You don't need to validate your entities in the lifecycle + events. Its only one of many options. Of course you can also + perform validations in value setters or any other method of your + entities that are used in your code. + + +Entities can register lifecycle event methods with Doctrine that +are called on different occasions. For validation we would need to +hook into the events called before persisting and updating. Even +though we don't support validation out of the box, the +implementation is even simpler than in Doctrine 1 and you will get +the additional benefit of being able to re-use your validation in +any other part of your domain. + +Say we have an ``Order`` with several ``OrderLine`` instances. We +never want to allow any customer to order for a larger sum than he +is allowed to: + +:: + + customer->getOrderLimit(); + + $amount = 0; + foreach ($this->orderLines AS $line) { + $amount += $line->getAmount(); + } + + if ($amount > $orderLimit) { + throw new CustomerOrderLimitExceededException(); + } + } + } + +Now this is some pretty important piece of business logic in your +code, enforcing it at any time is important so that customers with +a unknown reputation don't owe your business too much money. + +We can enforce this constraint in any of the metadata drivers. +First Annotations: + +:: + + + + + + + + + + +YAML needs some little change yet, to allow multiple lifecycle +events for one method, this will happen before Beta 1 though. + +Now validation is performed whenever you call +``EntityManager#persist($order)`` or when you call +``EntityManager#flush()`` and an order is about to be updated. Any +Exception that happens in the lifecycle callbacks will be cached by +the EntityManager and the current transaction is rolled back. + +Of course you can do any type of primitive checks, not null, +email-validation, string size, integer and date ranges in your +validation callbacks. + +:: + + plannedShipDate instanceof DateTime)) { + throw new ValidateException(); + } + + if ($this->plannedShipDate->format('U') < time()) { + throw new ValidateException(); + } + + if ($this->customer == null) { + throw new OrderRequiresCustomerException(); + } + } + } + +What is nice about lifecycle events is, you can also re-use the +methods at other places in your domain, for example in combination +with your form library. Additionally there is no limitation in the +number of methods you register on one particular event, i.e. you +can register multiple methods for validation in "PrePersist" or +"PreUpdate" or mix and share them in any combinations between those +two events. + +There is no limit to what you can and can't validate in +"PrePersist" and "PreUpdate" as long as you don't create new entity +instances. This was already discussed in the previous blog post on +the Versionable extension, which requires another type of event +called "onFlush". + +Further readings: + + +- `Doctrine 2 Manual: Events `_ + + diff --git a/cookbook/en/validation-of-entities.txt b/cookbook/en/validation-of-entities.txt deleted file mode 100644 index 047fed22d..000000000 --- a/cookbook/en/validation-of-entities.txt +++ /dev/null @@ -1,114 +0,0 @@ -Doctrine 2 does not ship with any internal validators, the reason being that -we think all the frameworks out there already ship with quite decent ones that can be integrated -into your Domain easily. What we offer are hooks to execute any kind of validation. - -> **Note** -> You don't need to validate your entities in the lifecycle events. Its only -> one of many options. Of course you can also perform validations in value setters -> or any other method of your entities that are used in your code. - -Entities can register lifecycle event methods with Doctrine that are called on -different occasions. For validation we would need to hook into the -events called before persisting and updating. Even though we don't support -validation out of the box, the implementation is even simpler than in Doctrine 1 -and you will get the additional benefit of being able to re-use your validation -in any other part of your domain. - -Say we have an `Order` with several `OrderLine` instances. We never want to -allow any customer to order for a larger sum than he is allowed to: - - [php] - class Order - { - public function assertCustomerAllowedBuying() - { - $orderLimit = $this->customer->getOrderLimit(); - - $amount = 0; - foreach ($this->orderLines AS $line) { - $amount += $line->getAmount(); - } - - if ($amount > $orderLimit) { - throw new CustomerOrderLimitExceededException(); - } - } - } - -Now this is some pretty important piece of business logic in your code, enforcing -it at any time is important so that customers with a unknown reputation don't -owe your business too much money. - -We can enforce this constraint in any of the metadata drivers. First Annotations: - - [php] - /** - * @Entity - * @HasLifecycleCallbacks - */ - class Order - { - /** - * @PrePersist @PreUpdate - */ - public function assertCustomerAllowedBuying() {} - } - -In XML Mappings: - - [xml] - - - - - - - - - -YAML needs some little change yet, to allow multiple lifecycle events for one method, -this will happen before Beta 1 though. - -Now validation is performed whenever you call `EntityManager#persist($order)` -or when you call `EntityManager#flush()` and an order is about to be updated. -Any Exception that happens in the lifecycle callbacks will be cached by the -EntityManager and the current transaction is rolled back. - -Of course you can do any type of primitive checks, not null, email-validation, string size, -integer and date ranges in your validation callbacks. - - [php] - class Order - { - /** - * @PrePersist @PreUpdate - */ - public function validate() - { - if (!($this->plannedShipDate instanceof DateTime)) { - throw new ValidateException(); - } - - if ($this->plannedShipDate->format('U') < time()) { - throw new ValidateException(); - } - - if ($this->customer == null) { - throw new OrderRequiresCustomerException(); - } - } - } - -What is nice about lifecycle events is, you can also re-use the methods at other places -in your domain, for example in combination with your form library. -Additionally there is no limitation in the number of methods you register -on one particular event, i.e. you can register multiple methods for validation in "PrePersist" -or "PreUpdate" or mix and share them in any combinations between those two events. - -There is no limit to what you can and can't validate in "PrePersist" and "PreUpdate" as long as -you don't create new entity instances. This was already discussed in the previous -blog post on the Versionable extension, which requires another type of event called "onFlush". - -Further readings: - -* [Doctrine 2 Manual: Events](http://www.doctrine-project.org/documentation/manual/2_0/en/events#lifecycle-events) \ No newline at end of file diff --git a/generate-docs.sh b/generate-docs.sh new file mode 100755 index 000000000..14fdb721b --- /dev/null +++ b/generate-docs.sh @@ -0,0 +1,2 @@ +#!/bin/bash +sphinx-build reference/en /var/www/docs \ No newline at end of file diff --git a/manual/en.rst b/manual/en.rst new file mode 100644 index 000000000..f66e1b2fd --- /dev/null +++ b/manual/en.rst @@ -0,0 +1,79 @@ +Introduction +============ + +Architecture +============ + +Configuration +============= + +Basic Mapping +============= + +Association Mapping +=================== + +Inheritance Mapping +=================== + +Working with objects +==================== + +Working with associations +========================= + +Transactions and Concurrency +============================ + +Events +====== + +Batch processing +================ + +DQL (Doctrine Query Language) +============================= + +Query Builder +============= + +Native SQL +========== + +Change Tracking Policies +======================== + +Partial Objects +=============== + +XML Mapping +=========== + +YAML Mapping +============ + +Annotations Reference +===================== + +PHP Mapping +=========== + +Caching +======= + +Improving Performance +===================== + +Tools +===== + +Metadata Drivers +================ + +Best Practices +============== + +Limitations and Known Issues +============================ + + diff --git a/manual/en.txt b/manual/en.txt index 460439100..95d8bdbec 100644 --- a/manual/en.txt +++ b/manual/en.txt @@ -1,26 +1,26 @@ -+ Introduction -+ Architecture -+ Configuration -+ Basic Mapping -+ Association Mapping -+ Inheritance Mapping -+ Working with objects -+ Working with associations -+ Transactions and Concurrency -+ Events -+ Batch processing -+ DQL (Doctrine Query Language) -+ Query Builder -+ Native SQL -+ Change Tracking Policies -+ Partial Objects -+ XML Mapping -+ YAML Mapping -+ Annotations Reference -+ PHP Mapping -+ Caching -+ Improving Performance -+ Tools -+ Metadata Drivers -+ Best Practices -+ Limitations and Known Issues \ No newline at end of file +# Introduction +# Architecture +# Configuration +# Basic Mapping +# Association Mapping +# Inheritance Mapping +# Working with objects +# Working with associations +# Transactions and Concurrency +# Events +# Batch processing +# DQL (Doctrine Query Language) +# Query Builder +# Native SQL +# Change Tracking Policies +# Partial Objects +# XML Mapping +# YAML Mapping +# Annotations Reference +# PHP Mapping +# Caching +# Improving Performance +# Tools +# Metadata Drivers +# Best Practices +# Limitations and Known Issues \ No newline at end of file diff --git a/manual/en/annotations-reference.rst b/manual/en/annotations-reference.rst new file mode 100644 index 000000000..d7515c38a --- /dev/null +++ b/manual/en/annotations-reference.rst @@ -0,0 +1,769 @@ +In this chapter a reference of every Doctrine 2 Annotation is given +with short explanations on their context and usage. + +Index +----- + + +- `@Column <#ann_column>`_ +- `@ChangeTrackingPolicy <#ann_changetrackingpolicy>`_ +- `@DiscriminatorColumn <#ann_discriminatorcolumn>`_ +- `@DiscriminatorMap <#ann_discriminatormap>`_ +- `@Entity <#ann_entity>`_ +- `@GeneratedValue <#ann_generatedvalue>`_ +- `@HasLifecycleCallbacks <#ann_haslifecyclecallbacks>`_ +- `@Index <#ann_indexes>`_ +- `@Id <#ann_id>`_ +- `@InheritanceType <#ann_inheritancetype>`_ +- `@JoinColumn <#ann_joincolumn>`_ +- `@JoinTable <#ann_jointable>`_ +- `@ManyToOne <#ann_manytoone>`_ +- `@ManyToMany <#ann_manytomany>`_ +- `@MappedSuperclass <#ann_mappedsuperclass>`_ +- `@OneToOne <#ann_onetoone>`_ +- `@OneToMany <#ann_onetomany>`_ +- `@OrderBy <#ann_orderby>`_ +- `@PostLoad <#ann_postload>`_ +- `@PostPersist <#ann_postpersist>`_ +- `@PostRemove <#ann_postremove>`_ +- `@PostUpdate <#ann_postupdate>`_ +- `@PrePersist <#ann_prepersist>`_ +- `@PreRemove <#ann_preremove>`_ +- `@PreUpdate <#ann_preupdate>`_ +- `@SequenceGenerator <#ann_sequencegenerator>`_ +- `@Table <#ann_table>`_ +- `@UniqueConstraint <#ann_uniqueconstraint>`_ +- `@Version <#ann_version>`_ + +Reference +--------- + +### @Column + +Marks an annotated instance variable as "persistent". It has to be +inside the instance variables PHP DocBlock comment. Any value hold +inside this variable will be saved to and loaded from the database +as part of the lifecycle of the instance variables entity-class. + +Required attributes: + + +- type - Name of the Doctrine Type which is converted between PHP + and Database representation. + +Optional attributes: + + +- name - By default the property name is used for the database + column name also, however the 'name' attribute allows you to + determine the column name. +- length - Used by the "string" type to determine its maximum + length in the database. Doctrine does not validate the length of a + string values for you. +- precision - The precision for a decimal (exact numeric) column + (Applies only for decimal column) +- scale - The scale for a decimal (exact numeric) column (Applies + only for decimal column) +- unique - Boolean value to determine if the value of the column + should be unique across all rows of the underlying entities table. +- nullable - Determines if NULL values allowed for this column. +- columnDefinition - DDL SQL snippet that starts after the column + name and specifies the complete (non-portable!) column definition. + This attribute allows to make use of advanced RMDBS features. + However you should make careful use of this feature and the + consequences. Additionally you should remember that the "type" + attribute still handles the conversion between PHP and Database + values. If you use this attribute on a column that is used for + joins between tables you should also take a look at + `@JoinColumn <#ann_joincolumn>`_. + +Examples: + +:: + + `_ +can be found in the configuration section. + +Example: + +:: + + `_. This +annotation is optional and only has meaning when used in +conjunction with @Id. + +If this annotation is not specified with @Id the NONE strategy is +used as default. + +Required attributes: + + +- strategy - Set the name of the identifier generation strategy. + Valid values are AUTO, SEQUENCE, TABLE, IDENTITY and NONE. + +Example: + +:: + + `_ annotation on +the entity-class level. It allows to hint the SchemaTool to +generate a database index on the specified table columns. It only +has meaning in the SchemaTool schema generation context. + +Required attributes: + + +- name - Name of the Index +- columns - Array of columns. + +Example: + +:: + + `_ and +`@DiscriminatorColumn <#ann_discriminatorcolumn>`_ annotations. + +Examples: + +:: + + `_, `@OneToOne <#ann_onetoone>`_ fields +and in the Context of `@JoinTable <#ann_jointable>`_ nested inside +a @ManyToMany. This annotation is not required. If its not +specified the attributes *name* and *referencedColumnName* are +inferred from the table and primary key names. + +Required attributes: + + +- name - Column name that holds the foreign key identifier for + this relation. In the context of @JoinTable it specifies the column + name in the join table. +- referencedColumnName - Name of the primary key identifier that + is used for joining of this relation. + +Optional attributes: + + +- unique - Determines if this relation exclusive between the + affected entities and should be enforced so on the database + constraint level. Defaults to false. +- nullable - Determine if the related entity is required, or if + null is an allowed state for the relation. Defaults to true. +- onDelete - Cascade Action (Database-level) +- onUpdate - Cascade Action (Database-level) +- columnDefinition - DDL SQL snippet that starts after the column + name and specifies the complete (non-portable!) column definition. + This attribute allows to make use of advanced RMDBS features. Using + this attribute on @JoinColumn is necessary if you need slightly + different column definitions for joining columns, for example + regarding NULL/NOT NULL defaults. However by default a + "columnDefinition" attribute on `@Column <#ann_column>`_ also sets + the related @JoinColumn's columnDefinition. This is necessary to + make foreign keys work. + +Example: + +:: + + `_ or `@OneToOne <#ann_onetoone>`_ +relation with an entity that has multiple identifiers. + +### @JoinTable + +Using `@OneToMany <#ann_onetomany>`_ or +`@ManyToMany <#ann_manytomany>`_ on the owning side of the relation +requires to specify the @JoinTable annotation which describes the +details of the database join table. If you do not specify +@JoinTable on these relations reasonable mapping defaults apply +using the affected table and the column names. + +Required attributes: + + +- name - Database name of the join-table +- joinColumns - An array of @JoinColumn annotations describing the + join-relation between the owning entities table and the join table. +- inverseJoinColumns - An array of @JoinColumn annotations + describing the join-relation between the inverse entities table and + the join table. + +Optional attributes: + + +- schema - Database schema name of this table. + +Example: + +:: + + `_ is an +additional, optional annotation that has reasonable default +configuration values using the table and names of the two related +entities. + +Required attributes: + + +- targetEntity - FQCN of the referenced target entity. Can be the + unqualified class name if both classes are in the same namespace. + *IMPORTANT:* No leading backslash! + +Optional attributes: + + +- mappedBy - This option specifies the property name on the + targetEntity that is the owning side of this relation. Its a + required attribute for the inverse side of a relationship. +- inversedBy - The inversedBy attribute designates the field in the + entity that is the inverse side of the relationship. +- cascade - Cascade Option +- fetch - One of LAZY or EAGER + + **NOTE** For ManyToMany bidirectional relationships either side may + be the owning side (the side that defines the @JoinTable and/or + does not make use of the mappedBy attribute, thus using a default + join table). + + +Example: + +:: + + `_. + +### @OneToOne + +The @OneToOne annotation works almost exactly as the +`@ManyToOne <#ann_manytoone>`_ with one additional option that can +be specified. The configuration defaults for +`@JoinColumn <#ann_joincolumn>`_ using the target entity table and +primary key column names apply here too. + +Required attributes: + + +- targetEntity - FQCN of the referenced target entity. Can be the + unqualified class name if both classes are in the same namespace. + *IMPORTANT:* No leading backslash! + +Optional attributes: + + +- cascade - Cascade Option +- fetch - One of LAZY or EAGER +- orphanRemoval - Boolean that specifies if orphans, inverse + OneToOne entities that are not connected to any owning instance, + should be removed by Doctrine. Defaults to false. +- inversedBy - The inversedBy attribute designates the field in the + entity that is the inverse side of the relationship. + +Example: + +:: + + `_ or `@OneToMany <#ann_onetomany>`_ +annotation to specify by which criteria the collection should be +retrieved from the database by using an ORDER BY clause. + +This annotation requires a single non-attributed value with an DQL +snippet: + +Example: + +:: + + `_ annotation on +the entity-class level. It allows to hint the SchemaTool to +generate a database unique constraint on the specified table +columns. It only has meaning in the SchemaTool schema generation +context. + +Required attributes: + + +- name - Name of the Index +- columns - Array of columns. + +Example: + +:: + + `_ annotations that have the type integer or +datetime. + +Example: + +:: + + -+++ @Column +### @Column Marks an annotated instance variable as "persistent". It has to be inside the instance variables PHP DocBlock comment. Any value hold inside this variable will be saved to and loaded from the database as part of the lifecycle of the instance variables entity-class. @@ -56,7 +56,7 @@ Optional attributes: Examples: - [php] + -+++ @ChangeTrackingPolicy +### @ChangeTrackingPolicy The Change Tracking Policy annotation allows to specify how the Doctrine 2 UnitOfWork should detect changes in properties of entities during flush. By default each entity is checked according to a deferred implicit @@ -86,7 +86,7 @@ can be found in the configuration section. Example: - [php] + -+++ @DiscrimnatorColumn +### @DiscrimnatorColumn This annotation is a required annotation for the topmost/super class of an inheritance hierarchy. It specifies the details of the column which saves the name of the class, which the entity is actually instantiated as. @@ -111,14 +111,14 @@ Optional attributes: * length - By default this is 255. -+++ @DiscriminatorMap +### @DiscriminatorMap The discriminator map is a required annotation on the top-most/super class in an inheritance hierarchy. It takes an array as only argument which defines which class should be saved under which name in the database. Keys are the database value and values are the classes, either as fully- or as unqualified class names depending if the classes are in the namespace or not. - [php] + -+++ @Entity +### @Entity Required annotation to mark a PHP class as Entity. Doctrine manages the persistence of all classes marked as entity. @@ -142,7 +142,7 @@ Optional attributes: Example: - [php] + -+++ @GeneratedValue +### @GeneratedValue Specifies which strategy is used for identifier generation for an instance variable which is annotated by [@Id](#ann_id). This annotation is optional and only has meaning when used in conjunction with @Id. @@ -165,7 +165,7 @@ Required attributes: Example: - [php] + -+++ @HasLifecycleCallbacks +### @HasLifecycleCallbacks Annotation which has to be set on the entity-class PHP DocBlock to notify Doctrine that this entity has entity life-cycle callback annotations set on at least one of its methods. Using @PostLoad, @PrePersist, @PostPersist, @PreRemove, @PostRemove, @@ -182,7 +182,7 @@ callback annotations set on at least one of its methods. Using @PostLoad, @PrePe Example: - [php] + -+++ @Index +### @Index Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the SchemaTool to generate a database index on the specified table columns. It only has meaning in the SchemaTool @@ -209,7 +209,7 @@ Required attributes: Example: - [php] + -+++ @Id +### @Id The annotated instance variable will be marked as entity identifier, the primary key in the database. This annotation is a marker only and has no required or optional attributes. For entities that have multiple @@ -227,7 +227,7 @@ identifier columns each column has to be marked with @Id. Example: - [php] + -+++ @InheritanceType +### @InheritanceType In an inheritance hierarchy you have to use this annotation on the topmost/super class to define which strategy should be used for inheritance. Currently Single Table and Class Table Inheritance are supported. @@ -245,7 +245,7 @@ This annotation has always been used in conjunction with the [@DiscriminatorMap] Examples: - [php] + -+++ @JoinColumn +### @JoinColumn This annotation is used in the context of relations in [@ManyToOne](#ann_manytoone), [@OneToOne](#ann_onetoone) fields and in the Context of [@JoinTable](#ann_jointable) nested inside a @ManyToMany. This annotation is not required. @@ -290,7 +290,7 @@ Optional attributes: Example: - [php] + -+++ @JoinColumns +### @JoinColumns An array of @JoinColumn annotations for a [@ManyToOne](#ann_manytoone) or [@OneToOne](#ann_onetoone) relation with an entity that has multiple identifiers. -+++ @JoinTable +### @JoinTable Using [@OneToMany](#ann_onetomany) or [@ManyToMany](#ann_manytomany) on the owning side of the relation requires to specify the @JoinTable annotation which describes the details of the database join table. If you do not specify @JoinTable on @@ -322,7 +322,7 @@ Optional attributes: Example: - [php] + -+++ @ManyToOne +### @ManyToOne Defines that the annotated instance variable holds a reference that describes a many-to-one relationship between two entities. @@ -349,14 +349,14 @@ Optional attributes: Example: - [php] + -+++ @ManyToMany +### @ManyToMany Defines an instance variable holds a many-to-many relationship between two entities. [@JoinTable](#ann_jointable) is an additional, optional annotation that has reasonable default configuration values using the table @@ -380,7 +380,7 @@ Optional attributes: Example: - [php] + -+++ @MappedSuperclass +### @MappedSuperclass An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information for its subclasses, but which is not itself an entity. This annotation is specified on the Class docblock @@ -410,7 +410,7 @@ The @MappedSuperclass annotation cannot be used in conjunction with @Entity. See section for [more details on the restrictions of mapped superclasses](/../inheritance-mapping#mapped-superclasses). -+++ @OneToOne +### @OneToOne The @OneToOne annotation works almost exactly as the [@ManyToOne](#ann_manytoone) with one additional option that can be specified. The configuration defaults for [@JoinColumn](#ann_joincolumn) using the target entity table and primary key column names @@ -430,7 +430,7 @@ owning instance, should be removed by Doctrine. Defaults to false. Example: - [php] + -+++ @OneToMany +### @OneToMany Required attributes: @@ -453,14 +453,14 @@ owning instance, should be removed by Doctrine. Defaults to false. Example: - [php] + -+++ @OrderBy +### @OrderBy Optional annotation that can be specified with a [@ManyToMany](#ann_manytomany) or [@OneToMany](#ann_onetomany) annotation to specify by which criteria the collection should be retrieved from the database by using an ORDER BY @@ -470,7 +470,7 @@ This annotation requires a single non-attributed value with an DQL snippet: Example: - [php] + -+++ @PostLoad +### @PostLoad Marks a method on the entity to be called as a @PostLoad event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -+++ @PostPersist +### @PostPersist Marks a method on the entity to be called as a @PostPersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -+++ @PostRemove +### @PostRemove Marks a method on the entity to be called as a @PostRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -+++ @PostUpdate +### @PostUpdate Marks a method on the entity to be called as a @PostUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -+++ @PrePersist +### @PrePersist Marks a method on the entity to be called as a @PrePersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -+++ @PreRemove +### @PreRemove Marks a method on the entity to be called as a @PreRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -+++ @PreUpdate +### @PreUpdate Marks a method on the entity to be called as a @PreUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -+++ @SequenceGenerator +### @SequenceGenerator For the use with @generatedValue(strategy="SEQUENCE") this annotation allows to specify details about the sequence, such as the increment size and initial values of the sequence. @@ -535,7 +535,7 @@ Optional attributes: Example: - [php] + -+++ @Table +### @Table Annotation describes the table an entity is persisted in. It is placed on the entity-class PHP DocBlock and is optional. If it is not specified the table name will default to the entities unqualified classname. @@ -562,7 +562,7 @@ Optional attributes: Example: - [php] + -+++ @UniqueConstraint +### @UniqueConstraint Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the SchemaTool to generate a database unique constraint on the specified table columns. It only has meaning in the SchemaTool @@ -586,7 +586,7 @@ Required attributes: Example: - [php] + -+++ @Version +### @Version Marker annotation that defines a specified column as version attribute used in an optimistic locking scenario. It only works on [@Column](#ann_column) annotations that have the type integer or datetime. Example: - [php] + `_. +- An entity class must not implement ``__wakeup`` or + `do so safely `_. + Also consider implementing + `Serializable `_ + instead. +- Any two entity classes in a class hierarchy that inherit + directly or indirectly from one another must not have a mapped + property with the same name. That is, if B inherits from A then B + must not have a mapped field with the same name as an already + mapped field that is inherited from A. + +Entities support inheritance, polymorphic associations, and +polymorphic queries. Both abstract and concrete classes can be +entities. Entities may extend non-entity classes as well as entity +classes, and non-entity classes may extend entity classes. + + **TIP** The constructor of an entity is only ever invoked when + *you* construct a new instance with the *new* keyword. Doctrine + never calls entity constructors, thus you are free to use them as + you wish and even have it require arguments of any type. + + +Entity states +~~~~~~~~~~~~~ + +An entity instance can be characterized as being NEW, MANAGED, +DETACHED or REMOVED. + + +- A NEW entity instance has no persistent identity, and is not yet + associated with an EntityManager and a UnitOfWork (i.e. those just + created with the "new" operator). +- A MANAGED entity instance is an instance with a persistent + identity that is associated with an EntityManager and whose + persistence is thus managed. +- A DETACHED entity instance is an instance with a persistent + identity that is not (or no longer) associated with an + EntityManager and a UnitOfWork. +- A REMOVED entity instance is an instance with a persistent + identity, associated with an EntityManager, that will be removed + from the database upon transaction commit. + +Persistent fields +~~~~~~~~~~~~~~~~~ + +The persistent state of an entity is represented by instance +variables. An instance variable must be directly accessed only from +within the methods of the entity by the entity instance itself. +Instance variables must not be accessed by clients of the entity. +The state of the entity is available to clients only through the +entity’s methods, i.e. accessor methods (getter/setter methods) or +other business methods. + +Collection-valued persistent fields and properties must be defined +in terms of the ``Doctrine\Common\Collections\Collection`` +interface. The collection implementation type may be used by the +application to initialize fields or properties before the entity is +made persistent. Once the entity becomes managed (or detached), +subsequent access must be through the interface type. + +Serializing entities +~~~~~~~~~~~~~~~~~~~~ + +Serializing entities can be problematic and is not really +recommended, at least not as long as an entity instance still holds +references to proxy objects or is still managed by an +EntityManager. If you intend to serialize (and unserialize) entity +instances that still hold references to proxy objects you may run +into problems with private properties because of technical +limitations. Proxy objects implement ``__sleep`` and it is not +possible for ``__sleep`` to return names of private properties in +parent classes. On the other hand it is not a solution for proxy +objects to implement ``Serializable`` because Serializable does not +work well with any potential cyclic object references (at least we +did not find a way yet, if you did, please contact us). + +The EntityManager +----------------- + +The ``EntityManager`` class is a central access point to the ORM +functionality provided by Doctrine 2. The ``EntityManager`` API is +used to manage the persistence of your objects and to query for +persistent objects. + +Transactional write-behind +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An ``EntityManager`` and the underlying ``UnitOfWork`` employ a +strategy called "transactional write-behind" that delays the +execution of SQL statements in order to execute them in the most +efficient way and to execute them at the end of a transaction so +that all write locks are quickly released. You should see Doctrine +as a tool to synchronize your in-memory objects with the database +in well defined units of work. Work with your objects and modify +them as usual and when you're done call ``EntityManager#flush()`` +to make your changes persistent. + +The Unit of Work +~~~~~~~~~~~~~~~~ + +Internally an ``EntityManager`` uses a ``UnitOfWork``, which is a +typical implementation of the +`Unit of Work pattern `_, +to keep track of all the things that need to be done the next time +``flush`` is invoked. You usually do not directly interact with a +``UnitOfWork`` but with the ``EntityManager`` instead. + + diff --git a/manual/en/architecture.txt b/manual/en/architecture.txt index e9ef689d5..7305c8418 100644 --- a/manual/en/architecture.txt +++ b/manual/en/architecture.txt @@ -1,7 +1,7 @@ This chapter gives an overview of the overall architecture, terminology and constraints of Doctrine 2. It is recommended to read this chapter carefully. -++ Entities +## Entities An entity is a lightweight, persistent domain object. An entity can be any regular PHP class observing the following restrictions: @@ -23,7 +23,7 @@ classes as well as entity classes, and non-entity classes may extend entity clas > with the *new* keyword. Doctrine never calls entity constructors, thus you are free to use > them as you wish and even have it require arguments of any type. -+++ Entity states +### Entity states An entity instance can be characterized as being NEW, MANAGED, DETACHED or REMOVED. @@ -32,7 +32,7 @@ An entity instance can be characterized as being NEW, MANAGED, DETACHED or REMOV * A DETACHED entity instance is an instance with a persistent identity that is not (or no longer) associated with an EntityManager and a UnitOfWork. * A REMOVED entity instance is an instance with a persistent identity, associated with an EntityManager, that will be removed from the database upon transaction commit. -+++ Persistent fields +### Persistent fields The persistent state of an entity is represented by instance variables. An instance variable must be directly accessed only from within the methods of the @@ -47,7 +47,7 @@ implementation type may be used by the application to initialize fields or properties before the entity is made persistent. Once the entity becomes managed (or detached), subsequent access must be through the interface type. -+++ Serializing entities +### Serializing entities Serializing entities can be problematic and is not really recommended, at least not as long as an entity instance still holds references to proxy objects or is still managed by an EntityManager. @@ -58,13 +58,13 @@ private properties in parent classes. On the other hand it is not a solution for to implement `Serializable` because Serializable does not work well with any potential cyclic object references (at least we did not find a way yet, if you did, please contact us). -++ The EntityManager +## The EntityManager The `EntityManager` class is a central access point to the ORM functionality provided by Doctrine 2. The `EntityManager` API is used to manage the persistence of your objects and to query for persistent objects. -+++ Transactional write-behind +### Transactional write-behind An `EntityManager` and the underlying `UnitOfWork` employ a strategy called "transactional write-behind" that delays the execution of SQL statements in @@ -74,7 +74,7 @@ Doctrine as a tool to synchronize your in-memory objects with the database in well defined units of work. Work with your objects and modify them as usual and when you're done call `EntityManager#flush()` to make your changes persistent. -+++ The Unit of Work +### The Unit of Work Internally an `EntityManager` uses a `UnitOfWork`, which is a typical implementation of the [Unit of Work pattern](http://martinfowler.com/eaaCatalog/unitOfWork.html), to keep track of all the things that need to be done diff --git a/manual/en/association-mapping.rst b/manual/en/association-mapping.rst new file mode 100644 index 000000000..de7b72790 --- /dev/null +++ b/manual/en/association-mapping.rst @@ -0,0 +1,969 @@ +This chapter explains how associations between entities are mapped +with Doctrine. We start out with an explanation of the concept of +owning and inverse sides which is important to understand when +working with bidirectional associations. Please read these +explanations carefully. + +Owning Side and Inverse Side +---------------------------- + +When mapping bidirectional associations it is important to +understand the concept of the owning and inverse sides. The +following general rules apply: + + +- Relationships may be bidirectional or unidirectional. +- A bidirectional relationship has both an owning side and an + inverse side. +- A unidirectional relationship only has an owning side. +- The owning side of a relationship determines the updates to the + relationship in the database. + +The following rules apply to *bidirectional* associations: + + +- The inverse side of a bidirectional relationship must refer to + its owning side by use of the mappedBy attribute of the OneToOne, + OneToMany, or ManyToMany mapping declaration. The mappedBy + attribute designates the field in the entity that is the owner of + the relationship. +- The owning side of a bidirectional relationship must refer to + its inverse side by use of the inversedBy attribute of the + OneToOne, ManyToOne, or ManyToMany mapping declaration. The + inversedBy attribute designates the field in the entity that is the + inverse side of the relationship. +- The many side of OneToMany/ManyToOne bidirectional relationships + *must* be the owning side, hence the mappedBy element can not be + specified on the ManyToOne side. +- For OneToOne bidirectional relationships, the owning side + corresponds to the side that contains the corresponding foreign key + (@JoinColumn(s)). +- For ManyToMany bidirectional relationships either side may be + the owning side (the side that defines the @JoinTable and/or does + not make use of the mappedBy attribute, thus using a default join + table). + +Especially important is the following: + +**The owning side of a relationship determines the updates to the relationship in the database**. + +To fully understand this, remember how bidirectional associations +are maintained in the object world. There are 2 references on each +side of the association and these 2 references both represent the +same association but can change independently of one another. Of +course, in a correct application the semantics of the bidirectional +association are properly maintained by the application developer +(that's his responsibility). Doctrine needs to know which of these +two in-memory references is the one that should be persisted and +which not. This is what the owning/inverse concept is mainly used +for. + +**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)** + +The owning side of a bidirectional association is the side Doctrine +"looks at" when determining the state of the association, and +consequently whether there is anything to do to update the +association in the database. + + **NOTE** "Owning side" and "inverse side" are technical concepts of + the ORM technology, not concepts of your domain model. What you + consider as the owning side in your domain model can be different + from what the owning side is for Doctrine. These are unrelated. + + +Collections +----------- + +In all the examples of many-valued associations in this manual we +will make use of a ``Collection`` interface and a corresponding +default implementation ``ArrayCollection`` that are defined in the +``Doctrine\Common\Collections`` namespace. Why do we need that? +Doesn't that couple my domain model to Doctrine? Unfortunately, PHP +arrays, while being great for many things, do not make up for good +collections of business objects, especially not in the context of +an ORM. The reason is that plain PHP arrays can not be +transparently extended / instrumented in PHP code, which is +necessary for a lot of advanced ORM features. The classes / +interfaces that come closest to an OO collection are ArrayAccess +and ArrayObject but until instances of these types can be used in +all places where a plain array can be used (something that may +happen in PHP6) their usability is fairly limited. You "can" +type-hint on ``ArrayAccess`` instead of ``Collection``, since the +Collection interface extends ``ArrayAccess``, but this will +severely limit you in the way you can work with the collection, +because the ``ArrayAccess`` API is (intentionally) very primitive +and more importantly because you can not pass this collection to +all the useful PHP array functions, which makes it very hard to +work with. + + **CAUTION** The Collection interface and ArrayCollection class, + like everything else in the Doctrine namespace, are neither part of + the ORM, nor the DBAL, it is a plain PHP class that has no outside + dependencies apart from dependencies on PHP itself (and the SPL). + Therefore using this class in your domain classes and elsewhere + does not introduce a coupling to the persistence layer. The + Collection class, like everything else in the Common namespace, is + not part of the persistence layer. You could even copy that class + over to your project if you want to remove Doctrine from your + project and all your domain classes will work the same as before. + + +Mapping Defaults +---------------- + +Before we introduce all the association mappings in detail, you +should note that the @JoinColumn and @JoinTable definitions are +usually optional and have sensible default values. The defaults for +a join column in a one-to-one/many-to-one association is as +follows: + +:: + + name: "_id" + referencedColumnName: "id" + +As an example, consider this mapping: + +:: + + groups; + } + } + +With this code alone the ``$groups`` field only contains an +instance of ``Doctrine\Common\Collections\Collection`` if the user +is retrieved from Doctrine, however not after you instantiated a +fresh instance of the User. When your user entity is still new +``$groups`` will obviously be null. + +This is why we recommend to initialize all collection fields to an +empty ``ArrayCollection`` in your entities constructor: + +:: + + groups = new ArrayCollection(); + } + + public function getGroups() + { + return $this->groups; + } + } + +Now the following code will be working even if the Entity hasn't +been associated with an EntityManager yet: + +:: + + find('Group', $groupId); + $user = new User(); + $user->getGroups()->add($group); + +Runtime vs Development Mapping Validation +----------------------------------------- + +For performance reasons Doctrine 2 has to skip some of the +necessary validation of association mappings. You have to execute +this validation in your development workflow to verify the +associations are correctly defined. + +You can either use the Doctrine Command Line Tool: + +:: + + doctrine orm:validate-schema + +Or you can trigger the validation manually: + +:: + + use Doctrine\ORM\Tools\SchemaValidator; + + $validator = new SchemaValidator($entityManager); + $errors = $validator->validateMapping(); + + if (count($errors) > 0) { + // Lots of errors! + echo implode("\n\n", $errors); + } + +If the mapping is invalid the errors array contains a positive +number of elements with error messages. + + **NOTE** + + One common error is to use a backlash in front of the + fully-qualified class-name. Whenever a FQCN is represented inside a + string (such as in your mapping definitions) you have to drop the + prefix backslash. PHP does this with ``get_class()`` or Reflection + methods for backwards compatibility reasons. + + +One-To-One, Unidirectional +-------------------------- + +A unidirectional one-to-one association is very common. Here is an +example of a ``Product`` that has one ``Shipping`` object +associated to it. The ``Shipping`` side does not reference back to +the ``Product`` so it is unidirectional. + +:: + + phonenumbers = new \Doctrine\Common\Collections\ArrayCollection(); + } + + // ... + } + + /** @Entity */ + class Phonenumber + { + // ... + } + + **NOTE** One-To-Many uni-directional relations with join-table only + work using the @ManyToMany annotation and a unique-constraint. + + +Generates the following MySQL Schema: + +:: + + [sql] + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + CREATE TABLE users_phonenumbers ( + user_id INT NOT NULL, + phonenumber_id INT NOT NULL, + UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id), + PRIMARY KEY(user_id, phonenumber_id) + ) ENGINE = InnoDB; + + CREATE TABLE Phonenumber ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id); + ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id); + +Many-To-One, Unidirectional +--------------------------- + +You can easily implement a many-to-one unidirectional association +with the following: + +:: + + features = new \Doctrine\Common\Collections\ArrayCollection(); + } + } + + /** @Entity */ + class Feature + { + // ... + /** + * @ManyToOne(targetEntity="Product", inversedBy="features") + * @JoinColumn(name="product_id", referencedColumnName="id") + */ + private $product; + // ... + } + +Note that the @JoinColumn is not really necessary in this example, +as the defaults would be the same. + +Generated MySQL Schema: + +:: + + [sql] + CREATE TABLE Product ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE Feature ( + id INT AUTO_INCREMENT NOT NULL, + product_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id); + +One-To-Many, Self-referencing +----------------------------- + +You can also setup a one-to-many association that is +self-referencing. In this example we setup a hierarchy of +``Category`` objects by creating a self referencing relationship. +This effectively models a hierarchy of categories and from the +database perspective is known as an adjacency list approach. + +:: + + children = new \Doctrine\Common\Collections\ArrayCollection(); + } + } + +Note that the @JoinColumn is not really necessary in this example, +as the defaults would be the same. + +Generated MySQL Schema: + +:: + + [sql] + CREATE TABLE Category ( + id INT AUTO_INCREMENT NOT NULL, + parent_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Category ADD FOREIGN KEY (parent_id) REFERENCES Category(id); + +Many-To-Many, Unidirectional +---------------------------- + +Real many-to-many associations are less common. The following +example shows a unidirectional association between User and Group +entities: + +:: + + groups = new \Doctrine\Common\Collections\ArrayCollection(); + } + } + + /** @Entity */ + class Group + { + // ... + } + + **NOTE** Why are many-to-many associations less common? Because + frequently you want to associate additional attributes with an + association, in which case you introduce an association class. + Consequently, the direct many-to-many association disappears and is + replaced by one-to-many/many-to-one associations between the 3 + participating classes. + + +Generated MySQL Schema: + +:: + + [sql] + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE users_groups ( + user_id INT NOT NULL, + group_id INT NOT NULL, + PRIMARY KEY(user_id, group_id) + ) ENGINE = InnoDB; + CREATE TABLE Group ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE users_groups ADD FOREIGN KEY (user_id) REFERENCES User(id); + ALTER TABLE users_groups ADD FOREIGN KEY (group_id) REFERENCES Group(id); + +Many-To-Many, Bidirectional +--------------------------- + +Here is a similar many-to-many relationship as above except this +one is bidirectional. + +:: + + groups = new \Doctrine\Common\Collections\ArrayCollection(); + } + + // ... + } + + /** @Entity */ + class Group + { + // ... + /** + * @ManyToMany(targetEntity="User", mappedBy="groups") + */ + private $users; + + public function __construct() { + $this->users = new \Doctrine\Common\Collections\ArrayCollection(); + } + + // ... + } + +The MySQL schema is exactly the same as for the Many-To-Many +uni-directional case above. + +Picking Owning and Inverse Side +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For Many-To-Many associations you can chose which entity is the +owning and which the inverse side. There is a very simple semantic +rule to decide which side is more suitable to be the owning side +from a developers perspective. You only have to ask yourself, which +entity is responsible for the connection management and pick that +as the owning side. + +Take an example of two entities ``Article`` and ``Tag``. Whenever +you want to connect an Article to a Tag and vice-versa, it is +mostly the Article that is responsible for this relation. Whenever +you add a new article, you want to connect it with existing or new +tags. Your create Article form will probably support this notion +and allow to specify the tags directly. This is why you should pick +the Article as owning side, as it makes the code more +understandable: + +:: + + addArticle($this); // synchronously updating inverse side + $this->tags[] = $tag; + } + } + + class Tag + { + private $articles; + + public function addArticle(Article $article) + { + $this->articles[] = $article; + } + } + +This allows to group the tag adding on the ``Article`` side of the +association: + +:: + + addTag($tagA); + $article->addTag($tagB); + +Many-To-Many, Self-referencing +------------------------------ + +You can even have a self-referencing many-to-many association. A +common scenario is where a ``User`` has friends and the target +entity of that relationship is a ``User`` so it is self +referencing. In this example it is bidirectional so ``User`` has a +field named ``$friendsWithMe`` and ``$myFriends``. + +:: + + friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection(); + $this->myFriends = new \Doctrine\Common\Collections\ArrayCollection(); + } + + // ... + } + +Generated MySQL Schema: + +:: + + [sql] + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE friends ( + user_id INT NOT NULL, + friend_user_id INT NOT NULL, + PRIMARY KEY(user_id, friend_user_id) + ) ENGINE = InnoDB; + ALTER TABLE friends ADD FOREIGN KEY (user_id) REFERENCES User(id); + ALTER TABLE friends ADD FOREIGN KEY (friend_user_id) REFERENCES User(id); + +Ordering To-Many Collections +---------------------------- + +In many use-cases you will want to sort collections when they are +retrieved from the database. In userland you do this as long as you +haven't initially saved an entity with its associations into the +database. To retrieve a sorted collection from the database you can +use the ``@OrderBy`` annotation with an collection that specifies +an DQL snippet that is appended to all queries with this +collection. + +Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you +can specify the ``@OrderBy`` in the following way: + +:: + + 10 + +However the following: + +:: + + [sql] + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 + +...would internally be rewritten to: + +:: + + [sql] + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC + +You can't reverse the order with an explicit DQL ORDER BY: + +:: + + [sql] + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC + +...is internally rewritten to: + +:: + + [sql] + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC + + diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt index 7e35e4566..fb22869f2 100644 --- a/manual/en/association-mapping.txt +++ b/manual/en/association-mapping.txt @@ -1,6 +1,6 @@ This chapter explains how associations between entities are mapped with Doctrine. We start out with an explanation of the concept of owning and inverse sides which is important to understand when working with bidirectional associations. Please read these explanations carefully. -++ Owning Side and Inverse Side +## Owning Side and Inverse Side When mapping bidirectional associations it is important to understand the concept of the owning and inverse sides. The following general rules apply: @@ -41,7 +41,7 @@ in the database. > of your domain model. What you consider as the owning side in your domain model can be different > from what the owning side is for Doctrine. These are unrelated. -++ Collections +## Collections In all the examples of many-valued associations in this manual we will make use of a `Collection` interface and a corresponding default implementation `ArrayCollection` that are defined in the `Doctrine\Common\Collections` namespace. Why do we need that? Doesn't that couple my domain model to Doctrine? Unfortunately, PHP arrays, while being great for many things, do not make up for good collections of business objects, especially not in the context of an ORM. The reason is that plain PHP arrays can not be transparently extended / instrumented in PHP code, which is necessary for a lot of advanced ORM features. The classes / interfaces that come closest to an OO collection are ArrayAccess and ArrayObject but until instances of these types can be used in all places where a plain array can be used (something that may happen in PHP6) their usability is fairly limited. You "can" type-hint on `ArrayAccess` instead of `Collection`, since the Collection interface extends `ArrayAccess`, but this will severely limit you in the way you can work with the collection, because the `ArrayAccess` API is (intentionally) very primitive and more importantly because you can not pass this collection to all the useful PHP array functions, which makes it very hard to work with. @@ -55,7 +55,7 @@ In all the examples of many-valued associations in this manual we will make use > over to your project if you want to remove Doctrine from your project and all your > domain classes will work the same as before. -++ Mapping Defaults +## Mapping Defaults Before we introduce all the association mappings in detail, you should note that the @JoinColumn and @JoinTable definitions are usually optional and have sensible default values. @@ -66,13 +66,13 @@ The defaults for a join column in a one-to-one/many-to-one association is as fol As an example, consider this mapping: - [php] + find('Group', $groupId); $user = new User(); $user->getGroups()->add($group); -++ Runtime vs Development Mapping Validation +## Runtime vs Development Mapping Validation For performance reasons Doctrine 2 has to skip some of the necessary validation of association mappings. You have to execute this validation in your development workflow to verify the associations are correctly @@ -191,11 +191,11 @@ If the mapping is invalid the errors array contains a positive number of element > inside a string (such as in your mapping definitions) you have to drop the prefix backslash. PHP does this with > `get_class()` or Reflection methods for backwards compatibility reasons. -++ One-To-One, Unidirectional +## One-To-One, Unidirectional A unidirectional one-to-one association is very common. Here is an example of a `Product` that has one `Shipping` object associated to it. The `Shipping` side does not reference back to the `Product` so it is unidirectional. - [php] + addTag($tagA); $article->addTag($tagB); -++ Many-To-Many, Self-referencing +## Many-To-Many, Self-referencing You can even have a self-referencing many-to-many association. A common scenario is where a `User` has friends and the target entity of that relationship is a `User` so it is self referencing. In this example it is bidirectional so `User` has a field named `$friendsWithMe` and `$myFriends`. - [php] + getConnection(); + $conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype'); + +Now using Schema-Tool, whenever it detects a column having the +``db_mytype`` it will convert it into a ``mytype`` Doctrine Type +instance for Schema representation. Keep in mind that you can +easily produce clashes this way, each database type can only map to +exactly one Doctrine mapping type. + +Identifiers / Primary Keys +-------------------------- + +Every entity class needs an identifier/primary key. You designate +the field that serves as the identifier with the ``@Id`` marker +annotation. Here is an example: + +:: + + recommended!) then the XML driver might have a slight edge in performance due to the > powerful native XML support in PHP. -++ Introduction to Docblock Annotations +## Introduction to Docblock Annotations You've probably used docblock annotations in some form already, most likely to provide documentation metadata for a tool like `PHPDocumentor` (@author, @link, ...). Docblock annotations are a tool to embed metadata inside the documentation section which can then be processed by some tool. Doctrine 2 generalizes the concept of docblock annotations so that they can be used for any kind of metadata and so that it is easy to define new docblock annotations. In order to allow more involved annotation values and to reduce the chances of clashes with other docblock annotations, the Doctrine 2 docblock annotations feature an alternative syntax that is heavily inspired by the Annotation syntax introduced in Java 5. @@ -30,11 +30,11 @@ The implementation of these enhanced docblock annotations is located in the `Doc > mentioned earlier Doctrine 2 provides XML and YAML alternatives and you could easily > implement your own favourite mechanism for defining ORM metadata. -++ Persistent classes +## Persistent classes In order to mark a class for object-relational persistence it needs to be designated as an entity. This can be done through the `@Entity` marker annotation. - [php] + Mapping types are *case-sensitive*. For example, using a DateTime column will NOT match the datetime type > that ships with Doctrine 2! -++ Property Mapping +## Property Mapping After a class has been marked as an entity it can specify mappings for its instance fields. Here we will only look at simple fields that hold scalar values like strings, numbers, etc. Associations to other objects are covered in the chapter "Association Mapping". @@ -91,7 +91,7 @@ To mark a property for relational persistence the `@Column` docblock annotation Example: - [php] + getConnection(); $conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype'); @@ -207,11 +207,11 @@ Now using Schema-Tool, whenever it detects a column having the `db_mytype` it wi Doctrine Type instance for Schema representation. Keep in mind that you can easily produce clashes this way, each database type can only map to exactly one Doctrine mapping type. -++ Identifiers / Primary Keys +## Identifiers / Primary Keys Every entity class needs an identifier/primary key. You designate the field that serves as the identifier with the `@Id` marker annotation. Here is an example: - [php] + strategy of the underlying platform is SEQUENCE, such as for Oracle and PostgreSQL. -+++ Composite Keys +### Composite Keys Doctrine 2 allows to use composite primary keys. There are however some restrictions opposed to using a single identifier. The use of the `@GeneratedValue` annotation is only supported for simple (not composite) primary keys, which means @@ -300,11 +300,11 @@ on the entity. To designate a composite primary key / identifier, simply put the @Id marker annotation on all fields that make up the primary key. -++ Quoting Reserved Words +## Quoting Reserved Words It may sometimes be necessary to quote a column or table name because it conflicts with a reserved word of the particular RDBMS in use. This is often referred to as "Identifier Quoting". To let Doctrine know that you would like a table or column name to be quoted in all SQL statements, enclose the table or column name in backticks. Here is an example: - [php] + setStatus('user'); + $user->setUsername('user' . $i); + $user->setName('Mr.Smith-' . $i); + $em->persist($user); + if (($i % $batchSize) == 0) { + $em->flush(); + $em->clear(); // Detaches all objects from Doctrine! + } + } + +Bulk Updates +------------ + +There are 2 possibilities for bulk updates with Doctrine. + +DQL UPDATE +~~~~~~~~~~ + +The by far most efficient way for bulk updates is to use a DQL +UPDATE query. Example: + +:: + + createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9'); + $numUpdated = $q->execute(); + +Iterating results +~~~~~~~~~~~~~~~~~ + +An alternative solution for bulk updates is to use the +``Query#iterate()`` facility to iterate over the query results step +by step instead of loading the whole result into memory at once. +The following example shows how to do this, combining the iteration +with the batching strategy that was already used for bulk inserts: + +:: + + createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + foreach($iterableResult AS $row) { + $user = $row[0]; + $user->increaseCredit(); + $user->calculateNewBonuses(); + if (($i % $batchSize) == 0) { + $em->flush(); // Executes all updates. + $em->clear(); // Detaches all objects from Doctrine! + } + ++$i; + } + + **NOTE** Iterating results is not possible with queries that + fetch-join a collection-valued association. The nature of such SQL + result sets is not suitable for incremental hydration. + + +Bulk Deletes +------------ + +There are two possibilities for bulk deletes with Doctrine. You can +either issue a single DQL DELETE query or you can iterate over +results removing them one at a time. + +DQL DELETE +~~~~~~~~~~ + +The by far most efficient way for bulk deletes is to use a DQL +DELETE query. + +Example: + +:: + + createQuery('delete from MyProject\Model\Manager m where m.salary > 100000'); + $numDeleted = $q->execute(); + +Iterating results +~~~~~~~~~~~~~~~~~ + +An alternative solution for bulk deletes is to use the +``Query#iterate()`` facility to iterate over the query results step +by step instead of loading the whole result into memory at once. +The following example shows how to do this: + +:: + + createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + while (($row = $iterableResult->next()) !== false) { + $em->remove($row[0]); + if (($i % $batchSize) == 0) { + $em->flush(); // Executes all deletions. + $em->clear(); // Detaches all objects from Doctrine! + } + ++$i; + } + + **NOTE** Iterating results is not possible with queries that + fetch-join a collection-valued association. The nature of such SQL + result sets is not suitable for incremental hydration. + + +Iterating Large Results for Data-Processing +------------------------------------------- + +You can use the ``iterate()`` method just to iterate over a large +result and no UPDATE or DELETE intention. The ``IterableResult`` +instance returned from ``$query->iterate()`` implements the +Iterator interface so you can process a large result without memory +problems using the following approach: + +:: + + _em->createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + foreach ($iterableResult AS $row) { + // do stuff with the data in the row, $row[0] is always the object + + // detach from Doctrine, so that it can be Garbage-Collected immediately + $this->_em->detach($row[0]); + } + + **NOTE** Iterating results is not possible with queries that + fetch-join a collection-valued association. The nature of such SQL + result sets is not suitable for incremental hydration. + + + diff --git a/manual/en/batch-processing.txt b/manual/en/batch-processing.txt index b9c3619d1..fdc44bd45 100644 --- a/manual/en/batch-processing.txt +++ b/manual/en/batch-processing.txt @@ -6,11 +6,11 @@ This chapter shows you how to accomplish bulk inserts, updates and deletes with > the options outlined below are not sufficient for your purposes we recommend you > use the tools for your particular RDBMS for these bulk operations. -++ Bulk Inserts +## Bulk Inserts Bulk inserts in Doctrine are best performed in batches, taking advantage of the transactional write-behind behavior of an `EntityManager`. The following code shows an example for inserting 10000 objects with a batch size of 20. You may need to experiment with the batch size to find the size that works best for you. Larger batch sizes mean more prepared statement reuse internally but also mean more work during `flush`. - [php] + createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9'); $numUpdated = $q->execute(); -+++ Iterating results +### Iterating results An alternative solution for bulk updates is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this, combining the iteration with the batching strategy that was already used for bulk inserts: - [php] + createQuery('select u from MyProject\Model\User u'); @@ -61,26 +61,26 @@ An alternative solution for bulk updates is to use the `Query#iterate()` facilit > association. The nature of such SQL result sets is not suitable for incremental > hydration. -++ Bulk Deletes +## Bulk Deletes There are two possibilities for bulk deletes with Doctrine. You can either issue a single DQL DELETE query or you can iterate over results removing them one at a time. -+++ DQL DELETE +### DQL DELETE The by far most efficient way for bulk deletes is to use a DQL DELETE query. Example: - [php] + createQuery('delete from MyProject\Model\Manager m where m.salary > 100000'); $numDeleted = $q->execute(); -+++ Iterating results +### Iterating results An alternative solution for bulk deletes is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this: - [php] + createQuery('select u from MyProject\Model\User u'); @@ -99,14 +99,14 @@ An alternative solution for bulk deletes is to use the `Query#iterate()` facilit > association. The nature of such SQL result sets is not suitable for incremental > hydration. -++ Iterating Large Results for Data-Processing +## Iterating Large Results for Data-Processing You can use the `iterate()` method just to iterate over a large result and no UPDATE or DELETE intention. The `IterableResult` instance returned from `$query->iterate()` implements the Iterator interface so you can process a large result without memory problems using the following approach: - [php] + _em->createQuery('select u from MyProject\Model\User u'); $iterableResult = $q->iterate(); foreach ($iterableResult AS $row) { diff --git a/manual/en/best-practices.rst b/manual/en/best-practices.rst new file mode 100644 index 000000000..48a8771c7 --- /dev/null +++ b/manual/en/best-practices.rst @@ -0,0 +1,124 @@ + **NOTE** The best practices mentioned here that affect database + design generally refer to best practices when working with Doctrine + and do not necessarily reflect best practices for database design + in general. + + +Don't use public properties on entities +--------------------------------------- + +It is very important that you don't map public properties on +entities, but only protected or private ones. The reason for this +is simple, whenever you access a public property of a proxy object +that hasn't been initialized yet the return value will be null. +Doctrine cannot hook into this process and magically make the +entity lazy load. + +This can create situations where it is very hard to debug the +current failure. We therefore urge you to map only private and +protected properties on entities and use getter methods or magic +\_\_get() to access them. + +Constrain relationships as much as possible +------------------------------------------- + +It is important to constrain relationships as much as possible. +This means: + + +- Impose a traversal direction (avoid bidirectional associations + if possible) +- Eliminate nonessential associations + +This has several benefits: + + +- Reduced coupling in your domain model +- Simpler code in your domain model (no need to maintain + bidirectionality properly) +- Less work for Doctrine + +Avoid composite keys +-------------------- + +Even though Doctrine fully supports composite keys it is best not +to use them if possible. Composite keys require additional work by +Doctrine and thus have a higher probability of errors. + +Use events judiciously +---------------------- + +The event system of Doctrine is great and fast. Even though making +heavy use of events, especially lifecycle events, can have a +negative impact on the performance of your application. Thus you +should use events judiciously. + +Use cascades judiciously +------------------------ + +Automatic cascades of the persist/remove/merge/etc. operations are +very handy but should be used wisely. Do NOT simply add all +cascades to all associations. Think about which cascades actually +do make sense for you for a particular association, given the +scenarios it is most likely used in. + +Don't use special characters +---------------------------- + +Avoid using any non-ASCII characters in class, field, table or +column names. Doctrine itself is not unicode-safe in many places +and will not be until PHP itself is fully unicode-aware (PHP6). + +Don't use identifier quoting +---------------------------- + +Identifier quoting is a workaround for using reserved words that +often causes problems in edge cases. Do not use identifier quoting +and avoid using reserved words as table or column names. + +Initialize collections in the constructor +----------------------------------------- + +It is recommended best practice to initialize any business +collections in entities in the constructor. Example: + +:: + + addresses = new ArrayCollection; + $this->articles = new ArrayCollection; + } + } + +Don't map foreign keys to fields in an entity +--------------------------------------------- + +Foreign keys have no meaning whatsoever in an object model. Foreign +keys are how a relational database establishes relationships. Your +object model establishes relationships through object references. +Thus mapping foreign keys to object fields heavily leaks details of +the relational model into the object model, something you really +should not do. + +Use explicit transaction demarcation +------------------------------------ + +While Doctrine will automatically wrap all DML operations in a +transaction on flush(), it is considered best practice to +explicitly set the transaction boundaries yourself. Otherwise every +single query is wrapped in a small transaction (Yes, SELECT +queries, too) since you can not talk to your database outside of a +transaction. While such short transactions for read-only (SELECT) +queries generally don't have any noticeable performance impact, it +is still preferable to use fewer, well-defined transactions that +are established through explicit transaction boundaries. + + diff --git a/manual/en/best-practices.txt b/manual/en/best-practices.txt index 116761b5f..1d4cee690 100644 --- a/manual/en/best-practices.txt +++ b/manual/en/best-practices.txt @@ -4,7 +4,7 @@ > practices when working with Doctrine and do not necessarily reflect best practices for > database design in general. -++ Don't use public properties on entities +## Don't use public properties on entities It is very important that you don't map public properties on entities, but only protected or private ones. The reason for this is simple, whenever you access a public property of a proxy object that hasn't been initialized @@ -13,7 +13,7 @@ yet the return value will be null. Doctrine cannot hook into this process and ma This can create situations where it is very hard to debug the current failure. We therefore urge you to map only private and protected properties on entities and use getter methods or magic __get() to access them. -++ Constrain relationships as much as possible +## Constrain relationships as much as possible It is important to constrain relationships as much as possible. This means: @@ -26,31 +26,31 @@ This has several benefits: * Simpler code in your domain model (no need to maintain bidirectionality properly) * Less work for Doctrine -++ Avoid composite keys +## Avoid composite keys Even though Doctrine fully supports composite keys it is best not to use them if possible. Composite keys require additional work by Doctrine and thus have a higher probability of errors. -++ Use events judiciously +## Use events judiciously The event system of Doctrine is great and fast. Even though making heavy use of events, especially lifecycle events, can have a negative impact on the performance of your application. Thus you should use events judiciously. -++ Use cascades judiciously +## Use cascades judiciously Automatic cascades of the persist/remove/merge/etc. operations are very handy but should be used wisely. Do NOT simply add all cascades to all associations. Think about which cascades actually do make sense for you for a particular association, given the scenarios it is most likely used in. -++ Don't use special characters +## Don't use special characters Avoid using any non-ASCII characters in class, field, table or column names. Doctrine itself is not unicode-safe in many places and will not be until PHP itself is fully unicode-aware (PHP6). -++ Don't use identifier quoting +## Don't use identifier quoting Identifier quoting is a workaround for using reserved words that often causes problems in edge cases. Do not use identifier quoting and avoid using reserved words as table or column names. -++ Initialize collections in the constructor +## Initialize collections in the constructor It is recommended best practice to initialize any business collections in entities in the constructor. Example: - [php] + `_ on the PHP website. It will give +you a little background information about what it is and how you +can use it as well as how to install it. + +Below is a simple example of how you could use the APC cache driver +by itself. + +:: + + save('cache_id', 'my_data'); + +Memcache +~~~~~~~~ + +In order to use the Memcache cache driver you must have it compiled +and enabled in your php.ini. You can read about Memcache +`here `_ on the PHP website. It will +give you a little background information about what it is and how +you can use it as well as how to install it. + +Below is a simple example of how you could use the Memcache cache +driver by itself. + +:: + + connect('memcache_host', 11211); + + $cacheDriver = new \Doctrine\Common\Cache\MemcacheCache(); + $cacheDriver->setMemcache() + $cacheDriver->save('cache_id', 'my_data'); + +Xcache +~~~~~~ + +In order to use the Xcache cache driver you must have it compiled +and enabled in your php.ini. You can read about Xcache +`here `_. It will give you a little +background information about what it is and how you can use it as +well as how to install it. + +Below is a simple example of how you could use the Xcache cache +driver by itself. + +:: + + save('cache_id', 'my_data'); + +Using Cache Drivers +------------------- + +In this section we'll describe how you can fully utilize the API of +the cache drivers to save cache, check if some cache exists, fetch +the cached data and delete the cached data. We'll use the +``ArrayCache`` implementation as our example here. + +:: + + save('cache_id', 'my_data'); + +The ``save()`` method accepts three arguments which are described +below. + + +- ``$id`` - The cache id +- ``$data`` - The cache entry/data. +- ``$lifeTime`` - The lifetime. If != false, sets a specific + lifetime for this cache entry (null => infinite lifeTime). + +You can save any type of data whether it be a string, array, +object, etc. + +:: + + 'value1', + 'key2' => 'value2' + ); + $cacheDriver->save('my_array', $array); + +Checking +~~~~~~~~ + +Checking whether some cache exists is very simple, just use the +``contains()`` method. It accepts a single argument which is the ID +of the cache entry. + +:: + + contains('cache_id')) { + echo 'cache exists'; + } else { + echo 'cache does not exist'; + } + +Fetching +~~~~~~~~ + +Now if you want to retrieve some cache entry you can use the +``fetch()`` method. It also accepts a single argument just like +``contains()`` which is the ID of the cache entry. + +:: + + fetch('my_array'); + +Deleting +~~~~~~~~ + +As you might guess, deleting is just as easy as saving, checking +and fetching. We have a few ways to delete cache entries. You can +delete by an individual ID, regular expression, prefix, suffix or +you can delete all entries. + +By Cache ID +^^^^^^^^^^^ + +:: + + delete('my_array'); + +You can also pass wild cards to the ``delete()`` method and it will +return an array of IDs that were matched and deleted. + +:: + + delete('users_*'); + +By Regular Expression +^^^^^^^^^^^^^^^^^^^^^ + +If you need a little more control than wild cards you can use a PHP +regular expression to delete cache entries. + +:: + + deleteByRegex('/users_.*/'); + +By Prefix +^^^^^^^^^ + +Because regular expressions are kind of slow, if simply deleting by +a prefix or suffix is sufficient, it is recommended that you do +that instead of using a regular expression because it will be much +faster if you have many cache entries. + +:: + + deleteByPrefix('users_'); + +By Suffix +^^^^^^^^^ + +Just like we did above with the prefix you can do the same with a +suffix. + +:: + + deleteBySuffix('_my_account'); + +All +^^^ + +If you simply want to delete all cache entries you can do so with +the ``deleteAll()`` method. + +:: + + deleteAll(); + +Counting +~~~~~~~~ + +If you want to count how many entries are stored in the cache +driver instance you can use the ``count()`` method. + +:: + + count(); + + **NOTE** In order to use ``deleteByRegex()``, ``deleteByPrefix()``, + ``deleteBySuffix()``, ``deleteAll()``, ``count()`` or ``getIds()`` + you must enable an option for the cache driver to manage your cache + IDs internally. This is necessary because APC, Memcache, etc. don't + have any advanced functionality for fetching and deleting. We add + some functionality on top of the cache drivers to maintain an index + of all the IDs stored in the cache driver so that we can allow more + granular deleting operations. + + :: + + setManageCacheIds(true); + + +Namespaces +~~~~~~~~~~ + +If you heavily use caching in your application and utilize it in +multiple parts of your application, or use it in different +applications on the same server you may have issues with cache +naming collisions. This can be worked around by using namespaces. +You can set the namespace a cache driver should use by using the +``setNamespace()`` method. + +:: + + setNamespace('my_namespace_'); + +Integrating with the ORM +------------------------ + +The Doctrine ORM package is tightly integrated with the cache +drivers to allow you to improve performance of various aspects of +Doctrine by just simply making some additional configurations and +method calls. + +Query Cache +~~~~~~~~~~~ + +It is highly recommended that in a production environment you cache +the transformation of a DQL query to its SQL counterpart. It +doesn't make sense to do this parsing multiple times as it doesn't +change unless you alter the DQL query. + +This can be done by configuring the query cache implementation to +use on your ORM configuration. + +:: + + setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + +Result Cache +~~~~~~~~~~~~ + +The result cache can be used to cache the results of your queries +so that we don't have to query the database or hydrate the data +again after the first time. You just need to configure the result +cache implementation. + +:: + + setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + +Now when you're executing DQL queries you can configure them to use +the result cache. + +:: + + createQuery('select u from \Entities\User u'); + $query->useResultCache(true); + +You can also configure an individual query to use a different +result cache driver. + +:: + + setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache()); + + **NOTE** Setting the result cache driver on the query will + automatically enable the result cache for the query. If you want to + disable it pass false to ``useResultCache()``. + + :: + + useResultCache(false); + + +If you want to set the time the cache has to live you can use the +``setResultCacheLifetime()`` method. + +:: + + setResultCacheLifetime(3600); + +The ID used to store the result set cache is a hash which is +automatically generated for you if you don't set a custom ID +yourself with the ``setResultCacheId()`` method. + +:: + + setResultCacheId('my_custom_id'); + +You can also set the lifetime and cache ID by passing the values as +the second and third argument to ``useResultCache()``. + +:: + + useResultCache(true, 3600, 'my_custom_id'); + +Metadata Cache +~~~~~~~~~~~~~~ + +Your class metadata can be parsed from a few different sources like +YAML, XML, Annotations, etc. Instead of parsing this information on +each request we should cache it using one of the cache drivers. + +Just like the query and result cache we need to configure it +first. + +:: + + setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + +Now the metadata information will only be parsed once and stored in +the cache driver. + +Clearing the Cache +------------------ + +We've already shown you previously how you can use the API of the +cache drivers to manually delete cache entries. For your +convenience we offer a command line task for you to help you with +clearing the query, result and metadata cache. + +From the Doctrine command line you can run the following command. + +:: + + $ ./doctrine clear-cache + +Running this task with no arguments will clear all the cache for +all the configured drivers. If you want to be more specific about +what you clear you can use the following options. + +To clear the query cache use the ``--query`` option. + +:: + + $ ./doctrine clear-cache --query + +To clear the metadata cache use the ``--metadata`` option. + +:: + + $ ./doctrine clear-cache --metadata + +To clear the result cache use the ``--result`` option. + +:: + + $ ./doctrine clear-cache --result + +When you use the ``--result`` option you can use some other options +to be more specific about what queries result sets you want to +clear. + +Just like the API of the cache drivers you can clear based on an +ID, regular expression, prefix or suffix. + +:: + + $ ./doctrine clear-cache --result --id=cache_id + +Or if you want to clear based on a regular expressions. + +:: + + $ ./doctrine clear-cache --result --regex=users_.* + +Or with a prefix. + +:: + + $ ./doctrine clear-cache --result --prefix=users_ + +And finally with a suffix. + +:: + + $ ./doctrine clear-cache --result --suffix=_my_account + + **NOTE** Using the ``--id``, ``--regex``, etc. options with the + ``--query`` and ``--metadata`` are not allowed as it is not + necessary to be specific about what you clear. You only ever need + to completely clear the cache to remove stale entries. + + +Cache Slams +----------- + +Something to be careful of when utilizing the cache drivers is +cache slams. If you have a heavily trafficked website with some +code that checks for the existence of a cache record and if it does +not exist it generates the information and saves it to the cache. +Now if 100 requests were issued all at the same time and each one +sees the cache does not exist and they all try and insert the same +cache entry it could lock up APC, Xcache, etc. and cause problems. +Ways exist to work around this, like pre-populating your cache and +not letting your users requests populate the cache. + +You can read more about cache slams +`here `_. + + diff --git a/manual/en/caching.txt b/manual/en/caching.txt index f3e5d202f..7a2909ac1 100644 --- a/manual/en/caching.txt +++ b/manual/en/caching.txt @@ -4,7 +4,7 @@ an `ArrayCache` driver which stores the data in a PHP array. Obviously, the cach does not live between requests but this is useful for testing in a development environment. -++ Cache Drivers +## Cache Drivers The cache drivers follow a simple interface that is defined in `Doctrine\Common\Cache\Cache`. All the cache drivers extend a base class `Doctrine\Common\Cache\AbstractCache` @@ -30,7 +30,7 @@ that are implemented by the drivers. The code is organized this way so that the protected methods in the drivers do the raw interaction with the cache implementation and the `AbstractCache` can build custom functionality on top of these methods. -+++ APC +### APC In order to use the APC cache driver you must have it compiled and enabled in your php.ini. You can read about APC [here](http://us2.php.net/apc) on the PHP @@ -39,11 +39,11 @@ how you can use it as well as how to install it. Below is a simple example of how you could use the APC cache driver by itself. - [php] + save('cache_id', 'my_data'); -+++ Memcache +### Memcache In order to use the Memcache cache driver you must have it compiled and enabled in your php.ini. You can read about Memcache [here](http://us2.php.net/memcache) on @@ -52,7 +52,7 @@ and how you can use it as well as how to install it. Below is a simple example of how you could use the Memcache cache driver by itself. - [php] + connect('memcache_host', 11211); @@ -60,7 +60,7 @@ Below is a simple example of how you could use the Memcache cache driver by itse $cacheDriver->setMemcache() $cacheDriver->save('cache_id', 'my_data'); -+++ Xcache +### Xcache In order to use the Xcache cache driver you must have it compiled and enabled in your php.ini. You can read about Xcache [here](http://xcache.lighttpd.net/). It @@ -69,25 +69,25 @@ use it as well as how to install it. Below is a simple example of how you could use the Xcache cache driver by itself. - [php] + save('cache_id', 'my_data'); -++ Using Cache Drivers +## Using Cache Drivers In this section we'll describe how you can fully utilize the API of the cache drivers to save cache, check if some cache exists, fetch the cached data and delete the cached data. We'll use the `ArrayCache` implementation as our example here. - [php] + save('cache_id', 'my_data'); The `save()` method accepts three arguments which are described below. @@ -98,88 +98,88 @@ The `save()` method accepts three arguments which are described below. You can save any type of data whether it be a string, array, object, etc. - [php] + 'value1', 'key2' => 'value2' ); $cacheDriver->save('my_array', $array); -+++ Checking +### Checking Checking whether some cache exists is very simple, just use the `contains()` method. It accepts a single argument which is the ID of the cache entry. - [php] + contains('cache_id')) { echo 'cache exists'; } else { echo 'cache does not exist'; } -+++ Fetching +### Fetching Now if you want to retrieve some cache entry you can use the `fetch()` method. It also accepts a single argument just like `contains()` which is the ID of the cache entry. - [php] + fetch('my_array'); -+++ Deleting +### Deleting As you might guess, deleting is just as easy as saving, checking and fetching. We have a few ways to delete cache entries. You can delete by an individual ID, regular expression, prefix, suffix or you can delete all entries. -++++ By Cache ID +#### By Cache ID - [php] + delete('my_array'); You can also pass wild cards to the `delete()` method and it will return an array of IDs that were matched and deleted. - [php] + delete('users_*'); -++++ By Regular Expression +#### By Regular Expression If you need a little more control than wild cards you can use a PHP regular expression to delete cache entries. - [php] + deleteByRegex('/users_.*/'); -++++ By Prefix +#### By Prefix Because regular expressions are kind of slow, if simply deleting by a prefix or suffix is sufficient, it is recommended that you do that instead of using a regular expression because it will be much faster if you have many cache entries. - [php] + deleteByPrefix('users_'); -++++ By Suffix +#### By Suffix Just like we did above with the prefix you can do the same with a suffix. - [php] + deleteBySuffix('_my_account'); -++++ All +#### All If you simply want to delete all cache entries you can do so with the `deleteAll()` method. - [php] + deleteAll(); -+++ Counting +### Counting If you want to count how many entries are stored in the cache driver instance you can use the `count()` method. - [php] + count(); > **NOTE** @@ -191,10 +191,10 @@ you can use the `count()` method. > all the IDs stored in the cache driver so that we can allow more granular deleting > operations. > -> [php] +> $cacheDriver->setManageCacheIds(true); -+++ Namespaces +### Namespaces If you heavily use caching in your application and utilize it in multiple parts of your application, or use it in different applications on the same server you @@ -202,16 +202,16 @@ may have issues with cache naming collisions. This can be worked around by using namespaces. You can set the namespace a cache driver should use by using the `setNamespace()` method. - [php] + setNamespace('my_namespace_'); -++ Integrating with the ORM +## Integrating with the ORM The Doctrine ORM package is tightly integrated with the cache drivers to allow you to improve performance of various aspects of Doctrine by just simply making some additional configurations and method calls. -+++ Query Cache +### Query Cache It is highly recommended that in a production environment you cache the transformation of a DQL query to its SQL counterpart. It doesn't make sense to @@ -221,28 +221,28 @@ query. This can be done by configuring the query cache implementation to use on your ORM configuration. - [php] + setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache()); -+++ Result Cache +### Result Cache The result cache can be used to cache the results of your queries so that we don't have to query the database or hydrate the data again after the first time. You just need to configure the result cache implementation. - [php] + setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache()); Now when you're executing DQL queries you can configure them to use the result cache. - [php] + createQuery('select u from \Entities\User u'); $query->useResultCache(true); You can also configure an individual query to use a different result cache driver. - [php] + setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache()); > **NOTE** @@ -250,28 +250,28 @@ You can also configure an individual query to use a different result cache drive > result cache for the query. If you want to disable it pass false to > `useResultCache()`. > -> [php] +> $query->useResultCache(false); If you want to set the time the cache has to live you can use the `setResultCacheLifetime()` method. - [php] + setResultCacheLifetime(3600); The ID used to store the result set cache is a hash which is automatically generated for you if you don't set a custom ID yourself with the `setResultCacheId()` method. - [php] + setResultCacheId('my_custom_id'); You can also set the lifetime and cache ID by passing the values as the second and third argument to `useResultCache()`. - [php] + useResultCache(true, 3600, 'my_custom_id'); -+++ Metadata Cache +### Metadata Cache Your class metadata can be parsed from a few different sources like YAML, XML, Annotations, etc. Instead of parsing this information on each request we should @@ -279,13 +279,13 @@ cache it using one of the cache drivers. Just like the query and result cache we need to configure it first. - [php] + setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); Now the metadata information will only be parsed once and stored in the cache driver. -++ Clearing the Cache +## Clearing the Cache We've already shown you previously how you can use the API of the cache drivers to manually delete cache entries. For your convenience we offer a command line task @@ -336,7 +336,7 @@ And finally with a suffix. > are not allowed as it is not necessary to be specific about what you clear. > You only ever need to completely clear the cache to remove stale entries. -++ Cache Slams +## Cache Slams Something to be careful of when utilizing the cache drivers is cache slams. If you have a heavily trafficked website with some code that checks for the existence diff --git a/manual/en/change-tracking-policies.rst b/manual/en/change-tracking-policies.rst new file mode 100644 index 000000000..97b3508fb --- /dev/null +++ b/manual/en/change-tracking-policies.rst @@ -0,0 +1,151 @@ +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: + +:: + + _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: + +:: + + _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. + + diff --git a/manual/en/change-tracking-policies.txt b/manual/en/change-tracking-policies.txt index 4f3746f9a..11fec2e4d 100644 --- a/manual/en/change-tracking-policies.txt +++ b/manual/en/change-tracking-policies.txt @@ -1,4 +1,4 @@ -++ Change Tracking Policies +## 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. @@ -7,7 +7,7 @@ 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 +### 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 @@ -19,7 +19,7 @@ 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 +### 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 @@ -37,7 +37,7 @@ on the way so you can pass them to EntityManager#persist(). This policy can be configured as follows: - [php] + register(); // register on SPL autoload stack + +Git +^^^ + +The Git bootstrap assumes that you have fetched the related +packages through ``git submodule update --init`` + +:: + + register(); + + $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\DBAL', $lib . 'vendor/doctrine-dbal/lib'); + $classLoader->register(); + + $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\ORM', $lib); + $classLoader->register(); + +Additional Symfony Components +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you don't use Doctrine2 in combination with Symfony2 you have to +register an additional namespace to be able to use the Doctrine-CLI +Tool or the YAML Mapping driver: + +:: + + register(); + + // Git Setup + $classloader = new \Doctrine\Common\ClassLoader('Symfony', $lib . 'vendor/'); + $classloader->register(); + +For best class loading performance it is recommended that you keep +your include\_path short, ideally it should only contain the path +to the PEAR libraries, and any other class libraries should be +registered with their full base path. + +Obtaining an EntityManager +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once you have prepared the class loading, you acquire an +*EntityManager* instance. The EntityManager class is the primary +access point to ORM functionality provided by Doctrine. + +A simple configuration of the EntityManager requires a +``Doctrine\ORM\Configuration`` instance as well as some database +connection parameters: + +:: + + setMetadataCacheImpl($cache); + $driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); + $config->setMetadataDriverImpl($driverImpl); + $config->setQueryCacheImpl($cache); + $config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); + $config->setProxyNamespace('MyProject\Proxies'); + + if ($applicationMode == "development") { + $config->setAutoGenerateProxyClasses(true); + } else { + $config->setAutoGenerateProxyClasses(false); + } + + $connectionOptions = array( + 'driver' => 'pdo_sqlite', + 'path' => 'database.sqlite' + ); + + $em = EntityManager::create($connectionOptions, $config); + + **CAUTION** Do not use Doctrine without a metadata and query cache! + Doctrine is highly optimized for working with caches. The main + parts in Doctrine that are optimized for caching are the metadata + mapping information with the metadata cache and the DQL to SQL + conversions with the query cache. These 2 caches require only an + absolute minimum of memory yet they heavily improve the runtime + performance of Doctrine. The recommended cache driver to use with + Doctrine is `APC `_. APC provides you with + an opcode-cache (which is highly recommended anyway) and a very + fast in-memory cache storage that you can use for the metadata and + query caches as seen in the previous code snippet. + + +Configuration Options +--------------------- + +The following sections describe all the configuration options +available on a ``Doctrine\ORM\Configuration`` instance. + +Proxy Directory (***REQUIRED***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + setProxyDir($dir); + $config->getProxyDir(); + +Gets or sets the directory where Doctrine generates any proxy +classes. For a detailed explanation on proxy classes and how they +are used in Doctrine, refer to the "Proxy Objects" section further +down. + +Proxy Namespace (***REQUIRED***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + setProxyNamespace($namespace); + $config->getProxyNamespace(); + +Gets or sets the namespace to use for generated proxy classes. For +a detailed explanation on proxy classes and how they are used in +Doctrine, refer to the "Proxy Objects" section further down. + +Metadata Driver (***REQUIRED***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + setMetadataDriverImpl($driver); + $config->getMetadataDriverImpl(); + +Gets or sets the metadata driver implementation that is used by +Doctrine to acquire the object-relational metadata for your +classes. + +There are currently 4 available implementations: + + +- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver`` +- ``Doctrine\ORM\Mapping\Driver\XmlDriver`` +- ``Doctrine\ORM\Mapping\Driver\YamlDriver`` +- ``Doctrine\ORM\Mapping\Driver\DriverChain`` + +Throughout the most part of this manual the AnnotationDriver is +used in the examples. For information on the usage of the XmlDriver +or YamlDriver please refer to the dedicated chapters +``XML Mapping`` and ``YAML Mapping``. + +The annotation driver can be configured with a factory method on +the ``Doctrine\ORM\Configuration``: + +:: + + newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); + $config->setMetadataDriverImpl($driverImpl); + +The path information to the entities is required for the annotation +driver, because otherwise mass-operations on all entities through +the console could not work correctly. All of metadata drivers +accept either a single directory as a string or an array of +directories. With this feature a single driver can support multiple +directories of Entities. + +Metadata Cache (***RECOMMENDED***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + setMetadataCacheImpl($cache); + $config->getMetadataCacheImpl(); + +Gets or sets the cache implementation to use for caching metadata +information, that is, all the information you supply via +annotations, xml or yaml, so that they do not need to be parsed and +loaded from scratch on every single request which is a waste of +resources. The cache implementation must implement the +``Doctrine\Common\Cache\Cache`` interface. + +Usage of a metadata cache is highly recommended. + +The recommended implementations for production are: + + +- ``Doctrine\Common\Cache\ApcCache`` +- ``Doctrine\Common\Cache\MemcacheCache`` +- ``Doctrine\Common\Cache\XcacheCache`` + +For development you should use the +``Doctrine\Common\Cache\ArrayCache`` which only caches data on a +per-request basis. + +Query Cache (***RECOMMENDED***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + setQueryCacheImpl($cache); + $config->getQueryCacheImpl(); + +Gets or sets the cache implementation to use for caching DQL +queries, that is, the result of a DQL parsing process that includes +the final SQL as well as meta information about how to process the +SQL result set of a query. Note that the query cache does not +affect query results. You do not get stale data. This is a pure +optimization cache without any negative side-effects (except some +minimal memory usage in your cache). + +Usage of a query cache is highly recommended. + +The recommended implementations for production are: + + +- ``Doctrine\Common\Cache\ApcCache`` +- ``Doctrine\Common\Cache\MemcacheCache`` +- ``Doctrine\Common\Cache\XcacheCache`` + +For development you should use the +``Doctrine\Common\Cache\ArrayCache`` which only caches data on a +per-request basis. + +SQL Logger (***Optional***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + setSQLLogger($logger); + $config->getSQLLogger(); + +Gets or sets the logger to use for logging all SQL statements +executed by Doctrine. The logger class must implement the +``Doctrine\DBAL\Logging\SqlLogger`` interface. A simple default +implementation that logs to the standard output using ``echo`` and +``var_dump`` can be found at +``Doctrine\DBAL\Logging\EchoSqlLogger``. + +Auto-generating Proxy Classes (***OPTIONAL***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + setAutoGenerateProxyClasses($bool); + $config->getAutoGenerateProxyClasses(); + +Gets or sets whether proxy classes should be generated +automatically at runtime by Doctrine. If set to ``FALSE``, proxy +classes must be generated manually through the doctrine command +line task ``generate-proxies``. The strongly recommended value for +a production environment is ``FALSE``. + +Development vs Production Configuration +--------------------------------------- + +You should code your Doctrine2 bootstrapping with two different +runtime models in mind. There are some serious benefits of using +APC or Memcache in production. In development however this will +frequently give you fatal errors, when you change your entities and +the cache still keeps the outdated metadata. That is why we +recommend the ``ArrayCache`` for development. + +Furthermore you should have the Auto-generating Proxy Classes +option to true in development and to false in production. If this +option is set to ``TRUE`` it can seriously hurt your script +performance if several proxy classes are re-generated during script +execution. Filesystem calls of that magnitude can even slower than +all the database queries Doctrine issues. Additionally writing a +proxy sets an exclusive file lock which can cause serious +performance bottlenecks in systems with regular concurrent +requests. + +Connection Options +------------------ + +The ``$connectionOptions`` passed as the first argument to +``EntityManager::create()`` has to be either an array or an +instance of ``Doctrine\DBAL\Connection``. If an array is passed it +is directly passed along to the DBAL Factory +``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL +configuration is explained in the +`DBAL section <./../../../../../dbal/2.0/docs/reference/configuration/en>`_. + +Proxy Objects +------------- + +A proxy object is an object that is put in place or used instead of +the "real" object. A proxy object can add behavior to the object +being proxied without that object being aware of it. In Doctrine 2, +proxy objects are used to realize several features but mainly for +transparent lazy-loading. + +Proxy objects with their lazy-loading facilities help to keep the +subset of objects that are already in memory connected to the rest +of the objects. This is an essential property as without it there +would always be fragile partial objects at the outer edges of your +object graph. + +Doctrine 2 implements a variant of the proxy pattern where it +generates classes that extend your entity classes and adds +lazy-loading capabilities to them. Doctrine can then give you an +instance of such a proxy class whenever you request an object of +the class being proxied. This happens in two situations: + +**Reference Proxies** + +The method ``EntityManager#getReference($entityName, $identifier)`` +lets you obtain a reference to an entity for which the identifier +is known, without loading that entity from the database. This is +useful, for example, as a performance enhancement, when you want to +establish an association to an entity for which you have the +identifier. You could simply do this: + +:: + + getReference('MyProject\Model\Item', $itemId); + $cart->addItem($item); + +Here, we added an Item to a Cart without loading the Item from the +database. If you invoke any method on the Item instance, it would +fully initialize its state transparently from the database. Here +$item is actually an instance of the proxy class that was generated +for the Item class but your code does not need to care. In fact it +**should not care**. Proxy objects should be transparent to your +code. + +**Association proxies** + +The second most important situation where Doctrine uses proxy +objects is when querying for objects. Whenever you query for an +object that has a single-valued association to another object that +is configured LAZY, without joining that association in the same +query, Doctrine puts proxy objects in place where normally the +associated object would be. Just like other proxies it will +transparently initialize itself on first access. + + **NOTE** Joining an association in a DQL or native query + essentially means eager loading of that association in that query. + This will override the 'fetch' option specified in the mapping for + that association, but only for that query. + + +Generating Proxy classes +~~~~~~~~~~~~~~~~~~~~~~~~ + +Proxy classes can either be generated manually through the Doctrine +Console or automatically by Doctrine. The configuration option that +controls this behavior is: + +:: + + setAutoGenerateProxyClasses($bool); + $config->getAutoGenerateProxyClasses(); + +The default value is ``TRUE`` for convenient development. However, +this setting is not optimal for performance and therefore not +recommended for a production environment. To eliminate the overhead +of proxy class generation during runtime, set this configuration +option to ``FALSE``. When you do this in a development environment, +note that you may get class/file not found errors if certain proxy +classes are not available or failing lazy-loads if new methods were +added to the entity class that are not yet in the proxy class. In +such a case, simply use the Doctrine Console to (re)generate the +proxy classes like so: + +:: + + $ ./doctrine orm:generate-proxies + +Multiple Metadata Sources +------------------------- + +When using different components using Doctrine 2 you may end up +with them using two different metadata drivers, for example XML and +YAML. You can use the DriverChain Metadata implementations to +aggregate these drivers based on namespaces: + +:: + + addDriver($xmlDriver, 'Doctrine\Tests\Models\Company'); + $chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping'); + +Based on the namespace of the entity the loading of entities is +delegated to the appropriate driver. The chain semantics come from +the fact that the driver loops through all namespaces and matches +the entity class name against the namespace using a +``strpos() === 0`` call. This means you need to order the drivers +correctly if sub-namespaces use different metadata driver +implementations. + + diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt index 73f9ec783..717032b0a 100644 --- a/manual/en/configuration.txt +++ b/manual/en/configuration.txt @@ -1,4 +1,4 @@ -++ Bootstrapping +## Bootstrapping Bootstrapping Doctrine is a relatively simple procedure that roughly exists of just 2 steps: @@ -6,7 +6,7 @@ just 2 steps: * Making sure Doctrine class files can be loaded on demand. * Obtaining an EntityManager instance. -+++ Class loading +### Class loading Lets start with the class loading setup. We need to set up some class loaders (often called "autoloader") so that Doctrine class files are loaded on demand. @@ -30,20 +30,20 @@ of Doctrine Installations: > This assumes you've created some kind of script to test the following code in. > Something like a `test.php` file. -++++ PEAR or Tarball Download +#### PEAR or Tarball Download - [php] + register(); // register on SPL autoload stack -++++ Git +#### Git The Git bootstrap assumes that you have fetched the related packages through `git submodule update --init` - [php] + register(); -++++ Additional Symfony Components +#### Additional Symfony Components If you don't use Doctrine2 in combination with Symfony2 you have to register an additional namespace to be able to use the Doctrine-CLI Tool or the YAML Mapping driver: - [php] + register(); @@ -74,7 +74,7 @@ the Doctrine-CLI Tool or the YAML Mapping driver: For best class loading performance it is recommended that you keep your include_path short, ideally it should only contain the path to the PEAR libraries, and any other class libraries should be registered with their full base path. -+++ Obtaining an EntityManager +### Obtaining an EntityManager Once you have prepared the class loading, you acquire an *EntityManager* instance. The EntityManager class is the primary access point to ORM functionality provided by Doctrine. @@ -82,7 +82,7 @@ The EntityManager class is the primary access point to ORM functionality provide A simple configuration of the EntityManager requires a `Doctrine\ORM\Configuration` instance as well as some database connection parameters: - [php] + a very fast in-memory cache storage that you can use for the metadata and query > caches as seen in the previous code snippet. -++ Configuration Options +## Configuration Options The following sections describe all the configuration options available on a `Doctrine\ORM\Configuration` instance. -+++ Proxy Directory (***REQUIRED***) +### Proxy Directory (***REQUIRED***) - [php] + setProxyDir($dir); $config->getProxyDir(); Gets or sets the directory where Doctrine generates any proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. -+++ Proxy Namespace (***REQUIRED***) +### Proxy Namespace (***REQUIRED***) - [php] + setProxyNamespace($namespace); $config->getProxyNamespace(); Gets or sets the namespace to use for generated proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. -+++ Metadata Driver (***REQUIRED***) +### Metadata Driver (***REQUIRED***) - [php] + setMetadataDriverImpl($driver); $config->getMetadataDriverImpl(); @@ -165,7 +165,7 @@ Throughout the most part of this manual the AnnotationDriver is used in the exam The annotation driver can be configured with a factory method on the `Doctrine\ORM\Configuration`: - [php] + newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); $config->setMetadataDriverImpl($driverImpl); @@ -174,9 +174,9 @@ mass-operations on all entities through the console could not work correctly. Al drivers accept either a single directory as a string or an array of directories. With this feature a single driver can support multiple directories of Entities. -+++ Metadata Cache (***RECOMMENDED***) +### Metadata Cache (***RECOMMENDED***) - [php] + setMetadataCacheImpl($cache); $config->getMetadataCacheImpl(); @@ -193,9 +193,9 @@ The recommended implementations for production are: For development you should use the `Doctrine\Common\Cache\ArrayCache` which only caches data on a per-request basis. -+++ Query Cache (***RECOMMENDED***) +### Query Cache (***RECOMMENDED***) - [php] + setQueryCacheImpl($cache); $config->getQueryCacheImpl(); @@ -211,23 +211,23 @@ The recommended implementations for production are: For development you should use the `Doctrine\Common\Cache\ArrayCache` which only caches data on a per-request basis. -+++ SQL Logger (***Optional***) +### SQL Logger (***Optional***) - [php] + setSQLLogger($logger); $config->getSQLLogger(); Gets or sets the logger to use for logging all SQL statements executed by Doctrine. The logger class must implement the `Doctrine\DBAL\Logging\SqlLogger` interface. A simple default implementation that logs to the standard output using `echo` and `var_dump` can be found at `Doctrine\DBAL\Logging\EchoSqlLogger`. -+++ Auto-generating Proxy Classes (***OPTIONAL***) +### Auto-generating Proxy Classes (***OPTIONAL***) - [php] + setAutoGenerateProxyClasses($bool); $config->getAutoGenerateProxyClasses(); Gets or sets whether proxy classes should be generated automatically at runtime by Doctrine. If set to `FALSE`, proxy classes must be generated manually through the doctrine command line task `generate-proxies`. The strongly recommended value for a production environment is `FALSE`. -++ Development vs Production Configuration +## Development vs Production Configuration You should code your Doctrine2 bootstrapping with two different runtime models in mind. There are some serious benefits of using APC or Memcache in production. In development however this will frequently give you fatal @@ -240,14 +240,14 @@ classes are re-generated during script execution. Filesystem calls of that magni the database queries Doctrine issues. Additionally writing a proxy sets an exclusive file lock which can cause serious performance bottlenecks in systems with regular concurrent requests. -++ Connection Options +## Connection Options The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The DBAL configuration is explained in the [DBAL section](./../../../../../dbal/2.0/docs/reference/configuration/en). -++ Proxy Objects +## Proxy Objects A proxy object is an object that is put in place or used instead of the "real" object. A proxy object can add behavior to the object being proxied without that object being aware of it. In Doctrine 2, proxy objects are used to realize several features but mainly for transparent lazy-loading. @@ -259,7 +259,7 @@ Doctrine 2 implements a variant of the proxy pattern where it generates classes The method `EntityManager#getReference($entityName, $identifier)` lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier. You could simply do this: - [php] + getReference('MyProject\Model\Item', $itemId); @@ -277,11 +277,11 @@ Just like other proxies it will transparently initialize itself on first access. > association in that query. This will override the 'fetch' option specified in > the mapping for that association, but only for that query. -+++ Generating Proxy classes +### Generating Proxy classes Proxy classes can either be generated manually through the Doctrine Console or automatically by Doctrine. The configuration option that controls this behavior is: - [php] + setAutoGenerateProxyClasses($bool); $config->getAutoGenerateProxyClasses(); @@ -290,13 +290,13 @@ To eliminate the overhead of proxy class generation during runtime, set this con $ ./doctrine orm:generate-proxies -++ Multiple Metadata Sources +## Multiple Metadata Sources When using different components using Doctrine 2 you may end up with them using two different metadata drivers, for example XML and YAML. You can use the DriverChain Metadata implementations to aggregate these drivers based on namespaces: - [php] + addDriver($xmlDriver, 'Doctrine\Tests\Models\Company'); $chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping'); diff --git a/manual/en/dql-doctrine-query-language.rst b/manual/en/dql-doctrine-query-language.rst new file mode 100644 index 000000000..2ec388c59 --- /dev/null +++ b/manual/en/dql-doctrine-query-language.rst @@ -0,0 +1,1488 @@ +DQL Explained +------------- + +DQL stands for **D**octrine **Q**uery **L**anguage and is an Object +Query Language derivate that is very similar to the **H**ibernate +**Q**uery **L**anguage (HQL) or the **J**ava **P**ersistence +**Q**uery **L**anguage (JPQL). + +In essence, DQL provides powerful querying capabilities over your +object model. Imagine all your objects lying around in some storage +(like an object database). When writing DQL queries, think about +querying that storage to pick a certain subset of your objects. + + **CAUTION** A common mistake for beginners is to mistake DQL for + being just some form of SQL and therefore trying to use table names + and column names or join arbitrary tables together in a query. You + need to think about DQL as a query language for your object model, + not for your relational schema. + + +DQL is case in-sensitive, except for namespace, class and field +names, which are case sensitive. + +Types of DQL queries +-------------------- + +DQL as a query language has SELECT, UPDATE and DELETE constructs +that map to their corresponding SQL statement types. INSERT +statements are not allowed in DQL, because entities and their +relations have to be introduced into the persistence context +through ``EntityManager#persist()`` to ensure consistency of your +object model. + +DQL SELECT statements are a very powerful way of retrieving parts +of your domain model that are not accessible via associations. +Additionally they allow to retrieve entities and their associations +in one single sql select statement which can make a huge difference +in performance in contrast to using several queries. + +DQL UPDATE and DELETE statements offer a way to execute bulk +changes on the entities of your domain model. This is often +necessary when you cannot load all the affected entities of a bulk +update into memory. + +SELECT queries +-------------- + +DQL SELECT clause +~~~~~~~~~~~~~~~~~ + +The select clause of a DQL query specifies what appears in the +query result. The composition of all the expressions in the select +clause also influences the nature of the query result. + +Here is an example that selects all users with an age > 20: + +:: + + createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20'); + $users = $query->getResult(); + +Lets examine the query: + + +- ``u`` is a so called identification variable or alias that + refers to the ``MyProject\Model\User`` class. By placing this alias + in the SELECT clause we specify that we want all instances of the + User class that are matched by this query appear in the query + result. +- The FROM keyword is always followed by a fully-qualified class + name which in turn is followed by an identification variable or + alias for that class name. This class designates a root of our + query from which we can navigate further via joins (explained + later) and path expressions. +- The expression ``u.age`` in the WHERE clause is a path + expression. Path expressions in DQL are easily identified by the + use of the '.' operator that is used for constructing paths. The + path expression ``u.age`` refers to the ``age`` field on the User + class. + +The result of this query would be a list of User objects where all +users are older than 20. + +The SELECT clause allows to specify both class identification +variables that signal the hydration of a complete entity class or +just fields of the entity using the syntax ``u.name``. Combinations +of both are also allowed and it is possible to wrap both fields and +identification values into aggregation and DQL functions. Numerical +fields can be part of computations using mathematical operations. +See the sub-section on +`DQL Functions, Aggregates and Operations <#dqlfn>`_ on more +information. + +Joins +~~~~~ + +A SELECT query can contain joins. There are 2 types of JOINs: +"Regular" Joins and "Fetch" Joins. + +**Regular Joins**: Used to limit the results and/or compute +aggregate values. + +**Fetch Joins**: In addition to the uses of regular joins: Used to +fetch related entities and include them in the hydrated result of a +query. + +There is no special DQL keyword that distinguishes a regular join +from a fetch join. A join (be it an inner or outer join) becomes a +"fetch join" as soon as fields of the joined entity appear in the +SELECT part of the DQL query outside of an aggregate function. +Otherwise its a "regular join". + +Example: + +Regular join of the address: + +:: + + createQuery("SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); + $users = $query->getResult(); + +Fetch join of the address: + +:: + + createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); + $users = $query->getResult(); + +When Doctrine hydrates a query with fetch-join it returns the class +in the FROM clause on the root level of the result array. In the +previous example an array of User instances is returned and the +address of each user is fetched and hydrated into the +``User#address`` variable. If you access the address Doctrine does +not need to lazy load the association with another query. + + **NOTE** Doctrine allows you to walk all the associations between + all the objects in your domain model. Objects that were not already + loaded from the database are replaced with lazy load proxy + instances. Non-loaded Collections are also replaced by lazy-load + instances that fetch all the contained objects upon first access. + However relying on the lazy-load mechanism leads to many small + queries executed against the database, which can significantly + affect the performance of your application. **Fetch Joins** are the + solution to hydrate most or all of the entities that you need in a + single SELECT query. + + +Named and Positional Parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +DQL supports both named and positional parameters, however in +contrast to many SQL dialects positional parameters are specified +with numbers, for example "?1", "?2" and so on. Named parameters +are specified with ":name1", ":name2" and so on. + +DQL SELECT Examples +~~~~~~~~~~~~~~~~~~~ + +This section contains a large set of DQL queries and some +explanations of what is happening. The actual result also depends +on the hydration mode. + +Hydrate all User entities: + +:: + + createQuery('SELECT u FROM MyProject\Model\User u'); + $users = $query->getResult(); // array of User objects + +Retrieve the IDs of all CmsUsers: + +:: + + createQuery('SELECT u.id FROM CmsUser u'); + $ids = $query->getResult(); // array of CmsUser ids + +Retrieve the IDs of all users that have written an article: + +:: + + createQuery('SELECT DISTINCT u.id FROM CmsArticle a JOIN a.user u'); + $ids = $query->getResult(); // array of CmsUser ids + +Retrieve all articles and sort them by the name of the articles +users instance: + +:: + + createQuery('SELECT a FROM CmsArticle a JOIN a.user u ORDER BY u.name ASC'); + $articles = $query->getResult(); // array of CmsArticle objects + +Retrieve the Username and Name of a CmsUser: + +:: + + createQuery('SELECT u.username, u.name FROM CmsUser u'); + $users = $query->getResults(); // array of CmsUser username and id values + echo $users[0]['username']; + +Retrieve a ForumUser and his single associated entity: + +:: + + createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a'); + $users = $query->getResult(); // array of ForumUser objects with the avatar association loaded + echo get_class($users[0]->getAvatar()); + +Retrieve a CmsUser and fetch join all the phonenumbers he has: + +:: + + createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p'); + $users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded + $phonenumbers = $users[0]->getPhonenumbers(); + +Hydrate a result in Ascending: + +:: + + createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC'); + $users = $query->getResult(); // array of ForumUser objects + +Or in Descending Order: + +:: + + createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC'); + $users = $query->getResult(); // array of ForumUser objects + +Using Aggregate Functions: + +:: + + createQuery('SELECT COUNT(u.id) FROM Entities\User u'); + $count = $query->getSingleScalarResult(); + +With WHERE Clause and Positional Parameter: + +:: + + createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1'); + $users = $query->getResult(); // array of ForumUser objects + +With WHERE Clause and Named Parameter: + +:: + + createQuery('SELECT u FROM ForumUser u WHERE u.username = :name'); + $users = $query->getResult(); // array of ForumUser objects + +With Nested Conditions in WHERE Clause: + +:: + + createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id'); + $users = $query->getResult(); // array of ForumUser objects + +With COUNT DISTINCT: + +:: + + createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser'); + $users = $query->getResult(); // array of ForumUser objects + +With Arithmetic Expression in WHERE clause: + +:: + + createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); + $users = $query->getResult(); // array of ForumUser objects + +Using a LEFT JOIN to hydrate all user-ids and optionally associated +article-ids: + +:: + + createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a'); + $results = $query->getResult(); // array of user ids and every article_id for each user + +Restricting a JOIN clause by additional conditions: + +:: + + createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%'"); + $users = $query->getResult(); + +Using several Fetch JOINs: + +:: + + createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c'); + $users = $query->getResult(); + +BETWEEN in WHERE clause: + +:: + + createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2'); + $usernames = $query->getResult(); + +DQL Functions in WHERE clause: + +:: + + createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'"); + $usernames = $query->getResult(); + +IN() Expression: + +:: + + createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)'); + $usernames = $query->getResult(); + + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id IN (1, 2)'); + $users = $query->getResult(); + + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id NOT IN (1)'); + $users = $query->getResult(); + +CONCAT() DQL Function: + +:: + + createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); + $ids = $query->getResult(); + + $query = $em->createQuery('SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1'); + $idUsernames = $query->getResult(); + +EXISTS in WHERE clause with correlated Subquery + +:: + + createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.user = u.id)'); + $ids = $query->getResult(); + +Get all users who are members of $group. + +:: + + createQuery('SELECT u.id FROM CmsUser u WHERE :groupId MEMBER OF u.groups'); + $query->setParameter(':groupId', $group); + $ids = $query->getResult(); + +Get all users that have more than 1 phonenumber + +:: + + createQuery('SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1'); + $users = $query->getResult(); + +Get all users that have no phonenumber + +:: + + createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); + $users = $query->getResult(); + +Get all instances of a specific type, for use with inheritance +hierarchies: + +:: + + createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee'); + $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1'); + $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1'); + +Partial Object Syntax +^^^^^^^^^^^^^^^^^^^^^ + +By default when you run a DQL query in Doctrine and select only a +subset of the fields for a given entity, you do not receive objects +back. Instead, you receive only arrays as a flat rectangular result +set, similar to how you would if you were just using SQL directly +and joining some data. + +If you want to select partial objects you can use the ``partial`` +DQL keyword: + +:: + + createQuery('SELECT partial u.{id, username} FROM CmsUser u'); + $users = $query->getResult(); // array of partially loaded CmsUser objects + +You use the partial syntax when joining as well: + +:: + + createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a'); + $users = $query->getResult(); // array of partially loaded CmsUser objects + +Using INDEX BY +~~~~~~~~~~~~~~ + +The INDEX BY construct is nothing that directly translates into SQL +but that affects object and array hydration. After each FROM and +JOIN clause you specify by which field this class should be indexed +in the result. By default a result is incremented by numerical keys +starting with 0. However with INDEX BY you can specify any other +column to be the key of your result, it really only makes sense +with primary or unique fields though: + +:: + + [sql] + SELECT u.id, u.status, upper(u.name) nameUpper FROM User u INDEX BY u.id + JOIN u.phonenumbers p INDEX BY p.phonenumber + +Returns an array of the following kind, indexed by both user-id +then phonenumber-id: + +:: + + array + 0 => + array + 1 => + object(stdClass)[299] + public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) + public 'id' => int 1 + .. + 'nameUpper' => string 'ROMANB' (length=6) + 1 => + array + 2 => + object(stdClass)[298] + public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) + public 'id' => int 2 + ... + 'nameUpper' => string 'JWAGE' (length=5) + +UPDATE queries +-------------- + +DQL not only allows to select your Entities using field names, you +can also execute bulk updates on a set of entities using an +DQL-UPDATE query. The Syntax of an UPDATE query works as expected, +as the following example shows: + +:: + + UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) + +References to related entities are only possible in the WHERE +clause and using sub-selects. + + **CAUTION** DQL UPDATE statements are ported directly into a + Database UPDATE statement and therefore bypass any locking scheme + and do not increment the version column. Entities that are already + loaded into the persistence context will *NOT* be synced with the + updated database state. It is recommended to call + ``EntityManager#clear()`` and retrieve new instances of any + affected entity. + + +DELETE queries +-------------- + +DELETE queries can also be specified using DQL and their syntax is +as simple as the UPDATE syntax: + +:: + + DELETE MyProject\Model\User u WHERE u.id = 4 + +The same restrictions apply for the reference of related entities. + + **CAUTION** DQL DELETE statements are ported directly into a + Database DELETE statement and therefore bypass any checks for the + version column if they are not explicitly added to the WHERE clause + of the query. Additionally Deletes of specifies entities are *NOT* + cascaded to related entities even if specified in the metadata. + + +Functions, Operators, Aggregates +-------------------------------- + +DQL Functions +~~~~~~~~~~~~~ + +The following functions are supported in SELECT, WHERE and HAVING +clauses: + + +- ABS(arithmetic\_expression) +- CONCAT(str1, str2) +- CURRENT\_DATE() - Return the current date +- CURRENT\_TIME() - Returns the current time +- CURRENT\_TIMESTAMP() - Returns a timestamp of the current date + and time. +- LENGTH(str) - Returns the length of the given string +- LOCATE(needle, haystack [, offset]) - Locate the first + occurrence of the substring in the string. +- LOWER(str) - returns the string lowercased. +- MOD(a, b) - Return a MOD b. +- SIZE(collection) - Return the number of elements in the + specified collection +- SQRT(q) - Return the square-root of q. +- SUBSTRING(str, start [, length]) - Return substring of given + string. +- TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim + the string by the given trim char, defaults to whitespaces. +- UPPER(str) - Return the upper-case of the given string. + +Arithmetic operators +~~~~~~~~~~~~~~~~~~~~ + +You can do math in DQL using numeric values, for example: + +:: + + SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 + +Aggregate Functions +~~~~~~~~~~~~~~~~~~~ + +The following aggregate functions are allowed in SELECT and GROUP +BY clauses: AVG, COUNT, MIN, MAX, SUM + +Other Expressions +~~~~~~~~~~~~~~~~~ + +DQL offers a wide-range of additional expressions that are known +from SQL, here is a list of all the supported constructs: + + +- ``ALL/ANY/SOME`` - Used in a WHERE clause followed by a + sub-select this works like the equivalent constructs in SQL. +- ``BETWEEN a AND b`` and ``NOT BETWEEN a AND b`` can be used to + match ranges of arithmetic values. +- ``IN (x1, x2, ...)`` and ``NOT IN (x1, x2, ..)`` can be used to + match a set of given values. +- ``LIKE ..`` and ``NOT LIKE ..`` match parts of a string or text + using % as a wildcard. +- ``IS NULL`` and ``IS NOT NULL`` to check for null values +- ``EXISTS`` and ``NOT EXISTS`` in combination with a sub-select + +Adding your own functions to the DQL language +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default DQL comes with functions that are part of a large basis +of underlying databases. However you will most likely choose a +database platform at the beginning of your project and most likely +never change it. For this cases you can easily extend the DQL +parser with own specialized platform functions. + +You can register custom DQL functions in your ORM Configuration: + +:: + + addCustomStringFunction($name, $class); + $config->addCustomNumericFunction($name, $class); + $config->addCustomDatetimeFunction($name, $class); + + $em = EntityManager::create($dbParams, $config); + +The functions have to return either a string, numeric or datetime +value depending on the registered function type. As an example we +will add a MySQL specific FLOOR() functionality. All the given +classes have to implement the base class : + +:: + + walkSimpleArithmeticExpression( + $this->simpleArithmeticExpression + ) . ')'; + } + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $lexer = $parser->getLexer(); + + $parser->match(Lexer::T_ABS); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + } + +We will register the function by calling and can then use it: + +:: + + `_ +is an inheritance mapping strategy where all classes of a hierarchy +are mapped to a single database table. In order to distinguish +which row represents which type in the hierarchy a so-called +discriminator column is used. + +First we need to setup an example set of entities to use. In this +scenario it is a generic Person and Employee example: + +:: + + setName('test'); + $employee->setDepartment('testing'); + $em->persist($employee); + $em->flush(); + +Now lets run a simple query to retrieve the ``Employee`` we just +created: + +:: + + [sql] + SELECT e FROM Entities\Employee e WHERE e.name = 'test' + +If we check the generated SQL you will notice it has some special +conditions added to ensure that we will only get back ``Employee`` +entities: + +:: + + [sql] + SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, p0_.discr AS discr3 FROM Person p0_ WHERE (p0_.name = ?) AND p0_.discr IN ('employee') + +Class Table Inheritance +~~~~~~~~~~~~~~~~~~~~~~~ + +`Class Table Inheritance `_ +is an inheritance mapping strategy where each class in a hierarchy +is mapped to several tables: its own table and the tables of all +parent classes. The table of a child class is linked to the table +of a parent class through a foreign key constraint. Doctrine 2 +implements this strategy through the use of a discriminator column +in the topmost table of the hierarchy because this is the easiest +way to achieve polymorphic queries with Class Table Inheritance. + +The example for class table inheritance is the same as single +table, you just need to change the inheritance type from +``SINGLE_TABLE`` to ``JOINED``: + +:: + + createQuery('select u from MyProject\Model\User u'); + + // example2: using setDql + $q = $em->createQuery(); + $q->setDql('select u from MyProject\Model\User u'); + +Query Result Formats +~~~~~~~~~~~~~~~~~~~~ + +The format in which the result of a DQL SELECT query is returned +can be influenced by a so-called ``hydration mode``. A hydration +mode specifies a particular way in which an SQL result set is +transformed. Each hydration mode has its own dedicated method on +the Query class. Here they are: + + +- ``Query#getResult()``: Retrieves a collection of objects. The + result is either a plain collection of objects (pure) or an array + where the objects are nested in the result rows (mixed). +- ``Query#getSingleResult()``: Retrieves a single object. If the + result contains more than one object, an exception is thrown. The + pure/mixed distinction does not apply. +- ``Query#getArrayResult()``: Retrieves an array graph (a nested + array) that is largely interchangeable with the object graph + generated by ``Query#getResultList()`` for read-only purposes. + + **NOTE** An array graph can differ from the corresponding object + graph in certain scenarios due to the difference of the identity + semantics between arrays and objects. + + + +- ``Query#getScalarResult()``: Retrieves a flat/rectangular result + set of scalar values that can contain duplicate data. The + pure/mixed distinction does not apply. +- ``Query#getSingleScalarResult()``: Retrieves a single scalar + value from the result returned by the dbms. If the result contains + more than a single scalar value, an exception is thrown. The + pure/mixed distinction does not apply. + +Instead of using these methods, you can alternatively use the +general-purpose method +``Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)``. +Using this method you can directly supply the hydration mode as the +second parameter via one of the Query constants. In fact, the +methods mentioned earlier are just convenient shortcuts for the +execute method. For example, the method ``Query#getResultList()`` +internally invokes execute, passing in ``Query::HYDRATE_OBJECT`` as +the hydration mode. + +The use of the methods mentioned earlier is generally preferred as +it leads to more concise code. + +Pure and Mixed Results +~~~~~~~~~~~~~~~~~~~~~~ + +The nature of a result returned by a DQL SELECT query retrieved +through ``Query#getResult()`` or ``Query#getArrayResult()`` can be +of 2 forms: **pure** and **mixed**. In the previous simple +examples, you already saw a "pure" query result, with only objects. +By default, the result type is **pure** but +**as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. +A mixed result has a different structure than a pure result in +order to accommodate for the scalar values. + +A pure result usually looks like this: + +:: + + array + [0] => Object + [1] => Object + [2] => Object + ... + +A mixed result on the other hand has the following general +structure: + +:: + + array + array + [0] => Object + [1] => "some scalar string" + ['count'] => 42 + // ... more scalar values, either indexed numerically or with a name + array + [0] => Object + [1] => "some scalar string" + ['count'] => 42 + // ... more scalar values, either indexed numerically or with a name + +To better understand mixed results, consider the following DQL +query: + +:: + + [sql] + SELECT u, UPPER(u.name) nameUpper FROM MyProject\Model\User u + +This query makes use of the ``UPPER`` DQL function that returns a +scalar value and because there is now a scalar value in the SELECT +clause, we get a mixed result. + +Here is how the result could look like: + +:: + + array + array + [0] => User (Object) + ['nameUpper'] => "Roman" + array + [0] => User (Object) + ['nameUpper'] => "Jonathan" + ... + +And here is how you would access it in PHP code: + +:: + + getName(); + echo "Name UPPER: " . $row['nameUpper']; + } + +You may have observed that in a mixed result, the object always +ends up on index 0 of a result row. + +Hydration Modes +~~~~~~~~~~~~~~~ + +Each of the Hydration Modes makes assumptions about how the result +is returned to user land. You should know about all the details to +make best use of the different result formats: + +The constants for the different hydration modes are: + + +- Query::HYDRATE\_OBJECT +- Query::HYDRATE\_ARRAY +- Query::HYDRATE\_SCALAR +- Query::HYDRATE\_SINGLE\_SCALAR + +Object Hydration +^^^^^^^^^^^^^^^^ + +Object hydration hydrates the result set into the object graph: + +:: + + createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_OBJECT); + +Array Hydration +^^^^^^^^^^^^^^^ + +You can run the same query with array hydration and the result set +is hydrated into an array that represents the object graph: + +:: + + createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_ARRAY); + +You can use the ``getArrayResult()`` shortcut as well: + +:: + + getArrayResult(); + +Scalar Hydration +^^^^^^^^^^^^^^^^ + +If you want to return a flat rectangular result set instead of an +object graph you can use scalar hydration: + +:: + + createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_SCALAR); + echo $users[0]['u_id']; + +The following assumptions are made about selected fields using +Scalar Hydration: + + +1. Fields from classes are prefixed by the DQL alias in the result. + A query of the kind 'SELECT u.name ..' returns a key 'u\_name' in + the result rows. + +Single Scalar Hydration +^^^^^^^^^^^^^^^^^^^^^^^ + +If you a query which returns just a single scalar value you can use +single scalar hydration: + +:: + + createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); + $query->setParameter(1, 'jwage'); + $numArticles = $query->getResult(Query::HYDRATE_SINGLE_SCALAR); + +You can use the ``getSingleScalarResult()`` shortcut as well: + +:: + + getSingleScalarResult(); + +Custom Hydration Modes +^^^^^^^^^^^^^^^^^^^^^^ + +You can easily add your own custom hydration modes by first +creating a class which extends ``AbstractHydrator``: + +:: + + _stmt->fetchAll(PDO::FETCH_ASSOC); + } + } + +Next you just need to add the class to the ORM configuration: + +:: + + getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); + +Now the hydrator is ready to be used in your queries: + +:: + + createQuery('SELECT u FROM CmsUser u'); + $results = $query->getResult('CustomHydrator'); + +Iterating Large Result Sets +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are situations when a query you want to execute returns a +very large result-set that needs to be processed. All the +previously described hydration modes completely load a result-set +into memory which might not be feasible with large result sets. See +the `Batch Processing `_ section on details how +to iterate large result sets. + +Functions +~~~~~~~~~ + +The following methods exist on the ``AbstractQuery`` which both +``Query`` and ``NativeQuery`` extend from. + +Parameters +^^^^^^^^^^ + +Prepared Statements that use numerical or named wildcards require +additional parameters to be executable against the database. To +pass parameters to the query the following methods can be used: + + +- ``AbstractQuery::setParameter($param, $value)`` - Set the + numerical or named wildcard to the given value. +- ``AbstractQuery::setParameters(array $params)`` - Set an array + of parameter key-value pairs. +- ``AbstractQuery::getParameter($param)`` +- ``AbstractQuery::getParameters()`` + +Cache related API +^^^^^^^^^^^^^^^^^ + +You can cache query results based either on all variables that +define the result (SQL, Hydration Mode, Parameters and Hints) or on +user-defined cache keys. However by default query results are not +cached at all. You have to enable the result cache on a per query +basis. The following example shows a complete workflow using the +Result Cache API: + +:: + + createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); + $query->setParameter(1, 12); + + $query->setResultCacheDriver(new ApcCache()); + + $query->useResultCache(true) + ->setResultCacheLifeTime($seconds = 3600); + + $result = $query->getResult(); // cache miss + + $query->expireResultCache(true); + $result = $query->getResult(); // forced expire, cache miss + + $query->setResultCacheId('my_query_result'); + $result = $query->getResult(); // saved in given result cache id. + + // or call useResultCache() with all parameters: + $query->useResultCache(true, $seconds = 3600, 'my_query_result'); + $result = $query->getResult(); // cache hit! + + **TIP!** You can set the Result Cache Driver globally on the + ``Doctrine\ORM\Configuration`` instance so that it is passed to + every ``Query`` and ``NativeQuery`` instance. + + +Query Hints +^^^^^^^^^^^ + +You can pass hints to the query parser and hydrators by using the +``AbstractQuery::setHint($name, $value)`` method. Currently there +exist mostly internal query hints that are not be consumed in +userland. However the following few hints are to be used in +userland: + + +- Query::HINT\_FORCE\_PARTIAL\_LOAD - Allows to hydrate objects + although not all their columns are fetched. This query hint can be + used to handle memory consumption problems with large result-sets + that contain char or binary data. Doctrine has no way of implicitly + reloading this data. Partially loaded objects have to be passed to + ``EntityManager::refresh()`` if they are to be reloaded fully from + the database. +- Query::HINT\_REFRESH - This query is used internally by + ``EntityManager::refresh()`` and can be used in userland as well. + If you specify this hint and a query returns the data for an entity + that is already managed by the UnitOfWork, the fields of the + existing entity will be refreshed. In normal operation a result-set + that loads data of an already existing entity is discarded in favor + of the already existing entity. +- Query::HINT\_CUSTOM\_TREE\_WALKERS - An array of additional + ``Doctrine\ORM\Query\TreeWalker`` instances that are attached to + the DQL query parsing process. + +Query Cache (DQL Query Only) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Parsing a DQL query and converting it into an SQL query against the +underlying database platform obviously has some overhead in +contrast to directly executing Native SQL queries. That is why +there is a dedicated Query Cache for caching the DQL parser +results. In combination with the use of wildcards you can reduce +the number of parsed queries in production to zero. + +The Query Cache Driver is passed from the +``Doctrine\ORM\Configuration`` instance to each +``Doctrine\ORM\Query`` instance by default and is also enabled by +default. This also means you don't regularly need to fiddle with +the parameters of the Query Cache, however if you do there are +several methods to interact with it: + + +- ``Query::setQueryCacheDriver($driver)`` - Allows to set a Cache + instance +- ``Query::setQueryCacheLifeTime($seconds = 3600)`` - Set lifetime + of the query caching. +- ``Query::expireQueryCache($bool)`` - Enforce the expiring of the + query cache if set to true. +- ``Query::getExpireQueryCache()`` +- ``Query::getQueryCacheDriver()`` +- ``Query::getQueryCacheLifeTime()`` + +First and Max Result Items (DQL Query Only) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can limit the number of results returned from a DQL query as +well as specify the starting offset, Doctrine then uses a strategy +of manipulating the select query to return only the requested +number of results: + + +- ``Query::setMaxResults($maxResults)`` +- ``Query::setFirstResult($offset)`` + + **NOTE** If your query contains a fetch-joined collection + specifying the result limit methods are not working as you would + expect. Set Max Results restricts the number of database result + rows, however in the case of fetch-joined collections one root + entity might appear in many rows, effectively hydrating less than + the specified number of results. + + +EBNF +---- + +The following context-free grammar, written in an EBNF variant, +describes the Doctrine Query Language. You can consult this grammar +whenever you are unsure about what is possible with DQL or what the +correct syntax for a particular query should be. + +Document syntax: +~~~~~~~~~~~~~~~~ + + +- non-terminals begin with an upper case character +- terminals begin with a lower case character +- parentheses (...) are used for grouping +- square brackets [...] are used for defining an optional part, + e.g. zero or one time +- curly brackets {...} are used for repetition, e.g. zero or more + times +- double quotation marks "..." define a terminal string a vertical + bar \| represents an alternative + +Terminals +~~~~~~~~~ + + +- identifier (name, email, ...) +- string ('foo', 'bar''s house', '%ninja%', ...) +- char ('/', '\\', ' ', ...) +- integer (-1, 0, 1, 34, ...) +- float (-0.23, 0.007, 1.245342E+8, ...) +- boolean (false, true) + +Query Language +~~~~~~~~~~~~~~ + +:: + + QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement + +Statements +~~~~~~~~~~ + +:: + + SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + UpdateStatement ::= UpdateClause [WhereClause] + DeleteStatement ::= DeleteClause [WhereClause] + +Identifiers +~~~~~~~~~~~ + +:: + + /* Alias Identification usage (the "u" of "u.name") */ + IdentificationVariable ::= identifier + + /* Alias Identification declaration (the "u" of "FROM User u") */ + AliasIdentificationVariable :: = identifier + + /* identifier that must be a class name (the "User" of "FROM User u") */ + AbstractSchemaName ::= identifier + + /* identifier that must be a field (the "name" of "u.name") */ + /* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */ + FieldIdentificationVariable ::= identifier + + /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ + CollectionValuedAssociationField ::= FieldIdentificationVariable + + /* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */ + SingleValuedAssociationField ::= FieldIdentificationVariable + + /* identifier that must be an embedded class state field (for the future) */ + EmbeddedClassStateField ::= FieldIdentificationVariable + + /* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ + /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ + SimpleStateField ::= FieldIdentificationVariable + + /* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */ + AliasResultVariable = identifier + + /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ + ResultVariable = identifier + +Path Expressions +~~~~~~~~~~~~~~~~ + +:: + + /* "u.Group" or "u.Phonenumbers" declarations */ + JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) + + /* "u.Group" or "u.Phonenumbers" usages */ + AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression + + /* "u.name" or "u.Group" */ + SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression + + /* "u.name" or "u.Group.name" */ + StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField + + /* "u.Group" */ + SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField + + /* "u.Group.Permissions" */ + CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField + + /* "name" */ + StateField ::= {EmbeddedClassStateField "."}* SimpleStateField + + /* "u.name" or "u.address.zip" (address = EmbeddedClassStateField) */ + SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField + +Clauses +~~~~~~~ + +:: + + SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* + SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression + UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* + DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable + FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* + SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* + WhereClause ::= "WHERE" ConditionalExpression + HavingClause ::= "HAVING" ConditionalExpression + GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* + OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* + Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + +Items +~~~~~ + +:: + + UpdateItem ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue + OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] + GroupByItem ::= IdentificationVariable | SingleValuedPathExpression + NewValue ::= ScalarExpression | SimpleEntityExpression | "NULL" + +From, Join and Index by +~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* + SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) + JoinVariableDeclaration ::= Join [IndexBy] + RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable + Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression + ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] + IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression + +Select Expressions +~~~~~~~~~~~~~~~~~~ + +:: + + SelectExpression ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")" | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable] + SimpleSelectExpression ::= ScalarExpression | IdentificationVariable | + (AggregateExpression [["AS"] AliasResultVariable]) + PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet + PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" + +Conditional Expressions +~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* + ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* + ConditionalFactor ::= ["NOT"] ConditionalPrimary + ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" + SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression | + InExpression | NullComparisonExpression | ExistsExpression | + EmptyCollectionComparisonExpression | CollectionMemberExpression + +Collection Expressions +~~~~~~~~~~~~~~~~~~~~~~ + +:: + + EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" + CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression + +Literal Values +~~~~~~~~~~~~~~ + +:: + + Literal ::= string | char | integer | float | boolean + InParameter ::= Literal | InputParameter + +Input Parameter +~~~~~~~~~~~~~~~ + +:: + + InputParameter ::= PositionalParameter | NamedParameter + PositionalParameter ::= "?" integer + NamedParameter ::= ":" string + +Arithmetic Expressions +~~~~~~~~~~~~~~~~~~~~~~ + +:: + + ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" + SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* + ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* + ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary + ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" + | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings + | FunctionsReturningDatetime | IdentificationVariable | InputParameter + +Scalar and Type Expressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression + BooleanPrimary | CaseExpression | EntityTypeExpression + CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | + CoalesceExpression | NullifExpression + GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression + "END" + WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* + "ELSE" ScalarExpression "END" + CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" + NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" + StringExpression ::= StringPrimary | "(" Subselect ")" + StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression + BooleanExpression ::= BooleanPrimary | "(" Subselect ")" + BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter + EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression + SimpleEntityExpression ::= IdentificationVariable | InputParameter + DatetimeExpression ::= DatetimePrimary | "(" Subselect ")" + DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression + +Aggregate Expressions +~~~~~~~~~~~~~~~~~~~~~ + +:: + + AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | + "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" + +Other Expressions +~~~~~~~~~~~~~~~~~ + +QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS + +:: + + QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" + BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression + ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) + InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" + LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] + NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" + ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" + ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" + +Functions +~~~~~~~~~ + +:: + + FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime + + FunctionsReturningNumerics ::= + "LENGTH" "(" StringPrimary ")" | + "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | + "ABS" "(" SimpleArithmeticExpression ")" | "SQRT" "(" SimpleArithmeticExpression ")" | + "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + "SIZE" "(" CollectionValuedPathExpression ")" + + FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" + + FunctionsReturningStrings ::= + "CONCAT" "(" StringPrimary "," StringPrimary ")" | + "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | + "LOWER" "(" StringPrimary ")" | + "UPPER" "(" StringPrimary ")" + + diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index c8dcefbc9..1a3f6ab6c 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -1,4 +1,4 @@ -++ DQL Explained +## DQL Explained DQL stands for **D**octrine **Q**uery **L**anguage and is an Object Query Language derivate that is very similar to the **H**ibernate **Q**uery **L**anguage (HQL) or the **J**ava **P**ersistence **Q**uery **L**anguage (JPQL). @@ -12,7 +12,7 @@ In essence, DQL provides powerful querying capabilities over your object model. DQL is case in-sensitive, except for namespace, class and field names, which are case sensitive. -++ Types of DQL queries +## Types of DQL queries DQL as a query language has SELECT, UPDATE and DELETE constructs that map to their corresponding SQL statement types. INSERT statements are not allowed in DQL, because entities and their relations @@ -28,15 +28,15 @@ DQL UPDATE and DELETE statements offer a way to execute bulk changes on the enti domain model. This is often necessary when you cannot load all the affected entities of a bulk update into memory. -++ SELECT queries +## SELECT queries -+++ DQL SELECT clause +### DQL SELECT clause The select clause of a DQL query specifies what appears in the query result. The composition of all the expressions in the select clause also influences the nature of the query result. Here is an example that selects all users with an age > 20: - [php] + createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20'); $users = $query->getResult(); @@ -55,7 +55,7 @@ identification values into aggregation and DQL functions. Numerical fields can be part of computations using mathematical operations. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn) on more information. -+++ Joins +### Joins A SELECT query can contain joins. There are 2 types of JOINs: "Regular" Joins and "Fetch" Joins. @@ -69,13 +69,13 @@ Example: Regular join of the address: - [php] + createQuery("SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); $users = $query->getResult(); Fetch join of the address: - [php] + createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); $users = $query->getResult(); @@ -93,143 +93,143 @@ the address Doctrine does not need to lazy load the association with another que > **Fetch Joins** are the solution to hydrate most or all of the entities that you > need in a single SELECT query. -+++ Named and Positional Parameters +### Named and Positional Parameters DQL supports both named and positional parameters, however in contrast to many SQL dialects positional parameters are specified with numbers, for example "?1", "?2" and so on. Named parameters are specified with ":name1", ":name2" and so on. -+++ DQL SELECT Examples +### DQL SELECT Examples This section contains a large set of DQL queries and some explanations of what is happening. The actual result also depends on the hydration mode. Hydrate all User entities: - [php] + createQuery('SELECT u FROM MyProject\Model\User u'); $users = $query->getResult(); // array of User objects Retrieve the IDs of all CmsUsers: - [php] + createQuery('SELECT u.id FROM CmsUser u'); $ids = $query->getResult(); // array of CmsUser ids Retrieve the IDs of all users that have written an article: - [php] + createQuery('SELECT DISTINCT u.id FROM CmsArticle a JOIN a.user u'); $ids = $query->getResult(); // array of CmsUser ids Retrieve all articles and sort them by the name of the articles users instance: - [php] + createQuery('SELECT a FROM CmsArticle a JOIN a.user u ORDER BY u.name ASC'); $articles = $query->getResult(); // array of CmsArticle objects Retrieve the Username and Name of a CmsUser: - [php] + createQuery('SELECT u.username, u.name FROM CmsUser u'); $users = $query->getResults(); // array of CmsUser username and id values echo $users[0]['username']; Retrieve a ForumUser and his single associated entity: - [php] + createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a'); $users = $query->getResult(); // array of ForumUser objects with the avatar association loaded echo get_class($users[0]->getAvatar()); Retrieve a CmsUser and fetch join all the phonenumbers he has: - [php] + createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p'); $users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded $phonenumbers = $users[0]->getPhonenumbers(); Hydrate a result in Ascending: - [php] + createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC'); $users = $query->getResult(); // array of ForumUser objects Or in Descending Order: - [php] + createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC'); $users = $query->getResult(); // array of ForumUser objects Using Aggregate Functions: - [php] + createQuery('SELECT COUNT(u.id) FROM Entities\User u'); $count = $query->getSingleScalarResult(); With WHERE Clause and Positional Parameter: - [php] + createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1'); $users = $query->getResult(); // array of ForumUser objects With WHERE Clause and Named Parameter: - [php] + createQuery('SELECT u FROM ForumUser u WHERE u.username = :name'); $users = $query->getResult(); // array of ForumUser objects With Nested Conditions in WHERE Clause: - [php] + createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id'); $users = $query->getResult(); // array of ForumUser objects With COUNT DISTINCT: - [php] + createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser'); $users = $query->getResult(); // array of ForumUser objects With Arithmetic Expression in WHERE clause: - [php] + createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); $users = $query->getResult(); // array of ForumUser objects Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids: - [php] + createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a'); $results = $query->getResult(); // array of user ids and every article_id for each user Restricting a JOIN clause by additional conditions: - [php] + createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%'"); $users = $query->getResult(); Using several Fetch JOINs: - [php] + createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c'); $users = $query->getResult(); BETWEEN in WHERE clause: - [php] + createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2'); $usernames = $query->getResult(); DQL Functions in WHERE clause: - [php] + createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'"); $usernames = $query->getResult(); IN() Expression: - [php] + createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)'); $usernames = $query->getResult(); @@ -241,7 +241,7 @@ IN() Expression: CONCAT() DQL Function: - [php] + createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); $ids = $query->getResult(); @@ -250,37 +250,37 @@ CONCAT() DQL Function: EXISTS in WHERE clause with correlated Subquery - [php] + createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.user = u.id)'); $ids = $query->getResult(); Get all users who are members of $group. - [php] + createQuery('SELECT u.id FROM CmsUser u WHERE :groupId MEMBER OF u.groups'); $query->setParameter(':groupId', $group); $ids = $query->getResult(); Get all users that have more than 1 phonenumber - [php] + createQuery('SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1'); $users = $query->getResult(); Get all users that have no phonenumber - [php] + createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); $users = $query->getResult(); Get all instances of a specific type, for use with inheritance hierarchies: - [php] + createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee'); $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1'); $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1'); -++++ Partial Object Syntax +#### Partial Object Syntax By default when you run a DQL query in Doctrine and select only a subset of the fields for a given entity, you do not receive objects back. Instead, you receive @@ -289,17 +289,17 @@ were just using SQL directly and joining some data. If you want to select partial objects you can use the `partial` DQL keyword: - [php] + createQuery('SELECT partial u.{id, username} FROM CmsUser u'); $users = $query->getResult(); // array of partially loaded CmsUser objects You use the partial syntax when joining as well: - [php] + createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a'); $users = $query->getResult(); // array of partially loaded CmsUser objects -+++ Using INDEX BY +### Using INDEX BY The INDEX BY construct is nothing that directly translates into SQL but that affects object and array hydration. After each FROM and JOIN clause you specify by which field @@ -332,7 +332,7 @@ Returns an array of the following kind, indexed by both user-id then phonenumber ... 'nameUpper' => string 'JWAGE' (length=5) -++ UPDATE queries +## UPDATE queries DQL not only allows to select your Entities using field names, you can also execute bulk updates on a set of entities using an DQL-UPDATE query. The Syntax of an UPDATE query works as expected, as the following @@ -348,7 +348,7 @@ References to related entities are only possible in the WHERE clause and using s > loaded into the persistence context will *NOT* be synced with the updated database state. It > is recommended to call `EntityManager#clear()` and retrieve new instances of any affected entity. -++ DELETE queries +## DELETE queries DELETE queries can also be specified using DQL and their syntax is as simple as the UPDATE syntax: @@ -362,10 +362,10 @@ The same restrictions apply for the reference of related entities. > clause of the query. Additionally Deletes of specifies entities are *NOT* cascaded > to related entities even if specified in the metadata. -++ Functions, Operators, Aggregates +## Functions, Operators, Aggregates -+++ DQL Functions +### DQL Functions The following functions are supported in SELECT, WHERE and HAVING clauses: @@ -384,17 +384,17 @@ The following functions are supported in SELECT, WHERE and HAVING clauses: * TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str) - Trim the string by the given trim char, defaults to whitespaces. * UPPER(str) - Return the upper-case of the given string. -+++ Arithmetic operators +### Arithmetic operators You can do math in DQL using numeric values, for example: SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 -+++ Aggregate Functions +### Aggregate Functions The following aggregate functions are allowed in SELECT and GROUP BY clauses: AVG, COUNT, MIN, MAX, SUM -+++ Other Expressions +### Other Expressions DQL offers a wide-range of additional expressions that are known from SQL, here is a list of all the supported constructs: @@ -406,7 +406,7 @@ all the supported constructs: * `IS NULL` and `IS NOT NULL` to check for null values * `EXISTS` and `NOT EXISTS` in combination with a sub-select -+++ Adding your own functions to the DQL language +### Adding your own functions to the DQL language By default DQL comes with functions that are part of a large basis of underlying databases. However you will most likely choose a database platform at the beginning of your project and most likely never change it. For this cases you can @@ -414,7 +414,7 @@ easily extend the DQL parser with own specialized platform functions. You can register custom DQL functions in your ORM Configuration: - [php] + addCustomStringFunction($name, $class); $config->addCustomNumericFunction($name, $class); @@ -426,7 +426,7 @@ The functions have to return either a string, numeric or datetime value dependin we will add a MySQL specific FLOOR() functionality. All the given classes have to implement the base class \Doctrine\ORM\Query\AST\Functions\FunctionNode: - [php] + setName('test'); $employee->setDepartment('testing'); @@ -538,7 +538,7 @@ ensure that we will only get back `Employee` entities: [sql] SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, p0_.discr AS discr3 FROM Person p0_ WHERE (p0_.name = ?) AND p0_.discr IN ('employee') -+++ Class Table Inheritance +### Class Table Inheritance [Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html) is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hierarchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance. @@ -546,7 +546,7 @@ Doctrine 2 implements this strategy through the use of a discriminator column in The example for class table inheritance is the same as single table, you just need to change the inheritance type from `SINGLE_TABLE` to `JOINED`: - [php] + createQuery(); $q->setDql('select u from MyProject\Model\User u'); -+++ Query Result Formats +### Query Result Formats The format in which the result of a DQL SELECT query is returned can be influenced by a so-called `hydration mode`. A hydration mode specifies a particular way in which an SQL result set is transformed. Each hydration mode has its own dedicated method on the Query class. Here they are: @@ -610,7 +610,7 @@ Instead of using these methods, you can alternatively use the general-purpose me The use of the methods mentioned earlier is generally preferred as it leads to more concise code. -+++ Pure and Mixed Results +### Pure and Mixed Results The nature of a result returned by a DQL SELECT query retrieved through `Query#getResult()` or `Query#getArrayResult()` can be of 2 forms: **pure** and **mixed**. In the previous simple examples, you already saw a "pure" query result, with only objects. By default, the result type is **pure** but **as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. A mixed result has a different structure than a pure result in order to accommodate for the scalar values. @@ -656,7 +656,7 @@ Here is how the result could look like: And here is how you would access it in PHP code: - [php] + getName(); echo "Name UPPER: " . $row['nameUpper']; @@ -664,7 +664,7 @@ And here is how you would access it in PHP code: You may have observed that in a mixed result, the object always ends up on index 0 of a result row. -+++ Hydration Modes +### Hydration Modes Each of the Hydration Modes makes assumptions about how the result is returned to user land. You should know about all the details to make best use of the different result formats: @@ -676,34 +676,34 @@ The constants for the different hydration modes are: * Query::HYDRATE_SCALAR * Query::HYDRATE_SINGLE_SCALAR -++++ Object Hydration +#### Object Hydration Object hydration hydrates the result set into the object graph: - [php] + createQuery('SELECT u FROM CmsUser u'); $users = $query->getResult(Query::HYDRATE_OBJECT); -++++ Array Hydration +#### Array Hydration You can run the same query with array hydration and the result set is hydrated into an array that represents the object graph: - [php] + createQuery('SELECT u FROM CmsUser u'); $users = $query->getResult(Query::HYDRATE_ARRAY); You can use the `getArrayResult()` shortcut as well: - [php] + getArrayResult(); -++++ Scalar Hydration +#### Scalar Hydration If you want to return a flat rectangular result set instead of an object graph you can use scalar hydration: - [php] + createQuery('SELECT u FROM CmsUser u'); $users = $query->getResult(Query::HYDRATE_SCALAR); echo $users[0]['u_id']; @@ -712,26 +712,26 @@ The following assumptions are made about selected fields using Scalar Hydration: 1. Fields from classes are prefixed by the DQL alias in the result. A query of the kind 'SELECT u.name ..' returns a key 'u_name' in the result rows. -++++ Single Scalar Hydration +#### Single Scalar Hydration If you a query which returns just a single scalar value you can use single scalar hydration: - [php] + createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); $query->setParameter(1, 'jwage'); $numArticles = $query->getResult(Query::HYDRATE_SINGLE_SCALAR); You can use the `getSingleScalarResult()` shortcut as well: - [php] + getSingleScalarResult(); -++++ Custom Hydration Modes +#### Custom Hydration Modes You can easily add your own custom hydration modes by first creating a class which extends `AbstractHydrator`: - [php] + getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); Now the hydrator is ready to be used in your queries: - [php] + createQuery('SELECT u FROM CmsUser u'); $results = $query->getResult('CustomHydrator'); -+++ Iterating Large Result Sets +### Iterating Large Result Sets There are situations when a query you want to execute returns a very large result-set that needs to be processed. All the previously described hydration modes completely load a result-set into memory which might not be feasible with large result sets. See the [Batch Processing](batch-processing) section on details how to iterate large result sets. -+++ Functions +### Functions The following methods exist on the `AbstractQuery` which both `Query` and `NativeQuery` extend from. -++++ Parameters +#### Parameters Prepared Statements that use numerical or named wildcards require additional parameters to be executable against the database. To pass parameters to the query the following methods can be used: @@ -776,14 +776,14 @@ against the database. To pass parameters to the query the following methods can * `AbstractQuery::getParameter($param)` * `AbstractQuery::getParameters()` -++++ Cache related API +#### Cache related API You can cache query results based either on all variables that define the result (SQL, Hydration Mode, Parameters and Hints) or on user-defined cache keys. However by default query results are not cached at all. You have to enable the result cache on a per query basis. The following example shows a complete workflow using the Result Cache API: - [php] + createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); $query->setParameter(1, 12); @@ -808,7 +808,7 @@ API: > You can set the Result Cache Driver globally on the `Doctrine\ORM\Configuration` instance > so that it is passed to every `Query` and `NativeQuery` instance. -++++ Query Hints +#### Query Hints You can pass hints to the query parser and hydrators by using the `AbstractQuery::setHint($name, $value)` method. Currently there exist mostly internal query hints that are not be consumed in userland. However the following few hints @@ -825,7 +825,7 @@ entity is discarded in favor of the already existing entity. * Query::HINT_CUSTOM_TREE_WALKERS - An array of additional `Doctrine\ORM\Query\TreeWalker` instances that are attached to the DQL query parsing process. -++++ Query Cache (DQL Query Only) +#### Query Cache (DQL Query Only) Parsing a DQL query and converting it into an SQL query against the underlying database platform obviously has some overhead in contrast to directly executing Native SQL queries. That is why there is a dedicated Query Cache for caching the @@ -843,7 +843,7 @@ of the Query Cache, however if you do there are several methods to interact with * `Query::getQueryCacheDriver()` * `Query::getQueryCacheLifeTime()` -++++ First and Max Result Items (DQL Query Only) +#### First and Max Result Items (DQL Query Only) You can limit the number of results returned from a DQL query as well as specify the starting offset, Doctrine then uses a strategy of manipulating the select query to return only the requested number of results: @@ -857,11 +857,11 @@ then uses a strategy of manipulating the select query to return only the request > case of fetch-joined collections one root entity might appear in many rows, effectively hydrating > less than the specified number of results. -++ EBNF +## EBNF The following context-free grammar, written in an EBNF variant, describes the Doctrine Query Language. You can consult this grammar whenever you are unsure about what is possible with DQL or what the correct syntax for a particular query should be. -+++ Document syntax: +### Document syntax: * non-terminals begin with an upper case character * terminals begin with a lower case character @@ -870,7 +870,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do * curly brackets {...} are used for repetition, e.g. zero or more times * double quotation marks "..." define a terminal string a vertical bar | represents an alternative -+++ Terminals +### Terminals * identifier (name, email, ...) * string ('foo', 'bar''s house', '%ninja%', ...) @@ -879,17 +879,17 @@ The following context-free grammar, written in an EBNF variant, describes the Do * float (-0.23, 0.007, 1.245342E+8, ...) * boolean (false, true) -+++ Query Language +### Query Language QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement -+++ Statements +### Statements SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] UpdateStatement ::= UpdateClause [WhereClause] DeleteStatement ::= DeleteClause [WhereClause] -+++ Identifiers +### Identifiers /* Alias Identification usage (the "u" of "u.name") */ IdentificationVariable ::= identifier @@ -923,7 +923,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ ResultVariable = identifier -+++ Path Expressions +### Path Expressions /* "u.Group" or "u.Phonenumbers" declarations */ JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) @@ -950,7 +950,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField -+++ Clauses +### Clauses SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression @@ -965,7 +965,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] -+++ Items +### Items UpdateItem ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] @@ -973,7 +973,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do NewValue ::= ScalarExpression | SimpleEntityExpression | "NULL" -+++ From, Join and Index by +### From, Join and Index by IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) @@ -984,7 +984,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression -+++ Select Expressions +### Select Expressions SelectExpression ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")" | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable] SimpleSelectExpression ::= ScalarExpression | IdentificationVariable | @@ -993,7 +993,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" -+++ Conditional Expressions +### Conditional Expressions ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* @@ -1004,24 +1004,24 @@ The following context-free grammar, written in an EBNF variant, describes the Do EmptyCollectionComparisonExpression | CollectionMemberExpression -+++ Collection Expressions +### Collection Expressions EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression -+++ Literal Values +### Literal Values Literal ::= string | char | integer | float | boolean InParameter ::= Literal | InputParameter -+++ Input Parameter +### Input Parameter InputParameter ::= PositionalParameter | NamedParameter PositionalParameter ::= "?" integer NamedParameter ::= ":" string -+++ Arithmetic Expressions +### Arithmetic Expressions ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* @@ -1032,7 +1032,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do | FunctionsReturningDatetime | IdentificationVariable | InputParameter -+++ Scalar and Type Expressions +### Scalar and Type Expressions ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression BooleanPrimary | CaseExpression | EntityTypeExpression @@ -1057,12 +1057,12 @@ The following context-free grammar, written in an EBNF variant, describes the Do DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression -+++ Aggregate Expressions +### Aggregate Expressions AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" -+++ Other Expressions +### Other Expressions QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS @@ -1075,7 +1075,7 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" -+++ Functions +### Functions FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime diff --git a/manual/en/events.rst b/manual/en/events.rst new file mode 100644 index 000000000..e899e3121 --- /dev/null +++ b/manual/en/events.rst @@ -0,0 +1,603 @@ +Doctrine 2 features a lightweight event system that is part of the +Common package. + +The Event System +---------------- + +The event system is controlled by the ``EventManager``. It is the +central point of Doctrine's event listener system. Listeners are +registered on the manager and events are dispatched through the +manager. + +:: + + addEventListener(array(self::preFoo, self::postFoo), $this); + } + + public function preFoo(EventArgs $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(EventArgs $e) + { + $this->postFooInvoked = true; + } + } + + // Create a new instance + $test = new EventTest($evm); + +Events can be dispatched by using the ``dispatchEvent()`` method. + +:: + + dispatchEvent(EventTest::preFoo); + $evm->dispatchEvent(EventTest::postFoo); + +You can easily remove a listener with the ``removeEventListener()`` +method. + +:: + + removeEventListener(array(self::preFoo, self::postFoo), $this); + +The Doctrine 2 event system also has a simple concept of event +subscribers. We can define a simple ``TestEventSubscriber`` class +which implements the ``\Doctrine\Common\EventSubscriber`` interface +and implements a ``getSubscribedEvents()`` method which returns an +array of events it should be subscribed to. + +:: + + preFooInvoked = true; + } + + public function getSubscribedEvents() + { + return array(TestEvent::preFoo); + } + } + + $eventSubscriber = new TestEventSubscriber(); + $evm->addEventSubscriber($eventSubscriber); + +Now when you dispatch an event any event subscribers will be +notified for that event. + +:: + + dispatchEvent(TestEvent::preFoo); + +Now you can test the ``$eventSubscriber`` instance to see if the +``preFoo()`` method was invoked. + +:: + + preFooInvoked) { + echo 'pre foo invoked!'; + } + +Naming convention +~~~~~~~~~~~~~~~~~ + +Events being used with the Doctrine 2 EventManager are best named +with camelcase and the value of the corresponding constant should +be the name of the constant itself, even with spelling. This has +several reasons: + + +- It is easy to read. +- Simplicity. +- Each method within an EventSubscriber is named after the + corresponding constant. If constant name and constant value differ, + you MUST use the new value and thus, your code might be subject to + codechanges when the value changes. This contradicts the intention + of a constant. + +An example for a correct notation can be found in the example +``EventTest`` above. + +Lifecycle Events +---------------- + +The EntityManager and UnitOfWork trigger a bunch of events during +the life-time of their registered entities. + + +- preRemove - The preRemove event occurs for a given entity before + the respective EntityManager remove operation for that entity is + executed. +- postRemove - The postRemove event occurs for an entity after the + entity has been deleted. It will be invoked after the database + delete operations. +- prePersist - The prePersist event occurs for a given entity + before the respective EntityManager persist operation for that + entity is executed. +- postPersist - The postPersist event occurs for an entity after + the entity has been made persistent. It will be invoked after the + database insert operations. Generated primary key values are + available in the postPersist event. +- preUpdate - The preUpdate event occurs before the database + update operations to entity data. +- postUpdate - The postUpdate event occurs after the database + update operations to entity data. +- postLoad - The postLoad event occurs for an entity after the + entity has been loaded into the current EntityManager from the + database or after the refresh operation has been applied to it. +- loadClassMetadata - The loadClassMetadata event occurs after the + mapping metadata for a class has been loaded from a mapping source + (annotations/xml/yaml). +- onFlush - The onFlush event occurs after the change-sets of all + managed entities are computed. This event is not a lifecycle + callback. + + **CAUTION** Note that the postLoad event occurs for an entity + before any associations have been initialized. Therefore it is not + safe to access associations in a postLoad callback or event + handler. + + +You can access the Event constants from the ``Events`` class in the +ORM package. + +:: + + createdAt = date('Y-m-d H:m:s'); + } + + /** @PrePersist */ + public function doOtherStuffOnPrePersist() + { + $this->value = 'changed from prePersist callback!'; + } + + /** @PostPersist */ + public function doStuffOnPostPersist() + { + $this->value = 'changed from postPersist callback!'; + } + + /** @PostLoad */ + public function doStuffOnPostLoad() + { + $this->value = 'changed from postLoad callback!'; + } + + /** @PreUpdate */ + public function doStuffOnPreUpdate() + { + $this->value = 'changed from preUpdate callback!'; + } + } + +Note that when using annotations you have to apply the +@HasLifecycleCallbacks marker annotation on the entity class. + +If you want to register lifecycle callbacks from YAML or XML you +can do it with the following. + +:: + + [yml] + User: + type: entity + fields: + # ... + name: + type: string(50) + lifecycleCallbacks: + doStuffOnPrePersist: prePersist + doStuffOnPostPersist: postPersist + +XML would look something like this: + +:: + + [xml] + + + + + + + + + + + + + + + +You just need to make sure a public ``doStuffOnPrePersist()`` and +``doStuffOnPostPersist()`` method is defined on your ``User`` +model. + +:: + + addEventListener(array(Events::preUpdate), MyEventListener()); + $eventManager->addEventSubscriber(new MyEventSubscriber()); + + $entityManager = EntityManager::create($dbOpts, $config, $eventManager); + +You can also retrieve the event manager instance after the +EntityManager was created: + +:: + + getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener()); + $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); + +Implementing Event Listeners +---------------------------- + +This section explains what is and what is not allowed during +specific lifecycle events of the UnitOfWork. Although you get +passed the EntityManager in all of these events, you have to follow +this restrictions very carefully since operations in the wrong +event may produce lots of different errors, such as inconsistent +data and lost updates/persists/removes. + +For the described events that are also lifecycle callback events +the restrictions apply as well, with the additional restriction +that you do not have access to the EntityManager or UnitOfWork APIs +inside these events. + +prePersist +~~~~~~~~~~ + +There are two ways for the ``prePersist`` event to be triggered. +One is obviously when you call ``EntityManager#persist()``. The +event is also called for all cascaded associations. + +There is another way for ``prePersist`` to be called, inside the +``flush()`` method when changes to associations are computed and +this association is marked as cascade persist. Any new entity found +during this operation is also persisted and ``prePersist`` called +on it. This is called "persistence by reachability". + +In both cases you get passed a ``LifecycleEventArgs`` instance +which has access to the entity and the entity manager. + +The following restrictions apply to ``prePersist``: + + +- If you are using a PrePersist Identity Generator such as + sequences the ID value will *NOT* be available within any + PrePersist events. +- Doctrine will not recognize changes made to relations in a pre + persist event called by "reachability" through a cascade persist + unless you use the internal ``UnitOfWork`` API. We do not recommend + such operations in the persistence by reachability context, so do + this at your own risk and possibly supported by unit-tests. + +preRemove +~~~~~~~~~ + +The ``preRemove`` event is called on every entity when its passed +to the ``EntityManager#remove()`` method. It is cascaded for all +associations that are marked as cascade delete. + +There are no restrictions to what methods can be called inside the +``preRemove`` event, except when the remove method itself was +called during a flush operation. + +onFlush +~~~~~~~ + +OnFlush is a very powerful event. It is called inside +``EntityManager#flush()`` after the changes to all the managed +entities and their associations have been computed. This means, the +``onFlush`` event has access to the sets of: + + +- Entities scheduled for insert +- Entities scheduled for update +- Entities scheduled for removal +- Collections scheduled for update +- Collections scheduled for removal + +To make use of the onFlush event you have to be familiar with the +internal UnitOfWork API, which grants you access to the previously +mentioned sets. See this example: + +:: + + getEntityManager(); + $uow = $em->getUnitOfWork(); + + foreach ($uow->getScheduledEntityInsertions() AS $entity) { + + } + + foreach ($uow->getScheduledEntityUpdates() AS $entity) { + + } + + foreach ($uow->getScheduledEntityDeletions() AS $entity) { + + } + + foreach ($uow->getScheduledCollectionDeletions() AS $col) { + + } + + foreach ($uow->getScheduledCollectionUpdates() AS $col) { + + } + } + } + +The following restrictions apply to the onFlush event: + + +- Calling ``EntityManager#persist()`` does not suffice to trigger + a persist on an entity. You have to execute an additional call to + ``$unitOfWork->computeChangeSet($classMetadata, $entity)``. +- Changing primitive fields or associations requires you to + explicitly trigger a re-computation of the changeset of the + affected entity. This can be done by either calling + ``$unitOfWork->computeChangeSet($classMetadata, $entity)`` or + ``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``. + The second method has lower overhead, but only re-computes + primitive fields, never associations. + +preUpdate +~~~~~~~~~ + +PreUpdate is the most restrictive to use event, since it is called +right before an update statement is called for an entity inside the +``EntityManager#flush()`` method. + +Changes to associations of the updated entity are never allowed in +this event, since Doctrine cannot guarantee to correctly handle +referential integrity at this point of the flush operation. This +event has a powerful feature however, it is executed with a +``PreUpdateEventArgs`` instance, which contains a reference to the +computed change-set of this entity. + +This means you have access to all the fields that have changed for +this entity with their old and new value. The following methods are +available on the ``PreUpdateEventArgs``: + + +- ``getEntity()`` to get access to the actual entity. +- ``getEntityChangeSet()`` to get a copy of the changeset array. + Changes to this returned array do not affect updating. +- ``hasChangedField($fieldName)`` to check if the given field name + of the current entity changed. +- ``getOldValue($fieldName)`` and ``getNewValue($fieldName)`` to + access the values of a field. +- ``setNewValue($fieldName, $value)`` to change the value of a + field to be updated. + +A simple example for this event looks like: + +:: + + getEntity() instanceof User) { + if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') { + $eventArgs->setNewValue('name', 'Bob'); + } + } + } + } + +You could also use this listener to implement validation of all the +fields that have changed. This is more efficient than using a +lifecycle callback when there are expensive validations to call: + +:: + + getEntity() instanceof Account) { + if ($eventArgs->hasChangedField('creditCard')) { + $this->validateCreditCard($eventArgs->getNewValue('creditCard')); + } + } + } + + private function validateCreditCard($no) + { + // throw an exception to interrupt flush event. Transaction will be rolled back. + } + } + +Restrictions for this event: + + +- Changes to associations of the passed entities are not + recognized by the flush operation anymore. +- Changes to fields of the passed entities are not recognized by + the flush operation anymore, use the computed change-set passed to + the event to modify primitive field values. +- Any calls to ``EntityManager#persist()`` or + ``EntityManager#remove()``, even in combination with the UnitOfWork + API are strongly discouraged and don't work as expected outside the + flush operation. + +postUpdate, postRemove, postPersist +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The three post events are called inside ``EntityManager#flush()``. +Changes in here are not relevant to the persistence in the +database, but you can use this events to + +postLoad +~~~~~~~~ + +This event is called after an entity is constructed by the +EntityManager. + +Load ClassMetadata Event +------------------------ + +When the mapping information for an entity is read, it is populated +in to a ``ClassMetadataInfo`` instance. You can hook in to this +process and manipulate the instance. + +:: + + getMetadataFactory(); + $evm = $em->getEventManager(); + $evm->addEventListener(Events::loadClassMetadata, $test); + + class EventTest + { + public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) + { + $classMetadata = $eventArgs->getClassMetadata(); + $fieldMapping = array( + 'fieldName' => 'about', + 'type' => 'string', + 'length' => 255 + ); + $classMetadata->mapField($fieldMapping); + } + } + + diff --git a/manual/en/events.txt b/manual/en/events.txt index 3218814d3..a83340f39 100644 --- a/manual/en/events.txt +++ b/manual/en/events.txt @@ -1,18 +1,18 @@ Doctrine 2 features a lightweight event system that is part of the Common package. -++ The Event System +## The Event System The event system is controlled by the `EventManager`. It is the central point of Doctrine's event listener system. Listeners are registered on the manager and events are dispatched through the manager. - [php] + dispatchEvent(EventTest::preFoo); $evm->dispatchEvent(EventTest::postFoo); You can easily remove a listener with the `removeEventListener()` method. - [php] + removeEventListener(array(self::preFoo, self::postFoo), $this); The Doctrine 2 event system also has a simple concept of event subscribers. We @@ -58,7 +58,7 @@ can define a simple `TestEventSubscriber` class which implements the `\Doctrine\Common\EventSubscriber` interface and implements a `getSubscribedEvents()` method which returns an array of events it should be subscribed to. - [php] + dispatchEvent(TestEvent::preFoo); Now you can test the `$eventSubscriber` instance to see if the `preFoo()` method was invoked. - [php] + preFooInvoked) { echo 'pre foo invoked!'; } -+++ Naming convention +### Naming convention Events being used with the Doctrine 2 EventManager are best named with camelcase and the value of the corresponding constant should be the name of the constant itself, even with spelling. This has several reasons: @@ -99,7 +99,7 @@ Events being used with the Doctrine 2 EventManager are best named with camelcase An example for a correct notation can be found in the example `EventTest` above. -++ Lifecycle Events +## Lifecycle Events The EntityManager and UnitOfWork trigger a bunch of events during the life-time of their registered entities. @@ -120,7 +120,7 @@ The EntityManager and UnitOfWork trigger a bunch of events during the life-time You can access the Event constants from the `Events` class in the ORM package. - [php] + operations that can be executed. Please read the *Implementing Event Listeners* section very carefully to understand > which operations are allowed in which lifecycle event. -++ Lifecycle Callbacks +## Lifecycle Callbacks A lifecycle event is a regular event with the additional feature of providing a mechanism to register direct callbacks inside the corresponding entity classes that are executed when the lifecycle event occurs. - [php] + addEventListener(array(Events::preUpdate), MyEventListener()); $eventManager->addEventSubscriber(new MyEventSubscriber()); @@ -264,11 +264,11 @@ To register an event listener you have to hook it into the EventManager that is You can also retrieve the event manager instance after the EntityManager was created: - [php] + getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener()); $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); -++ Implementing Event Listeners +## Implementing Event Listeners This section explains what is and what is not allowed during specific lifecycle events of the UnitOfWork. Although you get passed the EntityManager in all of these events, you have to follow this restrictions very @@ -279,7 +279,7 @@ For the described events that are also lifecycle callback events the restriction apply as well, with the additional restriction that you do not have access to the EntityManager or UnitOfWork APIs inside these events. -+++ prePersist +### prePersist There are two ways for the `prePersist` event to be triggered. One is obviously when you call `EntityManager#persist()`. The event is also called for all @@ -302,7 +302,7 @@ The following restrictions apply to `prePersist`: `UnitOfWork` API. We do not recommend such operations in the persistence by reachability context, so do this at your own risk and possibly supported by unit-tests. -+++ preRemove +### preRemove The `preRemove` event is called on every entity when its passed to the `EntityManager#remove()` method. It is cascaded for all @@ -312,7 +312,7 @@ There are no restrictions to what methods can be called inside the `preRemove` event, except when the remove method itself was called during a flush operation. -+++ onFlush +### onFlush OnFlush is a very powerful event. It is called inside `EntityManager#flush()` after the changes to all the managed entities and their associations have @@ -327,7 +327,7 @@ been computed. This means, the `onFlush` event has access to the sets of: To make use of the onFlush event you have to be familiar with the internal UnitOfWork API, which grants you access to the previously mentioned sets. See this example: - [php] + recomputeSingleEntityChangeSet($classMetadata, $entity)`. The second method has lower overhead, but only re-computes primitive fields, never associations. -+++ preUpdate +### preUpdate PreUpdate is the most restrictive to use event, since it is called right before an update statement is called for an entity inside the `EntityManager#flush()` @@ -389,7 +389,7 @@ with their old and new value. The following methods are available on the `PreUpd A simple example for this event looks like: - [php] + getMetadataFactory(); $evm = $em->getEventManager(); diff --git a/manual/en/improving-performance.rst b/manual/en/improving-performance.rst new file mode 100644 index 000000000..2d9ffb57f --- /dev/null +++ b/manual/en/improving-performance.rst @@ -0,0 +1,38 @@ +Bytecode Cache +-------------- + +It is highly recommended to make use of a bytecode cache like APC. +A bytecode cache removes the need for parsing PHP code on every +request and can greatly improve performance. + + **NOTE** "If you care about performance and don't use a bytecode + cache then you don't really care about performance. Please get one + and start using it." (Stas Malyshev, Core Contributor to PHP and + Zend Employee). + + +Metadata and Query caches +------------------------- + +As already mentioned earlier in the chapter about configuring +Doctrine, it is strongly discouraged to use Doctrine without a +Metadata and Query cache (preferably with APC or Memcache as the +cache driver). Operating Doctrine without these caches means +Doctrine will need to load your mapping information on every single +request and has to parse each DQL query on every single request. +This is a waste of resources. + +Alternative Query Result Formats +-------------------------------- + +Make effective use of the available alternative query result +formats like nested array graphs or pure scalar results, especially +in scenarios where data is loaded for read-only purposes. + +Apply Best Practices +-------------------- + +A lot of the points mentioned in the Best Practices chapter will +also positively affect the performance of Doctrine. + + diff --git a/manual/en/improving-performance.txt b/manual/en/improving-performance.txt index f0f05710e..01509bb81 100644 --- a/manual/en/improving-performance.txt +++ b/manual/en/improving-performance.txt @@ -1,5 +1,5 @@ -++ Bytecode Cache +## Bytecode Cache It is highly recommended to make use of a bytecode cache like APC. A bytecode cache removes the need for parsing PHP code on every request and can greatly improve performance. @@ -9,15 +9,15 @@ It is highly recommended to make use of a bytecode cache like APC. A bytecode ca > to PHP and Zend Employee). -++ Metadata and Query caches +## Metadata and Query caches As already mentioned earlier in the chapter about configuring Doctrine, it is strongly discouraged to use Doctrine without a Metadata and Query cache (preferably with APC or Memcache as the cache driver). Operating Doctrine without these caches means Doctrine will need to load your mapping information on every single request and has to parse each DQL query on every single request. This is a waste of resources. -++ Alternative Query Result Formats +## Alternative Query Result Formats Make effective use of the available alternative query result formats like nested array graphs or pure scalar results, especially in scenarios where data is loaded for read-only purposes. -++ Apply Best Practices +## Apply Best Practices A lot of the points mentioned in the Best Practices chapter will also positively affect the performance of Doctrine. diff --git a/manual/en/inheritance-mapping.rst b/manual/en/inheritance-mapping.rst new file mode 100644 index 000000000..f950577c5 --- /dev/null +++ b/manual/en/inheritance-mapping.rst @@ -0,0 +1,255 @@ +Mapped Superclasses +------------------- + +An mapped superclass is an abstract or concrete class that provides +persistent entity state and mapping information for its subclasses, +but which is not itself an entity. Typically, the purpose of such a +mapped superclass is to define state and mapping information that +is common to multiple entity classes. + +Mapped superclasses, just as regular, non-mapped classes, can +appear in the middle of an otherwise mapped inheritance hierarchy +(through Single Table Inheritance or Class Table Inheritance). + + **NOTE** + + A mapped superclass cannot be an entity, it is not query-able and + persistent relationships defined by a mapped superclass must be + unidirectional. For further support of inheritance, the single or + joined table inheritance features have to be used. + + +Example: + +:: + + `_ +is an inheritance mapping strategy where all classes of a hierarchy +are mapped to a single database table. In order to distinguish +which row represents which type in the hierarchy a so-called +discriminator column is used. + +Example: + +:: + + `_ +is an inheritance mapping strategy where each class in a hierarchy +is mapped to several tables: its own table and the tables of all +parent classes. The table of a child class is linked to the table +of a parent class through a foreign key constraint. Doctrine 2 +implements this strategy through the use of a discriminator column +in the topmost table of the hierarchy because this is the easiest +way to achieve polymorphic queries with Class Table Inheritance. + +Example: + +:: + + makes use of the foreign key property `ON DELETE CASCADE` in all database implementations. A failure to implement this > yourself will lead to dead rows in the database. -+++ Design-time considerations +### Design-time considerations Introducing a new type to the hierarchy, at any level, simply involves interjecting a new table into the schema. Subtypes of that type will automatically join with that new type at runtime. Similarly, modifying any entity type in the hierarchy by adding, modifying or removing fields affects only the immediate table mapped to that type. This mapping strategy provides the greatest flexibility at design time, since changes to any type are always limited to that type's dedicated table. -+++ Performance impact +### Performance impact This strategy inherently requires multiple JOIN operations to perform just about any query which can have a negative impact on performance, especially with large tables and/or large hierarchies. When partial objects are allowed, either globally or on the specific query, then querying for any type will not cause the tables of subtypes to be OUTER JOINed which can increase performance but the resulting partial objects will not fully load themselves on access of any subtype fields, so accessing fields of subtypes after such a query is not safe. There is a general performance consideration with Class Table Inheritance: If you use a CTI entity as a many-to-one or one-to-one entity you should never use one of the classes at the upper levels of the inheritance hierachy as "targetEntity", only those that have no subclasses. Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly. -+++ SQL Schema considerations +### SQL Schema considerations For each entity in the Class-Table Inheritance hierarchy all the mapped fields have to be columns on the table of this entity. Additionally each child table has to have an id column that matches the id column diff --git a/manual/en/introduction.rst b/manual/en/introduction.rst new file mode 100644 index 000000000..f4e540574 --- /dev/null +++ b/manual/en/introduction.rst @@ -0,0 +1,401 @@ +Welcome +------- + +Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that +provides transparent persistence for PHP objects. It sits on top of +a powerful database abstraction layer (DBAL). Object-Relational +Mappers primary task is the transparent translation between (PHP) +objects and relational database rows. + +One of Doctrines key features is the option to write database +queries in a proprietary object oriented SQL dialect called +Doctrine Query Language (DQL), inspired by Hibernates HQL. Besides +DQLs slight differences to SQL it abstracts the mapping between +database rows and objects considerably, allowing developers to +write powerful queries in a simple and flexible fashion. + +Disclaimer +---------- + +This is the Doctrine 2 reference documentation. Introductory guides +and tutorials that you can follow along from start to finish, like +the "Guide to Doctrine" book known from the Doctrine 1.x series, +will be available at a later date. + +Using an Object-Relational Mapper +--------------------------------- + +As the term ORM already hints at, Doctrine 2 aims to simplify the +translation between database rows and the PHP object model. The +primary use case for Doctrine are therefore applications that +utilize the Object-Oriented Programming Paradigm. For applications +that not primarily work with objects Doctrine 2 is not suited very +well. + +Requirements +------------ + +Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved +performance it is also recommended that you use APC with PHP. + +Doctrine 2 Packages +------------------- + +Doctrine 2 is divided into three main packages. + + +- Common +- DBAL (includes Common) +- ORM (includes DBAL+Common) + +This manual mainly covers the ORM package, sometimes touching parts +of the underlying DBAL and Common packages. The Doctrine code base +is split in to these packages for a few reasons and they are to... + + +- ...make things more maintainable and decoupled +- ...allow you to use the code in Doctrine Common without the ORM + or DBAL +- ...allow you to use the DBAL without the ORM + +The Common Package +~~~~~~~~~~~~~~~~~~ + +The Common package contains highly reusable components that have no +dependencies beyond the package itself (and PHP, of course). The +root namespace of the Common package is ``Doctrine\Common``. + +The DBAL Package +~~~~~~~~~~~~~~~~ + +The DBAL package contains an enhanced database abstraction layer on +top of PDO but is not strongly bound to PDO. The purpose of this +layer is to provide a single API that bridges most of the +differences between the different RDBMS vendors. The root namespace +of the DBAL package is ``Doctrine\DBAL``. + +The ORM Package +~~~~~~~~~~~~~~~ + +The ORM package contains the object-relational mapping toolkit that +provides transparent relational persistence for plain PHP objects. +The root namespace of the ORM package is ``Doctrine\ORM``. + +Installing +---------- + +Doctrine can be installed many different ways. We will describe all +the different ways and you can choose which one suits you best. + +PEAR +~~~~ + +You can easily install any of the three Doctrine packages from the +PEAR command line installation utility. + +To install just the ``Common`` package you can run the following +command: + +:: + + $ sudo pear install pear.doctrine-project.org/DoctrineCommon- + +If you want to use the Doctrine Database Abstraction Layer you can +install it with the following command. + +:: + + $ sudo pear install pear.doctrine-project.org/DoctrineDBAL- + +Or, if you want to get the works and go for the ORM you can install +it with the following command. + +:: + + $ sudo pear install pear.doctrine-project.org/DoctrineORM- + + **NOTE** The ```` tag above represents the version you + want to install. For example the current version at the time of + writing this is ``2.0.0BETA3`` for the ORM, so you could install it + like the following: + + :: + + $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.0BETA3 + + +When you have a package installed via PEAR you can require and load +the ``ClassLoader`` with the following code. + +:: + + `_. + +See the configuration section on how to configure and bootstrap a +downloaded version of Doctrine. + +GitHub +~~~~~~ + +Alternatively you can clone the latest version of Doctrine 2 via +GitHub.com: + +:: + + $ git clone git://github.com/doctrine/doctrine2.git doctrine + +This downloads all the sources of the ORM package. You need to +initialize the Github submodules for the Common and DBAL package +dependencies: + +:: + + $ git submodule init + $ git submodule update + +This updates your Git checkout to use the Doctrine and Doctrine +package versions that are recommended for the cloned Master version +of Doctrine 2. + +See the configuration chapter on how to configure a Github +installation of Doctrine with regards to autoloading. + + **NOTE** + + You should not combine the Doctrine-Common, Doctrine-DBAL and + Doctrine-ORM master commits with each other in combination. The ORM + may not work with the current Common or DBAL master versions. + Instead the ORM ships with the Git Submodules that are required. + + +Subversion +~~~~~~~~~~ + + **NOTE** + + Using the SVN Mirror is not recommended. It only allows access to + the latest master commit and does not automatically fetch the + submodules. + + +If you prefer subversion you can also checkout the code from +GitHub.com through the subversion protocol: + +:: + + $ svn co http://svn.github.com/doctrine/doctrine2.git doctrine2 + +However this only allows you to check out the current master of +Doctrine 2, without the Common and DBAL dependencies. You have to +grab them yourself, but might run into version incompatibilities +between the different master branches of Common, DBAL and ORM. + +Sandbox Quickstart +------------------ + + **NOTE** The sandbox is only available via the Doctrine2 Github + Repository or soon as a separate download on the downloads page. + You will find it in the $root/tools/sandbox folder. + + +The sandbox is a pre-configured environment for evaluating and +playing with Doctrine 2. + +Overview +~~~~~~~~ + +After navigating to the sandbox directory, you should see the +following structure: + +:: + + sandbox/ + Entities/ + Address.php + User.php + xml/ + Entities.Address.dcm.xml + Entities.User.dcm.xml + yaml/ + Entities.Address.dcm.yml + Entities.User.dcm.yml + cli-config.php + doctrine + doctrine.php + index.php + +Here is a short overview of the purpose of these folders and +files: + + +- The ``Entities`` folder is where any model classes are created. + Two example entities are already there. +- The ``xml`` folder is where any XML mapping files are created + (if you want to use XML mapping). Two example mapping documents for + the 2 example entities are already there. +- The ``yaml`` folder is where any YAML mapping files are created + (if you want to use YAML mapping). Two example mapping documents + for the 2 example entities are already there. +- The ``cli-config.php`` contains bootstrap code for a + configuration that is used by the Console tool ``doctrine`` + whenever you execute a task. +- ``doctrine``/``doctrine.php`` is a command-line tool. +- ``index.php`` is a basic classical bootstrap file of a php + application that uses Doctrine 2. + +Mini-tutorial +~~~~~~~~~~~~~ + + +1) From within the tools/sandbox folder, run the following command + and you should see the same output. + + $ php doctrine orm:schema-tool:create Creating database schema... + Database schema created successfully! + +2) Take another look into the tools/sandbox folder. A SQLite + database should have been created with the name + ``database.sqlite``. + +3) Open ``index.php`` and at the bottom edit it so it looks like + the following: + + +.. raw:: html + + + + setName('Garfield'); :math:`$em->persist($`user); $em->flush(); + + echo "User saved!"; + + +Open index.php in your browser or execute it on the command line. +You should see the output "User saved!". + + +4) Inspect the SQLite database. Again from within the tools/sandbox + folder, execute the following command: + + $ php doctrine dbal:run-sql "select \* from users" + + +You should get the following output: + +:: + + array(1) { + [0]=> + array(2) { + ["id"]=> + string(1) "1" + ["name"]=> + string(8) "Garfield" + } + } + +You just saved your first entity with a generated ID in an SQLite +database. + + +5) Replace the contents of index.php with the following: + + +.. raw:: html + + + + createQuery('select u from Entities u where u.name = ?1'); + $q->setParameter(1, 'Garfield'); $garfield = + $q->getSingleResult(); + + echo "Hello " . $garfield->getName() . "!"; + + +You just created your first DQL query to retrieve the user with the +name 'Garfield' from an SQLite database (Yes, there is an easier +way to do it, but we wanted to introduce you to DQL at this point. +Can you **find** the easier way?). + + **TIP** When you create new model classes or alter existing ones + you can recreate the database schema with the command + ``doctrine orm:schema-tool --drop`` followed by + ``doctrine orm:schema-tool --create``. + + + +6) Explore Doctrine 2! + +See the following links if you want to start with more complex +tutorials rather than reading the manual: + + +- Doctrine2 Cookbook: + `Getting Started XML Edition `_ + + diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt index 913525c68..4515ead00 100644 --- a/manual/en/introduction.txt +++ b/manual/en/introduction.txt @@ -1,4 +1,4 @@ -++ Welcome +## Welcome Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides transparent persistence for PHP objects. It sits on top of a powerful database @@ -12,25 +12,25 @@ differences to SQL it abstracts the mapping between database rows and objects considerably, allowing developers to write powerful queries in a simple and flexible fashion. -++ Disclaimer +## Disclaimer This is the Doctrine 2 reference documentation. Introductory guides and tutorials that you can follow along from start to finish, like the "Guide to Doctrine" book known from the Doctrine 1.x series, will be available at a later date. -++ Using an Object-Relational Mapper +## Using an Object-Relational Mapper As the term ORM already hints at, Doctrine 2 aims to simplify the translation between database rows and the PHP object model. The primary use case for Doctrine are therefore applications that utilize the Object-Oriented Programming Paradigm. For applications that not primarily work with objects Doctrine 2 is not suited very well. -++ Requirements +## Requirements Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved performance it is also recommended that you use APC with PHP. -++ Doctrine 2 Packages +## Doctrine 2 Packages Doctrine 2 is divided into three main packages. @@ -46,31 +46,31 @@ packages for a few reasons and they are to... * ...allow you to use the code in Doctrine Common without the ORM or DBAL * ...allow you to use the DBAL without the ORM -+++ The Common Package +### The Common Package The Common package contains highly reusable components that have no dependencies beyond the package itself (and PHP, of course). The root namespace of the Common package is `Doctrine\Common`. -+++ The DBAL Package +### The DBAL Package The DBAL package contains an enhanced database abstraction layer on top of PDO but is not strongly bound to PDO. The purpose of this layer is to provide a single API that bridges most of the differences between the different RDBMS vendors. The root namespace of the DBAL package is `Doctrine\DBAL`. -+++ The ORM Package +### The ORM Package The ORM package contains the object-relational mapping toolkit that provides transparent relational persistence for plain PHP objects. The root namespace of the ORM package is `Doctrine\ORM`. -++ Installing +## Installing Doctrine can be installed many different ways. We will describe all the different ways and you can choose which one suits you best. -+++ PEAR +### PEAR You can easily install any of the three Doctrine packages from the PEAR command line installation utility. @@ -99,7 +99,7 @@ following command. When you have a package installed via PEAR you can require and load the `ClassLoader` with the following code. - [php] + with each other in combination. The ORM may not work with the current Common or DBAL master versions. > Instead the ORM ships with the Git Submodules that are required. -+++ Subversion +### Subversion > **NOTE** > @@ -195,7 +195,7 @@ the Common and DBAL dependencies. You have to grab them yourself, but might run into version incompatibilities between the different master branches of Common, DBAL and ORM. -++ Sandbox Quickstart +## Sandbox Quickstart > **NOTE** > The sandbox is only available via the Doctrine2 Github Repository or soon as a separate download on the downloads @@ -204,7 +204,7 @@ and ORM. The sandbox is a pre-configured environment for evaluating and playing with Doctrine 2. -+++ Overview +### Overview After navigating to the sandbox directory, you should see the following structure: @@ -232,7 +232,7 @@ Here is a short overview of the purpose of these folders and files: * `doctrine`/`doctrine.php` is a command-line tool. * `index.php` is a basic classical bootstrap file of a php application that uses Doctrine 2. -+++ Mini-tutorial +### Mini-tutorial 1) From within the tools/sandbox folder, run the following command and you should see the same output. @@ -246,7 +246,7 @@ have been created with the name `database.sqlite`. 3) Open `index.php` and at the bottom edit it so it looks like the following: - [php] + `_. + +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 `_. + +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 `_. + +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 Persister 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 `_ +- `Evaluate possible ways in which stored-procedures can be used `_ +- The previous Filter Rules Feature Request + +Persist Keys 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 `_ + +Mapping many tables to one entity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is not possible to map several equally looking tables onto one +entity. For example if you have a production and an archive table +of a certain business concept then you cannot have both tables map +to the same entity. + +Behaviors +~~~~~~~~~ + +Doctrine 2 *will never* include a behavior system like Doctrine 1 +in the core library. We don't think behaviors add more value than +they cost pain and debugging hell. Please see the many different +blog posts we have written on this topics: + + +- `Doctrine2 "Behaviors" in a Nutshell `_ +- `A re-usable Versionable behavior for Doctrine2 `_ +- `Write your own ORM on top of Doctrine2 `_ + +Doctrine 2 has enough hooks and extension points so that *you* can +add whatever you want on top of it. None of this will ever become +core functionality of Doctrine2 however, you will have to rely on +third party extensions for magical behaviors. + +Nested Set +~~~~~~~~~~ + +NestedSet was offered as a behavior in Doctrine 1 and will not be +included in the core of Doctrine 2. However there are already two +extensions out there that offer support for Nested Set with +Doctrine 2: + + +- `Doctrine2 Hierachical-Structural Behavior `_ +- `Doctrine2 NestedSet `_ + +Known Issues +------------ + +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. + +Identifier Quoting and Legacy Databases +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For compatibility reasons between all the supported vendors and +edge case problems Doctrine 2 does *NOT* do automatic identifier +quoting. This can lead to problems when trying to get +legacy-databases to work with Doctrine 2. + + +- You can quote column-names as described in the + `Basic-Mapping `_ section. +- You cannot quote join column names. +- You cannot use non [a-zA-Z0-9\_]+ characters, they will break + several SQL statements. + +Having problems with these kind of column names? Many databases +support all CRUD operations on views that semantically map to +certain tables. You can create views for all your problematic +tables and column names to avoid the legacy quoting nightmare. + + diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt index 9439ae1b2..d65a8516d 100644 --- a/manual/en/limitations-and-known-issues.txt +++ b/manual/en/limitations-and-known-issues.txt @@ -4,12 +4,12 @@ Much like every other piece of software Doctrine2 is not perfect and far from fe This section should give you an overview of current limitations of Doctrine 2 as well as critical known issues that you should know about. -++ Current Limitations +## 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 +### 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: @@ -45,7 +45,7 @@ Although we state that we support composite primary keys that does not currently 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] + getConfiguration()->setMetadataCacheImpl(new ApcCache()); + + +If you want to use one of the included core metadata drivers you +just need to configure it. All the drivers are in the +``Doctrine\ORM\Mapping\Driver`` namespace: + +:: + + getConfiguration()->setMetadataDriverImpl($driver); + +Implementing Metadata Drivers +----------------------------- + +In addition to the included metadata drivers you can very easily +implement your own. All you need to do is define a class which +implements the ``Driver`` interface: + +:: + + _loadMappingFile($file); + + // populate ClassMetadataInfo instance from $data + } + + /** + * {@inheritdoc} + */ + protected function _loadMappingFile($file) + { + // parse contents of $file and return php data structure + } + } + + **NOTE** When using the ``AbstractFileDriver`` it requires that you + only have one entity defined per file and the file named after the + class described inside where namespace separators are replaced by + periods. So if you have an entity named ``Entities\User`` and you + wanted to write a mapping file for your driver above you would need + to name the file ``Entities.User.dcm.ext`` for it to be + recognized. + + +Now you can use your ``MyMetadataDriver`` implementation by setting +it with the ``setMetadataDriverImpl()`` method: + +:: + + getConfiguration()->setMetadataDriverImpl($driver); + +ClassMetadata +------------- + +The last piece you need to know and understand about metadata in +Doctrine 2 is the API of the ``ClassMetadata`` classes. You need to +be familiar with them in order to implement your own drivers but +more importantly to retrieve mapping information for a certain +entity when needed. + +You have all the methods you need to manually specify the mapping +information instead of using some mapping file to populate it from. +The base ``ClassMetadataInfo`` class is responsible for only data +storage and is not meant for runtime use. It does not require that +the class actually exists yet so it is useful for describing some +entity before it exists and using that information to generate for +example the entities themselves. The class ``ClassMetadata`` +extends ``ClassMetadataInfo`` and adds some functionality required +for runtime usage and requires that the PHP class is present and +can be autoloaded. + +You can read more about the API of the ``ClassMetadata`` classes in +the PHP Mapping chapter. + +Getting ClassMetadata Instances +------------------------------- + +If you want to get the ``ClassMetadata`` instance for an entity in +your project to programatically use some mapping information to +generate some HTML or something similar you can retrieve it through +the ``ClassMetadataFactory``: + +:: + + getMetadataFactory(); + $class = $cmf->getMetadataFor('MyEntityName'); + +Now you can learn about the entity and use the data stored in the +``ClassMetadata`` instance to get all mapped fields for example and +iterate over them: + +:: + + fieldMappings as $fieldMapping) { + echo $fieldMapping['fieldName'] . "\n"; + } + + diff --git a/manual/en/metadata-drivers.txt b/manual/en/metadata-drivers.txt index 9ba308cf5..0d0fe9514 100644 --- a/manual/en/metadata-drivers.txt +++ b/manual/en/metadata-drivers.txt @@ -2,7 +2,7 @@ The heart of an object relational mapper is the mapping information that glues everything together. It instructs the EntityManager how it should behave when dealing with the different entities. -++ Core Metadata Drivers +## Core Metadata Drivers Doctrine provides a few different ways for you to specify your metadata: @@ -23,23 +23,23 @@ information for an entity. > the metadata cache implementation using the `setMetadataCacheImpl()` method on > the `Doctrine\ORM\Configuration` class: > -> [php] +> $em->getConfiguration()->setMetadataCacheImpl(new ApcCache()); If you want to use one of the included core metadata drivers you just need to configure it. All the drivers are in the `Doctrine\ORM\Mapping\Driver` namespace: - [php] + getConfiguration()->setMetadataDriverImpl($driver); -++ Implementing Metadata Drivers +## Implementing Metadata Drivers In addition to the included metadata drivers you can very easily implement your own. All you need to do is define a class which implements the `Driver` interface: - [php] + getConfiguration()->setMetadataDriverImpl($driver); -++ ClassMetadata +## ClassMetadata The last piece you need to know and understand about metadata in Doctrine 2 is the API of the `ClassMetadata` classes. You need to be familiar with them in order @@ -136,20 +136,20 @@ class is present and can be autoloaded. You can read more about the API of the `ClassMetadata` classes in the PHP Mapping chapter. -++ Getting ClassMetadata Instances +## Getting ClassMetadata Instances If you want to get the `ClassMetadata` instance for an entity in your project to programatically use some mapping information to generate some HTML or something similar you can retrieve it through the `ClassMetadataFactory`: - [php] + getMetadataFactory(); $class = $cmf->getMetadataFor('MyEntityName'); Now you can learn about the entity and use the data stored in the `ClassMetadata` instance to get all mapped fields for example and iterate over them: - [php] + fieldMappings as $fieldMapping) { echo $fieldMapping['fieldName'] . "\n"; } \ No newline at end of file diff --git a/manual/en/native-sql.rst b/manual/en/native-sql.rst new file mode 100644 index 000000000..059168713 --- /dev/null +++ b/manual/en/native-sql.rst @@ -0,0 +1,353 @@ +A ``NativeQuery`` lets you execute native SQL, mapping the results +according to your specifications. Such a specification that +describes how an SQL result set is mapped to a Doctrine result is +represented by a ``ResultSetMapping``. It describes how each column +of the database result should be mapped by Doctrine in terms of the +object graph. This allows you to map arbitrary SQL code to objects, +such as highly vendor-optimized SQL or stored-procedures. + +The NativeQuery class +--------------------- + +To create a ``NativeQuery`` you use the method +``EntityManager#createNativeQuery($sql, $resultSetMapping)``. As +you can see in the signature of this method, it expects 2 +ingredients: The SQL you want to execute and the +``ResultSetMapping`` that describes how the results will be +mapped. + +Once you obtained an instance of a ``NativeQuery``, you can bind +parameters to it and finally execute it. + +The ResultSetMapping +-------------------- + +Understanding the ``ResultSetMapping`` is the key to using a +``NativeQuery``. A Doctrine result can contain the following +components: + + +- Entity results. These represent root result elements. +- Joined entity results. These represent joined entities in + associations of root entity results. +- Field results. These represent a column in the result set that + maps to a field of an entity. A field result always belongs to an + entity result or joined entity result. +- Scalar results. These represent scalar values in the result set + that will appear in each result row. Adding scalar results to a + ResultSetMapping can also cause the overall result to become + **mixed** (see DQL - Doctrine Query Language) if the same + ResultSetMapping also contains entity results. +- Meta results. These represent columns that contain + meta-information, such as foreign keys and discriminator columns. + When querying for objects (``getResult()``), all meta columns of + root entities or joined entities must be present in the SQL query + and mapped accordingly using ``ResultSetMapping#addMetaResult``. + + **TIP** It might not surprise you that Doctrine uses + ``ResultSetMapping``s internally when you create DQL queries. As + the query gets parsed and transformed to SQL, Doctrine fills a + ``ResultSetMapping`` that describes how the results should be + processed by the hydration routines. + + +We will now look at each of the result types that can appear in a +ResultSetMapping in detail. + +Entity results +~~~~~~~~~~~~~~ + +An entity result describes an entity type that appears as a root +element in the transformed result. You add an entity result through +``ResultSetMapping#addEntityResult()``. Let's take a look at the +method signature in detail: + +:: + + addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + + $query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +The result would look like this: + +:: + + array( + [0] => User (Object) + ) + +Note that this would be a partial object if the entity has more +fields than just id and name. In the example above the column and +field names are identical but that is not necessary, of course. +Also note that the query string passed to createNativeQuery is +**real native SQL**. Doctrine does not touch this SQL in any way. + +In the previous basic example, a User had no relations and the +table the class is mapped to owns no foreign keys. The next example +assumes User has a unidirectional or bidirectional one-to-one +association to a CmsAddress, where the User is the owning side and +thus owns the foreign key. + +:: + + addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addMetaResult('u', 'address_id', 'address_id'); + + $query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +Foreign keys are used by Doctrine for lazy-loading purposes when +querying for objects. In the previous example, each user object in +the result will have a proxy (a "ghost") in place of the address +that contains the address\_id. When the ghost proxy is accessed, it +loads itself based on this key. + +Consequently, associations that are *fetch-joined* do not require +the foreign keys to be present in the SQL result set, only +associations that are lazy. + +:: + + addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address'); + $rsm->addFieldResult('a', 'address_id', 'id'); + $rsm->addFieldResult('a', 'street', 'street'); + $rsm->addFieldResult('a', 'city', 'city'); + + $sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' . + 'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?'; + $query = $this->_em->createNativeQuery($sql, $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +In this case the nested entity ``Address`` is registered with the +``ResultSetMapping#addJoinedEntityResult`` method, which notifies +Doctrine that this entity is not hydrated at the root level, but as +a joined entity somewhere inside the object graph. In this case we +specify the alias 'u' as third parameter and ``address`` as fourth +parameter, which means the ``Address`` is hydrated into the +``User::$address`` property. + +If a fetched entity is part of a mapped hierarchy that requires a +discriminator column, this column must be present in the result set +as a meta column so that Doctrine can create the appropriate +concrete type. This is shown in the following example where we +assume that there are one or more subclasses that extend User and +either Class Table Inheritance or Single Table Inheritance is used +to map the hierarchy (both use a discriminator column). + +:: + + addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column + $rsm->setDiscriminatorColumn('u', 'discr'); + + $query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +Note that in the case of Class Table Inheritance, an example as +above would result in partial objects if any objects in the result +are actually a subtype of User. When using DQL, Doctrine +automatically includes the necessary joins for this mapping +strategy but with native SQL it is your responsibility. + + diff --git a/manual/en/native-sql.txt b/manual/en/native-sql.txt index 39de3ec68..867bc523c 100644 --- a/manual/en/native-sql.txt +++ b/manual/en/native-sql.txt @@ -4,13 +4,13 @@ represented by a `ResultSetMapping`. It describes how each column of the databas be mapped by Doctrine in terms of the object graph. This allows you to map arbitrary SQL code to objects, such as highly vendor-optimized SQL or stored-procedures. -++ The NativeQuery class +## The NativeQuery class To create a `NativeQuery` you use the method `EntityManager#createNativeQuery($sql, $resultSetMapping)`. As you can see in the signature of this method, it expects 2 ingredients: The SQL you want to execute and the `ResultSetMapping` that describes how the results will be mapped. Once you obtained an instance of a `NativeQuery`, you can bind parameters to it and finally execute it. -++ The ResultSetMapping +## The ResultSetMapping Understanding the `ResultSetMapping` is the key to using a `NativeQuery`. A Doctrine result can contain the following components: @@ -31,12 +31,12 @@ A Doctrine result can contain the following components: We will now look at each of the result types that can appear in a ResultSetMapping in detail. -+++ Entity results +### Entity results An entity result describes an entity type that appears as a root element in the transformed result. You add an entity result through `ResultSetMapping#addEntityResult()`. Let's take a look at the method signature in detail: - [php] + 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. + + diff --git a/manual/en/partial-objects.txt b/manual/en/partial-objects.txt index 68e23dce9..83e89b233 100644 --- a/manual/en/partial-objects.txt +++ b/manual/en/partial-objects.txt @@ -8,7 +8,7 @@ The following section will describe why partial objects are problematic and what > `Query#getArrayResult()`, `Query#getScalarResult()`, `Query#getSingleScalarResult()`, > etc. -++ What is the problem? +## 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 @@ -40,10 +40,10 @@ only selects partial object data and wants to retrieve the result as objects 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] + createQuery("select partial u.{id,name} from MyApp\Domain\User u"); -++ When should I force partial objects? +## 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 diff --git a/manual/en/php-mapping.rst b/manual/en/php-mapping.rst new file mode 100644 index 000000000..4a0326524 --- /dev/null +++ b/manual/en/php-mapping.rst @@ -0,0 +1,248 @@ +Doctrine 2 also allows you to provide the ORM metadata in the form +of plain PHP code using the ``ClassMetadata`` API. You can write +the code in PHP files or inside of a static function named +``loadMetadata($class)`` on the entity class itself. + +PHP Files +--------- + +If you wish to write your mapping information inside PHP files that +are named after the entity and included to populate the metadata +for an entity you can do so by using the ``PHPDriver``: + +:: + + getConfiguration()->setMetadataDriverImpl($driver); + +Now imagine we had an entity named ``Entities\User`` and we wanted +to write a mapping file for it using the above configured +``PHPDriver`` instance: + +:: + + mapField(array( + 'id' => true, + 'fieldName' => 'id', + 'type' => 'integer' + )); + + $metadata->mapField(array( + 'fieldName' => 'username', + 'type' => 'string' + )); + +Now we can easily retrieve the populated ``ClassMetadata`` instance +where the ``PHPDriver`` includes the file and the +``ClassMetadataFactory`` caches it for later retrieval: + +:: + + getMetadataFor('Entities\User'); + +Static Function +--------------- + +In addition to the PHP files you can also specify your mapping +information inside of a static function defined on the entity class +itself. This is useful for cases where you want to keep your entity +and mapping information together but don't want to use annotations. +For this you just need to use the ``StaticPHPDriver``: + +:: + + getConfiguration()->setMetadataDriverImpl($driver); + +Now you just need to define a static function named +``loadMetadata($metadata)`` on your entity: + +:: + + mapField(array( + 'id' => true, + 'fieldName' => 'id', + 'type' => 'integer' + )); + + $metadata->mapField(array( + 'fieldName' => 'username', + 'type' => 'string' + )); + } + } + +ClassMetadataInfo API +--------------------- + +The ``ClassMetadataInfo`` class is the base data object for storing +the mapping metadata for a single entity. It contains all the +getters and setters you need populate and retrieve information for +an entity. + +General Setters +~~~~~~~~~~~~~~~ + + +- ``setTableName($tableName)`` +- ``setPrimaryTable(array $primaryTableDefinition)`` +- ``setCustomRepositoryClass($repositoryClassName)`` +- ``setIdGeneratorType($generatorType)`` +- ``setIdGenerator($generator)`` +- ``setSequenceGeneratorDefinition(array $definition)`` +- ``setChangeTrackingPolicy($policy)`` +- ``setIdentifier(array $identifier)`` + +Inheritance Setters +~~~~~~~~~~~~~~~~~~~ + + +- ``setInheritanceType($type)`` +- ``setSubclasses(array $subclasses)`` +- ``setParentClasses(array $classNames)`` +- ``setDiscriminatorColumn($columnDef)`` +- ``setDiscriminatorMap(array $map)`` + +Field Mapping Setters +~~~~~~~~~~~~~~~~~~~~~ + + +- ``mapField(array $mapping)`` +- ``mapOneToOne(array $mapping)`` +- ``mapOneToMany(array $mapping)`` +- ``mapManyToOne(array $mapping)`` +- ``mapManyToMany(array $mapping)`` + +Lifecycle Callback Setters +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +- ``addLifecycleCallback($callback, $event)`` +- ``setLifecycleCallbacks(array $callbacks)`` + +Versioning Setters +~~~~~~~~~~~~~~~~~~ + + +- ``setVersionMapping(array &$mapping)`` +- ``setVersioned($bool)`` +- ``setVersionField()`` + +General Getters +~~~~~~~~~~~~~~~ + + +- ``getTableName()`` +- ``getTemporaryIdTableName()`` + +Identifier Getters +~~~~~~~~~~~~~~~~~~ + + +- ``getIdentifierColumnNames()`` +- ``usesIdGenerator()`` +- ``isIdentifier($fieldName)`` +- ``isIdGeneratorIdentity()`` +- ``isIdGeneratorSequence()`` +- ``isIdGeneratorTable()`` +- ``isIdentifierNatural()`` +- ``getIdentifierFieldNames()`` +- ``getSingleIdentifierFieldName()`` +- ``getSingleIdentifierColumnName()`` + +Inheritance Getters +~~~~~~~~~~~~~~~~~~~ + + +- ``isInheritanceTypeNone()`` +- ``isInheritanceTypeJoined()`` +- ``isInheritanceTypeSingleTable()`` +- ``isInheritanceTypeTablePerClass()`` +- ``isInheritedField($fieldName)`` +- ``isInheritedAssociation($fieldName)`` + +Change Tracking Getters +~~~~~~~~~~~~~~~~~~~~~~~ + + +- ``isChangeTrackingDeferredExplicit()`` +- ``isChangeTrackingDeferredImplicit()`` +- ``isChangeTrackingNotify()`` + +Field & Association Getters +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +- ``isUniqueField($fieldName)`` +- ``isNullable($fieldName)`` +- ``getColumnName($fieldName)`` +- ``getFieldMapping($fieldName)`` +- ``getAssociationMapping($fieldName)`` +- ``getAssociationMappings()`` +- ``getFieldName($columnName)`` +- ``hasField($fieldName)`` +- ``getColumnNames(array $fieldNames = null)`` +- ``getTypeOfField($fieldName)`` +- ``getTypeOfColumn($columnName)`` +- ``hasAssociation($fieldName)`` +- ``isSingleValuedAssociation($fieldName)`` +- ``isCollectionValuedAssociation($fieldName)`` + +Lifecycle Callback Getters +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +- ``hasLifecycleCallbacks($lifecycleEvent)`` +- ``getLifecycleCallbacks($event)`` + +ClassMetadata API +----------------- + +The ``ClassMetadata`` class extends ``ClassMetadataInfo`` and adds +the runtime functionality required by Doctrine. It adds a few extra +methods related to runtime reflection for working with the entities +themselves. + + +- ``getReflectionClass()`` +- ``getReflectionProperties()`` +- ``getReflectionProperty($name)`` +- ``getSingleIdReflectionProperty()`` +- ``getIdentifierValues($entity)`` +- ``setIdentifierValues($entity, $id)`` +- ``setFieldValue($entity, $field, $value)`` +- ``getFieldValue($entity, $field)`` + + diff --git a/manual/en/php-mapping.txt b/manual/en/php-mapping.txt index 19a4f66a5..05ea41ca3 100644 --- a/manual/en/php-mapping.txt +++ b/manual/en/php-mapping.txt @@ -2,20 +2,20 @@ Doctrine 2 also allows you to provide the ORM metadata in the form of plain PHP code using the `ClassMetadata` API. You can write the code in PHP files or inside of a static function named `loadMetadata($class)` on the entity class itself. -++ PHP Files +## PHP Files If you wish to write your mapping information inside PHP files that are named after the entity and included to populate the metadata for an entity you can do so by using the `PHPDriver`: - [php] + getConfiguration()->setMetadataDriverImpl($driver); Now imagine we had an entity named `Entities\User` and we wanted to write a mapping file for it using the above configured `PHPDriver` instance: - [php] + mapField(array( @@ -44,23 +44,23 @@ To write the mapping information you just need to create a file named Now we can easily retrieve the populated `ClassMetadata` instance where the `PHPDriver` includes the file and the `ClassMetadataFactory` caches it for later retrieval: - [php] + getMetadataFor('Entities\User'); -++ Static Function +## Static Function In addition to the PHP files you can also specify your mapping information inside of a static function defined on the entity class itself. This is useful for cases where you want to keep your entity and mapping information together but don't want to use annotations. For this you just need to use the `StaticPHPDriver`: - [php] + getConfiguration()->setMetadataDriverImpl($driver); Now you just need to define a static function named `loadMetadata($metadata)` on your entity: - [php] + createQueryBuilder(); + +Once you have created an instance of QueryBuilder, it provides a +set of useful informative functions that you can use. One good +example is to inspect what type of object the ``QueryBuilder`` is. + +:: + + getType(); // Prints: 0 + +There're currently 3 possible return values for ``getType()``: + + +- ``QueryBuilder::SELECT``, which returns value 0 +- ``QueryBuilder::DELETE``, returning value 1 +- ``QueryBuilder::UPDATE``, which returns value 2 + +It is possible to retrieve the associated ``EntityManager`` of the +current ``QueryBuilder``, its DQL and also a ``Query`` object when +you finish building your DQL. + +:: + + getEntityManager(); + + // example4: retrieve the DQL string of what was defined in QueryBuilder + $dql = $qb->getDql(); + + // example5: retrieve the associated Query object with the processed DQL + $q = $qb->getQuery(); + +Internally, ``QueryBuilder`` works with a DQL cache to increase +performance. Any changes that may affect the generated DQL actually +modifies the state of ``QueryBuilder`` to a stage we call +STATE\_DIRTY. One ``QueryBuilder`` can be in two different states: + + +- ``QueryBuilder::STATE_CLEAN``, which means DQL haven't been + altered since last retrieval or nothing were added since its + instantiation +- ``QueryBuilder::STATE_DIRTY``, means DQL query must (and will) + be processed on next retrieval + +Working with QueryBuilder +~~~~~~~~~~~~~~~~~~~~~~~~~ + +All helper methods in ``QueryBuilder`` actually rely on a single +one: ``add()``. This method is responsible of building every piece +of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and +``$append`` (default=false) + + +- ``$dqlPartName``: Where the ``$dqlPart`` should be placed. + Possible values: select, from, where, groupBy, having, orderBy +- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts + a string or any instance of ``Doctrine\ORM\Query\Expr\*`` +- ``$append``: Optional flag (default=false) if the ``$dqlPart`` + should override all previously defined items in ``$dqlPartName`` or + not + +- + +:: + + add('select', 'u') + ->add('from', 'User u') + ->add('where', 'u.id = ?1') + ->add('orderBy', 'u.name ASC'); + +Binding parameters to your query +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Doctrine supports dynamic binding of parameters to your query, +similar to preparing queries. You can use both strings and numbers +as placeholders, although both have a slightly different syntax. +Additionally, you must make your choice: Mixing both styles is not +allowed. Binding parameters can simply be achieved as follows: + +:: + + add('select', 'u') + ->add('from', 'User u') + ->add('where', 'u.id = ?1') + ->add('orderBy', 'u.name ASC'); + ->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100 + +You are not forced to enumerate your placeholders as the +alternative syntax is available: + +:: + + add('select', 'u') + ->add('from', 'User u') + ->add('where', 'u.id = :identifier') + ->add('orderBy', 'u.name ASC'); + ->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100 + +Note that numeric placeholders start with a ? followed by a number +while the named placeholders start with a : followed by a string. + +If you've got several parameters to bind to your query, you can +also use setParameters() instead of setParameter() with the +following syntax: + +:: + + setParameters(array(1 => 'value for ?1', 2 => 'value for ?2')); + +Getting already bound parameters is easy - simply use the above +mentioned syntax with "getParameter()" or "getParameters()": + +:: + + getParameters(array(1, 2)); + // Equivalent to + $param = array($qb->getParameter(1), $qb->getParameter(2)); + +Note: If you try to get a parameter that was not bound yet, +getParameter() simply returns NULL. + +Expr\* classes +^^^^^^^^^^^^^^ + +When you call ``add()`` with string, it internally evaluates to an +instance of ``Doctrine\ORM\Query\Expr\Expr\*`` class. Here is the +same query of example 6 written using +``Doctrine\ORM\Query\Expr\Expr\*`` classes: + +:: + + add('select', new Expr\Select(array('u'))) + ->add('from', new Expr\From('User', 'u')) + ->add('where', new Expr\Comparison('u.id', '=', '?1')) + ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); + +Of course this is the hardest way to build a DQL query in Doctrine. +To simplify some of these efforts, we introduce what we call as +``Expr`` helper class. + +The Expr class +^^^^^^^^^^^^^^ + +To workaround most of the issues that ``add()`` method may cause, +Doctrine created a class that can be considered as a helper for +building queries. This class is called ``Expr``, which provides a +set of useful static methods to help building queries: + +:: + + add('select', $qb->expr()->select('u')) + ->add('from', $qb->expr()->from('User', 'u')) + ->add('where', $qb->expr()->orx( + $qb->expr()->eq('u.id', '?1'), + $qb->expr()->like('u.nickname', '?2') + )) + ->add('orderBy', $qb->expr()->orderBy('u.surname', 'ASC')); + +Although it still sounds complex, the ability to programmatically +create conditions are the main feature of ``Expr``. Here it is a +complete list of supported helper methods available: + +:: + + expr()->select('u') + public function select($select = null); // Returns Expr\Select instance + + // Example - $qb->expr()->from('User', 'u') + public function from($from, $alias); // Returns Expr\From instance + + // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', Expr\Join::ON, 'p.user_id = u.id AND p.country_code = 55'); + // Example - $qb->expr()->leftJoin('u. Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); + public function leftJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance + + // Example - $qb->expr()->innerJoin('u.Group', 'g', Expr\Join::WITH, 'g.manager_level = 100'); + // Example - $qb->expr()->innerJoin('u.Group', 'g', 'WITH', $qb->expr()->eq('g.manager_level', '100')); + public function innerJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance + + // Example - $qb->expr()->orderBy('u.surname', 'ASC')->add('u.firstname', 'ASC')->... + public function orderBy($sort = null, $order = null); // Returns Expr\OrderBy instance + + // Example - $qb->expr()->groupBy()->add('u.id')->... + public function groupBy($groupBy = null); // Returns Expr\GroupBy instance + + + /** Conditional objects **/ + + // Example - $qb->expr()->andx($cond1 [, $condN])->add(...)->... + public function andx($x = null); // Returns Expr\Andx instance + + // Example - $qb->expr()->orx($cond1 [, $condN])->add(...)->... + public function orx($x = null); // Returns Expr\Orx instance + + + /** Comparison objects **/ + + // Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1 + public function eq($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1 + public function neq($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1 + public function lt($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1 + public function lte($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1 + public function gt($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1 + public function gte($x, $y); // Returns Expr\Comparison instance + + + /** Arithmetic objects **/ + + // Example - $qb->expr()->prod('u.id', '2') => u.id * 2 + public function prod($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->diff('u.id', '2') => u.id - 2 + public function diff($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->sum('u.id', '2') => u.id + 2 + public function sum($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->quot('u.id', '2') => u.id / 2 + public function quot($x, $y); // Returns Expr\Math instance + + + /** Pseudo-function objects **/ + + // Example - $qb->expr()->exists($qb2->getDql()) + public function exists($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->all($qb2->getDql()) + public function all($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->some($qb2->getDql()) + public function some($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->any($qb2->getDql()) + public function any($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1')) + public function not($restriction); // Returns Expr\Func instance + + // Example - $qb->expr()->in('u.id', array(1, 2, 3)) + // Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception. + // Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above) + public function in($x, $y); // Returns Expr\Func instance + + // Example - $qb->expr()->notIn('u.id', '2') + public function notIn($x, $y); // Returns Expr\Func instance + + // Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%')) + public function like($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->between('u.id', '1', '10') + public function between($val, $x, $y); // Returns Expr\Func + + + /** Function objects **/ + + // Example - $qb->expr()->trim('u.firstname') + public function trim($x); // Returns Expr\Func + + // Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat(' ', 'u.lastname')) + public function concat($x, $y); // Returns Expr\Func + + // Example - $qb->expr()->substr('u.firstname', 0, 1) + public function substr($x, $from, $len); // Returns Expr\Func + + // Example - $qb->expr()->lower('u.firstname') + public function lower($x); // Returns Expr\Func + + // Example - $qb->expr()->upper('u.firstname') + public function upper($x); // Returns Expr\Func + + // Example - $qb->expr()->length('u.firstname') + public function length($x); // Returns Expr\Func + + // Example - $qb->expr()->avg('u.age') + public function avg($x); // Returns Expr\Func + + // Example - $qb->expr()->max('u.age') + public function max($x); // Returns Expr\Func + + // Example - $qb->expr()->min('u.age') + public function min($x); // Returns Expr\Func + + // Example - $qb->expr()->abs('u.currentBalance') + public function abs($x); // Returns Expr\Func + + // Example - $qb->expr()->sqrt('u.currentBalance') + public function sqrt($x); // Returns Expr\Func + + // Example - $qb->expr()->count('u.firstname') + public function count($x); // Returns Expr\Func + + // Example - $qb->expr()->countDistinct('u.surname') + public function countDistinct($x); // Returns Expr\Func + } + +Helper methods +^^^^^^^^^^^^^^ + +Until now we have described the lowest level (thought of as the +hardcore method) of creating queries. It may be useful to work at +this level for optimization purposes, but most of the time it is +preferred to work at a higher level of abstraction. To simplify +even more the way you build a query in Doctrine, we can take +advantage of what we call Helper methods. For all base code, there +is a set of useful methods to simplify a programmer's life. To +illustrate how to work with them, here is the same example 6 +re-written using ``QueryBuilder`` helper methods: + +:: + + select('u') + ->from('User', 'u') + ->where('u.id = ?1') + ->orderBy('u.name ASC'); + +``QueryBuilder`` helper methods are considered the standard way to +build DQL queries. Although it is supported, it should be avoided +to use string based queries and greatly encouraged to use +``$qb->expr()->*`` methods. Here is a converted example 8 to +suggested standard way to build queries: + +:: + + select(array('u')) // string 'u' is converted to array internally + ->from('User', 'u') + ->where($qb->expr()->orx( + $qb->expr()->eq('u.id', '?1'), + $qb->expr()->like('u.nickname', '?2') + )) + ->orderBy('u.surname', 'ASC')); + +Here is a complete list of helper methods available in +``QueryBuilder``: + +:: + + select('u') + // Example - $qb->select(array('u', 'p')) + // Example - $qb->select($qb->expr()->select('u', 'p')) + public function select($select = null); + + // Example - $qb->delete('User', 'u') + public function delete($delete = null, $alias = null); + + // Example - $qb->update('Group', 'g') + public function update($update = null, $alias = null); + + // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold')) + // Example - $qb->set('u.numChilds', 'u.numChilds + ?1') + // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1')) + public function set($key, $value); + + // Example - $qb->from('Phonenumber', 'p') + public function from($from, $alias = null); + + // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::ON, $qb->expr()->and($qb->expr()->eq('u.group_id', 'g.id'), 'g.name = ?1')) + // Example - $qb->innerJoin('u.Group', 'g', 'ON', 'u.group_id = g.id AND g.name = ?1') + public function innerJoin($join, $alias = null, $conditionType = null, $condition = null); + + // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55)) + // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55') + public function leftJoin($join, $alias = null, $conditionType = null, $condition = null); + + // NOTE: ->where() overrides all previously set conditions + // + // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2')) + // Example - $qb->where($qb->expr()->andx($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2'))) + // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2') + public function where($where); + + // Example - $qb->andWhere($qb->expr()->orx($qb->expr()->lte('u.age', 40), 'u.numChild = 0')) + public function andWhere($where); + + // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10)); + public function orWhere($where); + + // NOTE: -> groupBy() overrides all previously set grouping conditions + // + // Example - $qb->groupBy('u.id') + public function groupBy($groupBy); + + // Example - $qb->addGroupBy('g.name') + public function addGroupBy($groupBy); + + // NOTE: -> having() overrides all previously set having conditions + // + // Example - $qb->having('u.salary >= ?1') + // Example - $qb->having($qb->expr()->gte('u.salary', '?1')) + public function having($having); + + // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0)) + public function andHaving($having); + + // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100')) + public function orHaving($having); + + // NOTE: -> orderBy() overrides all previously set ordering conditions + // + // Example - $qb->orderBy('u.surname', 'DESC') + public function orderBy($sort, $order = null); + + // Example - $qb->addOrderBy('u.firstName') + public function addOrderBy($sort, $order = null); // Default $order = 'ASC' + } + + diff --git a/manual/en/query-builder.txt b/manual/en/query-builder.txt index 0ad69b3e3..5f15bed7a 100644 --- a/manual/en/query-builder.txt +++ b/manual/en/query-builder.txt @@ -1,17 +1,17 @@ -++ The QueryBuilder +## The QueryBuilder A `QueryBuilder` provides an API that is designed for conditionally constructing a DQL query in several steps. It provides a set of classes and methods that is able to programmatically build queries, and also provides a fluent API. This means that you can change between one methodology to the other as you want, and also pick one if you prefer. -+++ Constructing a new QueryBuilder object +### Constructing a new QueryBuilder object The same way you build a normal Query, you build a `QueryBuilder` object, just providing the correct method name. Here is an example how to build a `QueryBuilder` object: - [php] + add('where', 'u.id = ?1') ->add('orderBy', 'u.name ASC'); -++++ Binding parameters to your query +#### Binding parameters to your query Doctrine supports dynamic binding of parameters to your query, similar to preparing queries. You can use both strings and numbers as placeholders, although both have a slightly different syntax. Additionally, you must make your choice: Mixing both styles is not allowed. Binding parameters can simply be achieved as follows: - [php] + expr()->*` methods. Here is a converted example 8 to suggested standard way to build queries: - [php] + select('u') diff --git a/manual/en/tools.rst b/manual/en/tools.rst new file mode 100644 index 000000000..79941ea1d --- /dev/null +++ b/manual/en/tools.rst @@ -0,0 +1,376 @@ +The Doctrine Console +-------------------- + +The Doctrine Console is a Command Line Interface tool for +simplifying common tasks during the development of a project that +uses Doctrine 2. + +Installation +~~~~~~~~~~~~ + +If you installed Doctrine 2 through PEAR, the ``doctrine`` command +line tool should already be available to you. + +If you use Doctrine through SVN or a release package you need to +copy the ``doctrine`` and ``doctrine.php`` files from the +``tools/sandbox`` or ``bin`` folder, respectively, to a location of +your choice, for example a ``tools`` folder of your project. You +probably need to edit ``doctrine.php`` to adjust some paths to the +new environment, most importantly the first line that includes the +``Doctrine\Common\ClassLoader``. + +Getting Help +~~~~~~~~~~~~ + +Type ``doctrine`` on the command line and you should see an +overview of the available commands or use the --help flag to get +information on the available commands. If you want to know more +about the use of generate entities for example, you can call: + +:: + + doctrine orm:generate-entities --help + +Configuration +~~~~~~~~~~~~~ + +Whenever the ``doctrine`` command line tool is invoked, it can +access alls Commands that were registered by developer. There is no +auto-detection mechanism at work. The ``bin\doctrine.php`` file +already registers all the commands that currently ship with +Doctrine DBAL and ORM. If you want to use additional commands you +have to register them yourself. + +All the commands of the Doctrine Console require either the ``db`` +or the ``em`` helpers to be defined in order to work correctly. +Doctrine Console requires the definition of a HelperSet that is the +DI tool to be injected in the Console. In case of a project that is +dealing exclusively with DBAL, the ConnectionHelper is required: + +:: + + new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn) + )); + $cli->setHelperSet($helperSet); + +When dealing with the ORM package, the EntityManagerHelper is +required: + +:: + + new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) + )); + $cli->setHelperSet($helperSet); + +The HelperSet instance has to be generated in a separate file (i.e. +``cli-config.php``) that contains typical Doctrine bootstrap code +and predefines the needed HelperSet attributes mentioned above. A +typical ``cli-config.php`` file looks as follows: + +:: + + register(); + + $classLoader = new \Doctrine\Common\ClassLoader('Proxies', __DIR__); + $classLoader->register(); + + $config = new \Doctrine\ORM\Configuration(); + $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); + $config->setProxyDir(__DIR__ . '/Proxies'); + $config->setProxyNamespace('Proxies'); + + $connectionOptions = array( + 'driver' => 'pdo_sqlite', + 'path' => 'database.sqlite' + ); + + $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); + + $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( + 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), + 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) + )); + +It is important to define a correct HelperSet that doctrine.php +script will ultimately use. The Doctrine Binary will automatically +find the first instance of HelperSet in the global variable +namespace and use this. + +You can also add your own commands on-top of the Doctrine supported +tools. To include a new command on Doctrine Console, you need to +do: + +:: + + addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand()); + +Additionally, include multiple commands (and overriding previously +defined ones) is possible through the command: + +:: + + addCommands(array( + new \MyProject\Tools\Console\Commands\MyCustomCommand(), + new \MyProject\Tools\Console\Commands\SomethingCommand(), + new \MyProject\Tools\Console\Commands\AnotherCommand(), + new \MyProject\Tools\Console\Commands\OneMoreCommand(), + )); + +Command Overview +~~~~~~~~~~~~~~~~ + +The following Commands are currently available: + + +- ``help`` Displays help for a command (?) +- ``list`` Lists commands +- ``dbal:import`` Import SQL file(s) directly to Database. +- ``dbal:run-sql`` Executes arbitrary SQL directly from the + command line. +- ``orm:clear-cache:metadata`` Clear all metadata cache of the + various cache drivers. +- ``orm:clear-cache:query`` Clear all query cache of the various + cache drivers. +- ``orm:clear-cache:result`` Clear result cache of the various + cache drivers. +- ``orm:convert-d1-schema`` Converts Doctrine 1.X schema into a + Doctrine 2.X schema. +- ``orm:convert-mapping`` Convert mapping information between + supported formats. +- ``orm:ensure-production-settings`` Verify that Doctrine is + properly configured for a production environment. +- ``orm:generate-entities`` Generate entity classes and method + stubs from your mapping information. +- ``orm:generate-proxies`` Generates proxy classes for entity + classes. +- ``orm:generate-repositories`` Generate repository classes from + your mapping information. +- ``orm:run-dql`` Executes arbitrary DQL directly from the command + line. +- ``orm:schema-tool:create`` Processes the schema and either + create it directly on EntityManager Storage Connection or generate + the SQL output. +- ``orm:schema-tool:drop`` Processes the schema and either drop + the database schema of EntityManager Storage Connection or generate + the SQL output. +- ``orm:schema-tool:update`` Processes the schema and either + update the database schema of EntityManager Storage Connection or + generate the SQL output. + +Database Schema Generation +-------------------------- + + **Note** + + SchemaTool can do harm to your database. It will drop or alter + tables, indexes, sequences and such. Please use this tool with + caution in development and not on a production server. It is meant + for helping you develop your Database Schema, but NOT with + migrating schema from A to B in production. A safe approach would + be generating the SQL on development server and saving it into SQL + Migration files that are executed manually on the production + server. + + SchemaTool assumes your Doctrine Project uses the given database on + its own. Update and Drop commands will mess with other tables if + they are not related to the current project that is using Doctrine. + Please be careful! + + +To generate your database schema from your Doctrine mapping files +you can use the ``SchemaTool`` class or the ``schema-tool`` Console +Command. + +When using the SchemaTool class directly, create your schema using +the ``createSchema()`` method. First create an instance of the +``SchemaTool`` and pass it an instance of the ``EntityManager`` +that you want to use to create the schema. This method receives an +array of ``ClassMetadataInfo`` instances. + +:: + + getClassMetadata('Entities\User'), + $em->getClassMetadata('Entities\Profile') + ); + $tool->createSchema($classes); + +To drop the schema you can use the ``dropSchema()`` method. + +:: + + dropSchema($classes); + +This drops all the tables that are currently used by your metadata +model. When you are changing your metadata a lot during development +you might want to drop the complete database instead of only the +tables of the current model to clean up with orphaned tables. + +:: + + dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE); + +You can also use database introspection to update your schema +easily with the ``updateSchema()`` method. It will compare your +existing database schema to the passed array of +``ClassMetdataInfo`` instances. + +:: + + updateSchema($classes); + +If you want to use this functionality from the command line you can +use the ``schema-tool`` command. + +To create the schema use the ``create`` command: + +:: + + $ php doctrine orm:schema-tool:create + +To drop the schema use the ``drop`` command: + +:: + + $ php doctrine orm:schema-tool:drop + +If you want to drop and then recreate the schema then use both +options: + +:: + + $ php doctrine orm:schema-tool:drop + $ php doctrine orm:schema-tool:create + +As you would think, if you want to update your schema use the +``update`` command: + +:: + + $ php doctrine orm:schema-tool:update + +All of the above commands also accept a ``--dump-sql`` option that +will output the SQL for the ran operation. + +:: + + $ php doctrine orm:schema-tool:create --dump-sql + +Before using the orm:schema-tool commands, remember to configure +your cli-config.php properly. + + **NOTE** + + When using the Annotation Mapping Driver you have to either setup + your autoloader in the cli-config.php correctly to find all the + entities, or you can use the second argument of the + ``EntityManagerHelper`` to specify all the paths of your entities + (or mapping files), i.e. + ``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);`` + + +Convert Mapping Information +--------------------------- + +To convert some mapping information between the various supported +formats you can use the ``ClassMetadataExporter`` to get exporter +instances for the different formats: + +:: + + getExporter('yml', '/path/to/export/yml'); + +Now you can export some ``ClassMetadata`` instances: + +:: + + getClassMetadata('Entities\User'), + $em->getClassMetadata('Entities\Profile') + ); + $exporter->setMetadata($classes); + $exporter->export(); + +This functionality is also available from the command line to +convert your loaded mapping information to another format. The +``orm:convert-mapping`` command accepts two arguments, the type to +convert to and the path to generate it: + +:: + + $ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml + +Reverse Engineering +------------------- + +You can use the ``DatabaseDriver`` to reverse engineer a database +to an array of ``ClassMetadataInfo`` instances and generate YAML, +XML, etc. from them. + +First you need to retrieve the metadata instances with the +``DatabaseDriver``: + +:: + + getConfiguration()->setMetadataDriverImpl( + new \Doctrine\ORM\Mapping\Driver\DatabaseDriver( + $em->getConnection()->getSchemaManager() + ) + ); + + $cmf = new DisconnectedClassMetadataFactory($em); + $metadata = $cmf->getAllMetadata(); + +Now you can get an exporter instance and export the loaded metadata +to yml: + +:: + + getExporter('yml', '/path/to/export/yml'); + $exporter->setMetadata($metadata); + $exporter->export(); + +You can also reverse engineer a database using the +``orm:convert-mapping`` command: + +:: + + $ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml + + **CAUTION** Reverse Engineering is not always working perfectly + depending on special cases. It will only detect Many-To-One + relations (even if they are One-To-One) and will try to create + entities from Many-To-Many tables. It also has problems with naming + of foreign keys that have multiple column names. Any Reverse + Engineered Database-Schema needs considerable manual work to become + a useful domain model. + + + diff --git a/manual/en/tools.txt b/manual/en/tools.txt index d461bafcf..221ce89ce 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -1,21 +1,21 @@ -++ The Doctrine Console +## The Doctrine Console The Doctrine Console is a Command Line Interface tool for simplifying common tasks during the development of a project that uses Doctrine 2. -+++ Installation +### Installation If you installed Doctrine 2 through PEAR, the `doctrine` command line tool should already be available to you. If you use Doctrine through SVN or a release package you need to copy the `doctrine` and `doctrine.php` files from the `tools/sandbox` or `bin` folder, respectively, to a location of your choice, for example a `tools` folder of your project. You probably need to edit `doctrine.php` to adjust some paths to the new environment, most importantly the first line that includes the `Doctrine\Common\ClassLoader`. -+++ Getting Help +### Getting Help Type `doctrine` on the command line and you should see an overview of the available commands or use the --help flag to get information on the available commands. If you want to know more about the use of generate entities for example, you can call: doctrine orm:generate-entities --help -+++ Configuration +### Configuration Whenever the `doctrine` command line tool is invoked, it can access alls Commands that were registered by developer. There is no auto-detection mechanism at work. The `bin\doctrine.php` file already registers all the commands that @@ -24,7 +24,7 @@ currently ship with Doctrine DBAL and ORM. If you want to use additional command All the commands of the Doctrine Console require either the `db` or the `em` helpers to be defined in order to work correctly. Doctrine Console requires the definition of a HelperSet that is the DI tool to be injected in the Console. In case of a project that is dealing exclusively with DBAL, the ConnectionHelper is required: - [php] + new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn) )); @@ -32,7 +32,7 @@ In case of a project that is dealing exclusively with DBAL, the ConnectionHelper When dealing with the ORM package, the EntityManagerHelper is required: - [php] + new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); @@ -41,7 +41,7 @@ When dealing with the ORM package, the EntityManagerHelper is required: The HelperSet instance has to be generated in a separate file (i.e. `cli-config.php`) that contains typical Doctrine bootstrap code and predefines the needed HelperSet attributes mentioned above. A typical `cli-config.php` file looks as follows: - [php] + addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand()); Additionally, include multiple commands (and overriding previously defined ones) is possible through the command: - [php] + addCommands(array( new \MyProject\Tools\Console\Commands\MyCustomCommand(), new \MyProject\Tools\Console\Commands\SomethingCommand(), @@ -86,7 +86,7 @@ Additionally, include multiple commands (and overriding previously defined ones) new \MyProject\Tools\Console\Commands\OneMoreCommand(), )); -+++ Command Overview +### Command Overview The following Commands are currently available: @@ -108,7 +108,7 @@ The following Commands are currently available: * `orm:schema-tool:drop` Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output. * `orm:schema-tool:update` Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output. -++ Database Schema Generation +## Database Schema Generation > **Note** > @@ -126,7 +126,7 @@ To generate your database schema from your Doctrine mapping files you can use th When using the SchemaTool class directly, create your schema using the `createSchema()` method. First create an instance of the `SchemaTool` and pass it an instance of the `EntityManager` that you want to use to create the schema. This method receives an array of `ClassMetadataInfo` instances. - [php] + getClassMetadata('Entities\User'), @@ -136,7 +136,7 @@ When using the SchemaTool class directly, create your schema using the `createSc To drop the schema you can use the `dropSchema()` method. - [php] + dropSchema($classes); This drops all the tables that are currently used by your metadata model. @@ -144,14 +144,14 @@ When you are changing your metadata a lot during development you might want to drop the complete database instead of only the tables of the current model to clean up with orphaned tables. - [php] + dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE); You can also use database introspection to update your schema easily with the `updateSchema()` method. It will compare your existing database schema to the passed array of `ClassMetdataInfo` instances. - [php] + updateSchema($classes); If you want to use this functionality from the command line you can use the @@ -188,23 +188,23 @@ Before using the orm:schema-tool commands, remember to configure your cli-config > specify all the paths of your entities (or mapping files), i.e. > `new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);` -++ Convert Mapping Information +## Convert Mapping Information To convert some mapping information between the various supported formats you can use the `ClassMetadataExporter` to get exporter instances for the different formats: - [php] + getExporter('yml', '/path/to/export/yml'); Now you can export some `ClassMetadata` instances: - [php] + getClassMetadata('Entities\User'), $em->getClassMetadata('Entities\Profile') @@ -218,14 +218,14 @@ accepts two arguments, the type to convert to and the path to generate it: $ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml -++ Reverse Engineering +## Reverse Engineering You can use the `DatabaseDriver` to reverse engineer a database to an array of `ClassMetadataInfo` instances and generate YAML, XML, etc. from them. First you need to retrieve the metadata instances with the `DatabaseDriver`: - [php] + getConfiguration()->setMetadataDriverImpl( new \Doctrine\ORM\Mapping\Driver\DatabaseDriver( $em->getConnection()->getSchemaManager() @@ -237,7 +237,7 @@ First you need to retrieve the metadata instances with the `DatabaseDriver`: Now you can get an exporter instance and export the loaded metadata to yml: - [php] + getExporter('yml', '/path/to/export/yml'); $exporter->setMetadata($metadata); $exporter->export(); diff --git a/manual/en/transactions-and-concurrency.rst b/manual/en/transactions-and-concurrency.rst new file mode 100644 index 000000000..19692f1ac --- /dev/null +++ b/manual/en/transactions-and-concurrency.rst @@ -0,0 +1,351 @@ +Transaction Demarcation +----------------------- + +Transaction demarcation is the task of defining your transaction +boundaries. Proper transaction demarcation is very important +because if not done properly it can negatively affect the +performance of your application. Many databases and database +abstraction layers like PDO by default operate in auto-commit mode, +which means that every single SQL statement is wrapped in a small +transaction. Without any explicit transaction demarcation from your +side, this quickly results in poor performance because transactions +are not cheap. + +For the most part, Doctrine 2 already takes care of proper +transaction demarcation for you: All the write operations +(INSERT/UPDATE/DELETE) are queued until ``EntityManager#flush()`` +is invoked which wraps all of these changes in a single +transaction. + +However, Doctrine 2 also allows (and encourages) you to take over +and control transaction demarcation yourself. + +These are two ways to deal with transactions when using the +Doctrine ORM and are now described in more detail. + +Approach 1: Implicitly +~~~~~~~~~~~~~~~~~~~~~~ + +The first approach is to use the implicit transaction handling +provided by the Doctrine ORM EntityManager. Given the following +code snippet, without any explicit transaction demarcation: + +:: + + setName('George'); + $em->persist($user); + $em->flush(); + +Since we do not do any custom transaction demarcation in the above +code, ``EntityManager#flush()`` will begin and commit/rollback a +transaction. This behavior is made possible by the aggregation of +the DML operations by the Doctrine ORM and is sufficient if all the +data manipulation that is part of a unit of work happens through +the domain model and thus the ORM. + +Approach 2: Explicitly +~~~~~~~~~~~~~~~~~~~~~~ + +The explicit alternative is to use the ``Doctrine\DBAL\Connection`` +API directly to control the transaction boundaries. The code then +looks like this: + +:: + + getConnection()->beginTransaction(); // suspend auto-commit + try { + //... do some work + $user = new User; + $user->setName('George'); + $em->persist($user); + $em->flush(); + $em->getConnection()->commit(); + } catch (Exception $e) { + $em->getConnection()->rollback(); + $em->close(); + throw $e; + } + +Explicit transaction demarcation is required when you want to +include custom DBAL operations in a unit of work or when you want +to make use of some methods of the ``EntityManager`` API that +require an active transaction. Such methods will throw a +``TransactionRequiredException`` to inform you of that +requirement. + +A more convenient alternative for explicit transaction demarcation +is the use of provided control abstractions in the form of +``Connection#transactional($func)`` and +``EntityManager#transactional($func)``. When used, these control +abstractions ensure that you never forget to rollback the +transaction or close the ``EntityManager``, apart from the obvious +code reduction. An example that is functionally equivalent to the +previously shown code looks as follows: + +:: + + transactional(function($em) { + //... do some work + $user = new User; + $user->setName('George'); + $em->persist($user); + }); + +The difference between ``Connection#transactional($func)`` and +``EntityManager#transactional($func)`` is that the latter +abstraction flushes the ``EntityManager`` prior to transaction +commit and also closes the ``EntityManager`` properly when an +exception occurs (in addition to rolling back the transaction). + +Exception Handling +~~~~~~~~~~~~~~~~~~ + +When using implicit transaction demarcation and an exception occurs +during ``EntityManager#flush()``, the transaction is automatically +rolled back and the ``EntityManager`` closed. + +When using explicit transaction demarcation and an exception +occurs, the transaction should be rolled back immediately and the +``EntityManager`` closed by invoking ``EntityManager#close()`` and +subsequently discarded, as demonstrated in the example above. This +can be handled elegantly by the control abstractions shown earlier. +Note that when catching ``Exception`` you should generally re-throw +the exception. If you intend to recover from some exceptions, catch +them explicitly in earlier catch blocks (but do not forget to +rollback the transaction and close the ``EntityManager`` there as +well). All other best practices of exception handling apply +similarly (i.e. either log or re-throw, not both, etc.). + +As a result of this procedure, all previously managed or removed +instances of the ``EntityManager`` become detached. The state of +the detached objects will be the state at the point at which the +transaction was rolled back. The state of the objects is in no way +rolled back and thus the objects are now out of synch with the +database. The application can continue to use the detached objects, +knowing that their state is potentially no longer accurate. + +If you intend to start another unit of work after an exception has +occurred you should do that with a new ``EntityManager``. + +Locking Support +--------------- + +Doctrine 2 offers support for Pessimistic- and Optimistic-locking +strategies natively. This allows to take very fine-grained control +over what kind of locking is required for your Entities in your +application. + +Optimistic Locking +~~~~~~~~~~~~~~~~~~ + +Database transactions are fine for concurrency control during a +single request. However, a database transaction should not span +across requests, the so-called "user think time". Therefore a +long-running "business transaction" that spans multiple requests +needs to involve several database transactions. Thus, database +transactions alone can no longer control concurrency during such a +long-running business transaction. Concurrency control becomes the +partial responsibility of the application itself. + +Doctrine has integrated support for automatic optimistic locking +via a version field. In this approach any entity that should be +protected against concurrent modifications during long-running +business transactions gets a version field that is either a simple +number (mapping type: integer) or a timestamp (mapping type: +datetime). When changes to such an entity are persisted at the end +of a long-running conversation the version of the entity is +compared to the version in the database and if they don't match, an +``OptimisticLockException`` is thrown, indicating that the entity +has been modified by someone else already. + +You designate a version field in an entity as follows. In this +example we'll use an integer. + +:: + + find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion); + + // do the work + + $em->flush(); + } catch(OptimisticLockException $e) { + echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; + } + +Or you can use ``EntityManager#lock()`` to find out: + +:: + + find('User', $theEntityId); + + try { + // assert version + $em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion); + + } catch(OptimisticLockException $e) { + echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; + } + +Important Implementation Notes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can easily get the optimistic locking workflow wrong if you +compare the wrong versions. Say you have Alice and Bob accessing a +hypothetical bank account: + + +- Alice reads the headline of the blog post being "Foo", at + optimistic lock version 1 (GET Request) +- Bob reads the headline of the blog post being "Foo", at + optimistic lock version 1 (GET Request) +- Bob updates the headline to "Bar", upgrading the optimistic lock + version to 2 (POST Request of a Form) +- Alice updates the headline to "Baz", ... (POST Request of a + Form) + +Now at the last stage of this scenario the blog post has to be read +again from the database before Alice's headline can be applied. At +this point you will want to check if the blog post is still at +version 1 (which it is not in this scenario). + +Using optimistic locking correctly, you *have* to add the version +as an additional hidden field (or into the SESSION for more +safety). Otherwise you cannot verify the version is still the one +being originally read from the database when Alice performed her +GET request for the blog post. If this happens you might see lost +updates you wanted to prevent with Optimistic Locking. + +See the example code, The form (GET Request): + +:: + + find('BlogPost', 123456); + + echo ''; + echo ''; + +And the change headline action (POST Request): + +:: + + find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion); + +Pessimistic Locking +~~~~~~~~~~~~~~~~~~~ + +Doctrine 2 supports Pessimistic Locking at the database level. No +attempt is being made to implement pessimistic locking inside +Doctrine, rather vendor-specific and ANSI-SQL commands are used to +acquire row-level locks. Every Entity can be part of a pessimistic +lock, there is no special metadata required to use this feature. + +However for Pessimistic Locking to work you have to disable the +Auto-Commit Mode of your Database and start a transaction around +your pessimistic lock use-case using the "Approach 2: Explicit +Transaction Demarcation" described above. Doctrine 2 will throw an +Exception if you attempt to acquire an pessimistic lock and no +transaction is running. + +Doctrine 2 currently supports two pessimistic lock modes: + + +- Pessimistic Write + (``Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE``), locks the + underlying database rows for concurrent Read and Write Operations. +- Pessimistic Read (``Doctrine\DBAL\LockMode::PESSIMISTIC_READ``), + locks other concurrent requests that attempt to update or lock rows + in write mode. + +You can use pessimistic locks in three different scenarios: + + +1. Using + ``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)`` + or + ``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`` +2. Using + ``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)`` + or + ``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`` +3. Using + ``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)`` + or + ``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`` + + diff --git a/manual/en/transactions-and-concurrency.txt b/manual/en/transactions-and-concurrency.txt index 7d49539ed..550f8e52e 100644 --- a/manual/en/transactions-and-concurrency.txt +++ b/manual/en/transactions-and-concurrency.txt @@ -1,4 +1,4 @@ -++ Transaction Demarcation +## Transaction Demarcation Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very important because if not done properly it can negatively affect the performance of your application. @@ -14,12 +14,12 @@ However, Doctrine 2 also allows (and encourages) you to take over and control tr These are two ways to deal with transactions when using the Doctrine ORM and are now described in more detail. -+++ Approach 1: Implicitly +### Approach 1: Implicitly The first approach is to use the implicit transaction handling provided by the Doctrine ORM EntityManager. Given the following code snippet, without any explicit transaction demarcation: - [php] + setName('George'); @@ -32,12 +32,12 @@ by the Doctrine ORM and is sufficient if all the data manipulation that is part through the domain model and thus the ORM. -+++ Approach 2: Explicitly +### Approach 2: Explicitly The explicit alternative is to use the `Doctrine\DBAL\Connection` API directly to control the transaction boundaries. The code then looks like this: - [php] + getConnection()->beginTransaction(); // suspend auto-commit try { @@ -63,7 +63,7 @@ When used, these control abstractions ensure that you never forget to rollback t close the `EntityManager`, apart from the obvious code reduction. An example that is functionally equivalent to the previously shown code looks as follows: - [php] + transactional(function($em) { //... do some work @@ -77,7 +77,7 @@ that the latter abstraction flushes the `EntityManager` prior to transaction com the `EntityManager` properly when an exception occurs (in addition to rolling back the transaction). -+++ Exception Handling +### Exception Handling When using implicit transaction demarcation and an exception occurs during `EntityManager#flush()`, the transaction is automatically rolled back and the `EntityManager` closed. @@ -98,12 +98,12 @@ accurate. If you intend to start another unit of work after an exception has occurred you should do that with a new `EntityManager`. -++ Locking Support +## Locking Support Doctrine 2 offers support for Pessimistic- and Optimistic-locking strategies natively. This allows to take very fine-grained control over what kind of locking is required for your Entities in your application. -+++ Optimistic Locking +### Optimistic Locking Database transactions are fine for concurrency control during a single request. However, a database transaction should not span across requests, the so-called "user think time". Therefore a long-running "business transaction" @@ -120,7 +120,7 @@ entity has been modified by someone else already. You designate a version field in an entity as follows. In this example we'll use an integer. - [php] + find('BlogPost', 123456); echo ''; @@ -220,13 +220,13 @@ See the example code, The form (GET Request): And the change headline action (POST Request): - [php] + find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion); -+++ Pessimistic Locking +### Pessimistic Locking Doctrine 2 supports Pessimistic Locking at the database level. No attempt is being made to implement pessimistic locking inside Doctrine, rather vendor-specific and ANSI-SQL commands are used to acquire row-level locks. Every Entity can diff --git a/manual/en/working-with-associations.rst b/manual/en/working-with-associations.rst new file mode 100644 index 000000000..12d2f10a6 --- /dev/null +++ b/manual/en/working-with-associations.rst @@ -0,0 +1,521 @@ +Associations between entities are represented just like in regular +object-oriented PHP, with references to other objects or +collections of objects. When it comes to persistence, it is +important to understand three main things: + + +- The concept of owning and inverse sides in bidirectional + associations as described + `here `_. +- If an entity is removed from a collection, the association is + removed, not the entity itself. A collection of entities always + only represents the association to the containing entities, not the + entity itself. +- Collection-valued persistent fields have to be instances of the + ``Doctrine\Common\Collections\Collection`` interface. + `See here `_ + for more details. + +Changes to associations in your code are not synchronized to the +database directly, but upon calling ``EntityManager#flush()``. + +To describe all the concepts of working with associations we +introduce a specific set of example entities that show all the +different flavors of association management in Doctrine. + +Association Example Entities +---------------------------- + +We will use a simple comment system with Users and Comments as +entities to show examples of association management. See the PHP +docblocks of each association in the following example for +information about its type and if its the owning or inverse side. + +:: + + commentsRead; + } + + public function setFirstComment(Comment $c) { + $this->firstComment = $c; + } + } + +The interaction code would then look like in the following snippet +(``$em`` here is an instance of the EntityManager): + +:: + + find('User', $userId); + + // unidirectional many to many + $comment = $em->find('Comment', $readCommentId); + $user->getReadComments()->add($comment); + + $em->flush(); + + // unidirectional many to one + $myFirstComment = new Comment(); + $user->setFirstComment($myFirstComment); + + $em->persist($myFirstComment); + $em->flush(); + +In the case of bi-directional associations you have to update the +fields on both sides: + +:: + + commentsAuthored; + } + + public function getFavoriteComments() { + return $this->favorites; + } + } + + class Comment + { + // ... + + public function getUserFavorites() { + return $this->userFavorites; + } + + public function setAuthor(User $author = null) { + $this->author = $author; + } + } + + // Many-to-Many + $user->getFavorites()->add($favoriteComment); + $favoriteComment->getUserFavorites()->add($user); + + $em->flush(); + + // Many-To-One / One-To-Many Bidirectional + $newComment = new Comment(); + $user->getAuthoredComments()->add($newComment); + $newComment->setAuthor($user); + + $em->persist($newComment); + $em->flush(); + +Notice how always both sides of the bidirectional association are +updated. The previous unidirectional associations were simpler to +handle. + +Removing Associations +--------------------- + +Removing an association between two entities is similarly +straight-forward. There are two strategies to do so, by key and by +element. Here are some examples: + +:: + + getComments()->removeElement($comment); + $comment->setAuthor(null); + + $user->getFavorites()->removeElement($comment); + $comment->getUserFavorites()->removeElement($user); + + // Remove by Key + $user->getComments()->removeElement($ithComment); + $comment->setAuthor(null); + +You need to call ``$em->flush()`` to make persist these changes in +the database permanently. + +Notice how both sides of the bidirectional association are always +updated. Unidirectional associations are consequently simpler to +handle. Also note that if you type-hint your methods, i.e. +``setAddress(Address $address)``, then PHP does only allows null +values if ``null`` is set as default value. Otherwise +setAddress(null) will fail for removing the association. If you +insist on type-hinting a typical way to deal with this is to +provide a special method, like ``removeAddress()``. This can also +provide better encapsulation as it hides the internal meaning of +not having an address. + +When working with collections, keep in mind that a Collection is +essentially an ordered map (just like a PHP array). That is why the +``remove`` operation accepts an index/key. ``removeElement`` is a +separate method that has O(n) complexity using ``array_search``, +where n is the size of the map. + + **NOTE** + + Since Doctrine always only looks at the owning side of a + bidirectional association for updates, it is not necessary for + write operations that an inverse collection of a bidirectional + one-to-many or many-to-many association is updated. This knowledge + can often be used to improve performance by avoiding the loading of + the inverse collection. + + +You can also clear the contents of a whole collection using the +``Collections::clear()`` method. You should be aware that using +this method can lead to a straight and optimized database delete or +update call during the flush operation that is not aware of +entities that have been re-added to the collection. + +Say you clear a collection of tags by calling +``$post->getTags()->clear();`` and then call +``$post->getTags()->add($tag)``. This will not recognize tag being +already added before and issue two database calls. + +Association Management Methods +------------------------------ + +It is generally a good idea to encapsulate proper association +management inside the entity classes. This makes it easier to use +the class correctly and can encapsulate details about how the +association is maintained. + +The following code shows updates to the previous User and Comment +example that encapsulate much of the association management code: + +:: + + commentsRead[] = $comment; + } + + public function addComment(Comment $comment) { + if (count($this->commentsAuthored) == 0) { + $this->setFirstComment($comment); + } + $this->comments[] = $comment; + $comment->setAuthor($this); + } + + private function setFirstComment(Comment $c) { + $this->firstComment = $c; + } + + public function addFavorite(Comment $comment) { + $this->favorites->add($comment); + $comment->addUserFavorite($this); + } + + public function removeFavorite(Comment $comment) { + $this->favorites->removeElement($comment); + $comment->removeUserFavorite($this); + } + } + + class Comment + { + // .. + + public function addUserFavorite(User $user) { + $this->userFavorites[] = $user; + } + + public function removeUserFavorite(User $user) { + $this->userFavorites->removeElement($user); + } + } + +You will notice that ``addUserFavorite`` and ``removeUserFavorite`` +do not call ``addFavorite`` and ``removeFavorite``, thus the +bidirectional association is strictly-speaking still incomplete. +However if you would naively add the ``addFavorite`` in +``addUserFavorite``, you end up with an infinite loop, so more work +is needed. As you can see, proper bidirectional association +management in plain OOP is a non-trivial task and encapsulating all +the details inside the classes can be challenging. + + **NOTE** + + If you want to make sure that your collections are perfectly + encapsulated you should not return them from a + ``getCollectionName()`` method directly, but call + ``$collection->toArray()``. This way a client programmer for the + entity cannot circumvent the logic you implement on your entity for + association management. For example: + + +:: + + commentsRead->toArray(); + } + } + +This will however always initialize the collection, with all the +performance penalties given the size. In some scenarios of large +collections it might even be a good idea to completely hide the +read access behind methods on the EntityRepository. + +There is no single, best way for association management. It greatly +depends on the requirements of your concrete domain model as well +as your preferences. + +Synchronizing Bidirectional Collections +--------------------------------------- + +In the case of Many-To-Many associations you as the developer are +responsible to keep the collections on the owning and inverse side +up in sync, when you apply changes to them. Doctrine can only +guarantee a consistent state for the hydration, not for your client +code. + +Using the User-Comment entities from above, a very simple example +can show the possible caveats you can encounter: + +:: + + getFavorites()->add($favoriteComment); + // not calling $favoriteComment->getUserFavorites()->add($user); + + $user->getFavorites()->contains($favoriteComment); // TRUE + $favoriteComment->getUserFavorites()->contains($user); // FALSE + +There are to approaches to handle this problem in your code: + + +1. Ignore updating the inverse side of bidirectional collections, + BUT never read from them in requests that changed their state. In + the next Request Doctrine hydrates the consistent collection state + again. +2. Always keep the bidirectional collections in sync through + association management methods. Reads of the Collections directly + after changes are consistent then. + +Transitive persistence / Cascade Operations +------------------------------------------- + +Persisting, removing, detaching and merging individual entities can +become pretty cumbersome, especially when a larger object graph +with collections is involved. Therefore Doctrine 2 provides a +mechanism for transitive persistence through cascading of these +operations. Each association to another entity or a collection of +entities can be configured to automatically cascade certain +operations. By default, no operations are cascaded. + +The following cascade options exist: + + +- persist : Cascades persist operations to the associated + entities. +- remove : Cascades remove operations to the associated entities. +- merge : Cascades merge operations to the associated entities. +- detach : Cascades detach operations to the associated entities. +- all : Cascades persist, remove, merge and detach operations to + associated entities. + +The following example is an extension to the User-Comment example +of this chapter. Suppose in our application a user is created +whenever he writes his first comment. In this case we would use the +following code: + +:: + + addComment($myFirstComment); + + $em->persist($user); + $em->persist($myFirstComment); + $em->flush(); + +Even if you *persist* a new User that contains our new Comment this +code would fail if you removed the call to +``EntityManager#persist($myFirstComment)``. Doctrine 2 does not +cascade the persist operation to all nested entities that are new +as well. + +More complicated is the deletion of all a users comments when he is +removed from the system: + +:: + + $user = $em->find('User', $deleteUserId); + + foreach ($user->getAuthoredComments() AS $comment) { + $em->remove($comment); + } + $em->remove($user); + $em->flush(); + +Without the loop over all the authored comments Doctrine would use +an UPDATE statement only to set the foreign key to NULL and only +the User would be deleted from the database during the +flush()-Operation. + +To have Doctrine handle both cases automatically we can change the +``User#commentsAuthored`` property to cascade both the "persist" +and the "remove" operation. + +:: + + find('User', $userId); // unidirectional many to many @@ -145,7 +145,7 @@ The interaction code would then look like in the following snippet (`$em` here i In the case of bi-directional associations you have to update the fields on both sides: - [php] + getComments()->removeElement($comment); $comment->setAuthor(null); @@ -232,14 +232,14 @@ Say you clear a collection of tags by calling `$post->getTags()->clear();` and t `$post->getTags()->add($tag)`. This will not recognize tag being already added before and issue two database calls. -++ Association Management Methods +## Association Management Methods It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained. The following code shows updates to the previous User and Comment example that encapsulate much of the association management code: - [php] + them from a `getCollectionName()` method directly, but call `$collection->toArray()`. This way a client programmer > for the entity cannot circumvent the logic you implement on your entity for association management. For example: - [php] + commentsRead->toArray(); @@ -310,7 +310,7 @@ methods on the EntityRepository. There is no single, best way for association management. It greatly depends on the requirements of your concrete domain model as well as your preferences. -++ Synchronizing Bidirectional Collections +## Synchronizing Bidirectional Collections In the case of Many-To-Many associations you as the developer are responsible to keep the collections on the owning and inverse side up in sync, when you apply changes to them. Doctrine can only guarantee a consistent @@ -318,7 +318,7 @@ state for the hydration, not for your client code. Using the User-Comment entities from above, a very simple example can show the possible caveats you can encounter: - [php] + getFavorites()->add($favoriteComment); // not calling $favoriteComment->getUserFavorites()->add($user); @@ -332,7 +332,7 @@ There are to approaches to handle this problem in your code: 2. Always keep the bidirectional collections in sync through association management methods. Reads of the Collections directly after changes are consistent then. -++ Transitive persistence / Cascade Operations +## Transitive persistence / Cascade Operations Persisting, removing, detaching and merging individual entities can become pretty cumbersome, especially when a larger object graph with collections is involved. @@ -353,7 +353,7 @@ The following example is an extension to the User-Comment example of this chapte Suppose in our application a user is created whenever he writes his first comment. In this case we would use the following code: - [php] + addComment($myFirstComment); @@ -383,7 +383,7 @@ during the flush()-Operation. To have Doctrine handle both cases automatically we can change the `User#commentsAuthored` property to cascade both the "persist" and the "remove" operation. - [php] + find('CMS\Article', 1234); + $article->setHeadline('Hello World dude!'); + + $article2 = $entityManager->find('CMS\Article', 1234); + echo $article2->getHeadline(); + +In this case the Article is accessed from the entity manager twice, +but modified in between. Doctrine 2 realizes this and will only +ever give you access to one instance of the Article with ID 1234, +no matter how often do you retrieve it from the EntityManager and +even no matter what kind of Query method you are using (find, +Repository Finder or DQL). This is called "Identity Map" pattern, +which means Doctrine keeps a map of each entity and ids that have +been retrieved per PHP request and keeps returning you the same +instances. + +In the previous example the echo prints "Hello World dude!" to the +screen. You can even verify that ``$article`` and ``$article2`` are +indeed pointing to the same instance by running the following +code: + +:: + + comments = new ArrayCollection(); + } + + public function getAuthor() { return $this->author; } + public function getComments() { return $this->comments; } + } + + $article = $em->find('Article', 1); + +This code only retrieves the ``User`` instance with id 1 executing +a single SELECT statement against the user table in the database. +You can still access the associated properties author and comments +and the associated objects they contain. + +This works by utilizing the lazy loading pattern. Instead of +passing you back a real Author instance and a collection of +comments Doctrine will create proxy instances for you. Only if you +access these proxies for the first time they will go through the +EntityManager and load their state from the database. + +This lazy-loading process happens behind the scenes, hidden from +your code. See the following code: + +:: + + find('Article', 1); + + // accessing a method of the user instance triggers the lazy-load + echo "Author: " . $article->getAuthor()->getName() . "\n"; + + // Lazy Loading Proxies pass instanceof tests: + if ($article->getAuthor() instanceof User) { + // a User Proxy is a generated "UserProxy" class + } + + // accessing the comments as an iterator triggers the lazy-load + // retrieving ALL the comments of this article from the database + // using a single SELECT statement + foreach ($article->getComments() AS $comment) { + echo $comment->getText() . "\n\n"; + } + + // Article::$comments passes instanceof tests for the Collection interface + // But it will NOT pass for the ArrayCollection interface + if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) { + echo "This will always be true!"; + } + +A slice of the generated proxy classes code looks like the +following piece of code. A real proxy class override ALL public +methods along the lines of the ``getName()`` method shown below: + +:: + + _load(); + return parent::getName(); + } + // .. other public methods of User + } + + **Warning** + + Traversing the object graph for parts that are lazy-loaded will + easily trigger lots of SQL queries and will perform badly if used + to heavily. Make sure to use DQL to fetch-join all the parts of the + object-graph that you need as efficiently as possible. + + +Persisting entities +------------------- + +An entity can be made persistent by passing it to the +``EntityManager#persist($entity)`` method. By applying the persist +operation on some entity, that entity becomes MANAGED, which means +that its persistence is from now on managed by an EntityManager. As +a result the persistent state of such an entity will subsequently +be properly synchronized with the database when +``EntityManager#flush()`` is invoked. + + **CAUTION** Invoking the ``persist`` method on an entity does NOT + cause an immediate SQL INSERT to be issued on the database. + Doctrine applies a strategy called "transactional write-behind", + which means that it will delay most SQL commands until + ``EntityManager#flush()`` is invoked which will then issue all + necessary SQL statements to synchronize your objects with the + database in the most efficient way and a single, short transaction, + taking care of maintaining referential integrity. + + +Example: + +:: + + setName('Mr.Right'); + $em->persist($user); + $em->flush(); + + **CAUTION** Generated entity identifiers / primary keys are + guaranteed to be available after the next successful flush + operation that involves the entity in question. You can not rely on + a generated identifier to be available directly after invoking + ``persist``. The inverse is also true. You can not rely on a + generated identifier being not available after a failed flush + operation. + + +The semantics of the persist operation, applied on an entity X, are +as follows: + + +- If X is a new entity, it becomes managed. The entity X will be + entered into the database as a result of the flush operation. +- If X is a preexisting managed entity, it is ignored by the + persist operation. However, the persist operation is cascaded to + entities referenced by X, if the relationships from X to these + other entities are mapped with cascade=PERSIST or cascade=ALL (see + "Transitive Persistence"). +- If X is a removed entity, it becomes managed. +- If X is a detached entity, an exception will be thrown on + flush. + +Removing entities +----------------- + +An entity can be removed from persistent storage by passing it to +the ``EntityManager#remove($entity)`` method. By applying the +``remove`` operation on some entity, that entity becomes REMOVED, +which means that its persistent state will be deleted once +``EntityManager#flush()`` is invoked. + + **CAUTION** Just like ``persist``, invoking ``remove`` on an entity + does NOT cause an immediate SQL DELETE to be issued on the + database. The entity will be deleted on the next invocation of + ``EntityManager#flush()`` that involves that entity. + + +Example: + +:: + + remove($user); + $em->flush(); + +The semantics of the remove operation, applied to an entity X are +as follows: + + +- If X is a new entity, it is ignored by the remove operation. + However, the remove operation is cascaded to entities referenced by + X, if the relationship from X to these other entities is mapped + with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). +- If X is a managed entity, the remove operation causes it to + become removed. The remove operation is cascaded to entities + referenced by X, if the relationships from X to these other + entities is mapped with cascade=REMOVE or cascade=ALL (see + "Transitive Persistence"). +- If X is a detached entity, an InvalidArgumentException will be + thrown. +- If X is a removed entity, it is ignored by the remove operation. +- A removed entity X will be removed from the database as a result + of the flush operation. + +After an entity has been removed its in-memory state is the same as +before the removal, except for generated identifiers. + +Removing an entity will also automatically delete any existing +records in many-to-many join tables that link this entity. The +action taken depends on the value of the ``@joinColumn`` mapping +attribute "onDelete". Either Doctrine issues a dedicated ``DELETE`` +statement for records of each join table or it depends on the +foreign key semantics of onDelete="CASCADE". + +Deleting an object with all its associated objects can be achieved +in multiple ways with very different performance impacts. + + +1. If an association is marked as ``CASCADE=REMOVE`` Doctrine 2 + will fetch this association. If its a Single association it will + pass this entity to + ´EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()\`. + In both cases the cascade remove semantics are applied recursively. + For large object graphs this removal strategy can be very costly. +2. Using a DQL ``DELETE`` statement allows you to delete multiple + entities of a type with a single command and without hydrating + these entities. This can be very efficient to delete large object + graphs from the database. +3. Using foreign key semantics ``onDelete="CASCADE"`` can force the + database to remove all associated objects internally. This strategy + is a bit tricky to get right but can be very powerful and fast. You + should be aware however that using strategy 1 (``CASCADE=REMOVE``) + completely by-passes any foreign key ``onDelete=CASCADE`` option, + because Doctrine will fetch and remove all associated entities + explicitly nevertheless. + +Detaching entities +------------------ + +An entity is detached from an EntityManager and thus no longer +managed by invoking the ``EntityManager#detach($entity)`` method on +it or by cascading the detach operation to it. Changes made to the +detached entity, if any (including removal of the entity), will not +be synchronized to the database after the entity has been +detached. + +Doctrine will not hold on to any references to a detached entity. + +Example: + +:: + + detach($entity); + +The semantics of the detach operation, applied to an entity X are +as follows: + + +- If X is a managed entity, the detach operation causes it to + become detached. The detach operation is cascaded to entities + referenced by X, if the relationships from X to these other + entities is mapped with cascade=DETACH or cascade=ALL (see + "Transitive Persistence"). Entities which previously referenced X + will continue to reference X. +- If X is a new or detached entity, it is ignored by the detach + operation. +- If X is a removed entity, the detach operation is cascaded to + entities referenced by X, if the relationships from X to these + other entities is mapped with cascade=DETACH or cascade=ALL (see + "Transitive Persistence"). Entities which previously referenced X + will continue to reference X. + +There are several situations in which an entity is detached +automatically without invoking the ``detach`` method: + + +- When ``EntityManager#clear()`` is invoked, all entities that are + currently managed by the EntityManager instance become detached. +- When serializing an entity. The entity retrieved upon subsequent + unserialization will be detached (This is the case for all entities + that are serialized and stored in some cache, i.e. when using the + Query Result Cache). + +The ``detach`` operation is usually not as frequently needed and +used as ``persist`` and ``remove``. + +Merging entities +---------------- + +Merging entities refers to the merging of (usually detached) +entities into the context of an EntityManager so that they become +managed again. To merge the state of an entity into an +EntityManager use the ``EntityManager#merge($entity)`` method. The +state of the passed entity will be merged into a managed copy of +this entity and this copy will subsequently be returned. + +Example: + +:: + + merge($detachedEntity); + // $entity now refers to the fully managed copy returned by the merge operation. + // The EntityManager $em now manages the persistence of $entity as usual. + + **CAUTION** When you want to serialize/unserialize entities you + have to make all entity properties protected, never private. The + reason for this is, if you serialize a class that was a proxy + instance before, the private variables won't be serialized and a + PHP Notice is thrown. + + +The semantics of the merge operation, applied to an entity X, are +as follows: + + +- If X is a detached entity, the state of X is copied onto a + pre-existing managed entity instance X' of the same identity. +- If X is a new entity instance, a new managed copy X' will be + created and the state of X is copied onto this managed instance. +- If X is a removed entity instance, an InvalidArgumentException + will be thrown. +- If X is a managed entity, it is ignored by the merge operation, + however, the merge operation is cascaded to entities referenced by + relationships from X if these relationships have been mapped with + the cascade element value MERGE or ALL (see "Transitive + Persistence"). +- For all entities Y referenced by relationships from X having the + cascade element value MERGE or ALL, Y is merged recursively as Y'. + For all such Y referenced by X, X' is set to reference Y'. (Note + that if X is managed then X is the same object as X'.) +- If X is an entity merged to X', with a reference to another + entity Y, where cascade=MERGE or cascade=ALL is not specified, then + navigation of the same association from X' yields a reference to a + managed object Y' with the same persistent identity as Y. + +The ``merge`` operation will throw an ``OptimisticLockException`` +if the entity being merged uses optimistic locking through a +version field and the versions of the entity being merged and the +managed copy don't match. This usually means that the entity has +been modified while being detached. + +The ``merge`` operation is usually not as frequently needed and +used as ``persist`` and ``remove``. The most common scenario for +the ``merge`` operation is to reattach entities to an EntityManager +that come from some cache (and are therefore detached) and you want +to modify and persist such an entity. + + **NOTE** If you load some detached entities from a cache and you do + not need to persist or delete them or otherwise make use of them + without the need for persistence services there is no need to use + ``merge``. I.e. you can simply pass detached objects from a cache + directly to the view. + + +Synchronization with the Database +--------------------------------- + +The state of persistent entities is synchronized with the database +on flush of an ``EntityManager`` which commits the underlying +``UnitOfWork``. The synchronization involves writing any updates to +persistent entities and their relationships to the database. +Thereby bidirectional relationships are persisted based on the +references held by the owning side of the relationship as explained +in the Association Mapping chapter. + +When ``EntityManager#flush()`` is called, Doctrine inspects all +managed, new and removed entities and will perform the following +operations. + +Synchronizing New and Managed Entities +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The flush operation applies to a managed entity with the following +semantics: + + +- The entity itself is synchronized to the database using a SQL + UPDATE statement, only if at least one persistent field has + changed. +- No SQL updates are executed if the entity did not change. + +The flush operation applies to a new entity with the following +semantics: + + +- The entity itself is synchronized to the database using a SQL + INSERT statement. + +For all (initialized) relationships of the new or managed entity +the following semantics apply to each associated entity X: + + +- If X is new and persist operations are configured to cascade on + the relationship, X will be persisted. +- If X is new and no persist operations are configured to cascade + on the relationship, an exception will be thrown as this indicates + a programming error. +- If X is removed and persist operations are configured to cascade + on the relationship, an exception will be thrown as this indicates + a programming error (X would be re-persisted by the cascade). +- If X is detached and persist operations are configured to + cascade on the relationship, an exception will be thrown (This is + semantically the same as passing X to persist()). + +Synchronizing Removed Entities +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The flush operation applies to a removed entity by deleting its +persistent state from the database. No cascade options are relevant +for removed entities on flush, the cascade remove option is already +executed during ``EntityManager#remove($entity)``. + +The size of a Unit of Work +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The size of a Unit of Work mainly refers to the number of managed +entities at a particular point in time. + +The cost of flushing +~~~~~~~~~~~~~~~~~~~~ + +How costly a flush operation is, mainly depends on two factors: + + +- The size of the EntityManager's current UnitOfWork. +- The configured change tracking policies + +You can get the size of a UnitOfWork as follows: + +:: + + getUnitOfWork()->size(); + +The size represents the number of managed entities in the Unit of +Work. This size affects the performance of flush() operations due +to change tracking (see "Change Tracking Policies") and, of course, +memory consumption, so you may want to check it from time to time +during development. + + **CAUTION** Do not invoke ``flush`` after every change to an entity + or every single invocation of persist/remove/merge/... This is an + anti-pattern and unnecessarily reduces the performance of your + application. Instead, form units of work that operate on your + objects and call ``flush`` when you are done. While serving a + single HTTP request there should be usually no need for invoking + ``flush`` more than 0-2 times. + + +Direct access to a Unit of Work +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can get direct access to the Unit of Work by calling +``EntityManager#getUnitOfWork()``. This will return the UnitOfWork +instance the EntityManager is currently using. + +:: + + getUnitOfWork(); + + **NOTE** Directly manipulating a UnitOfWork is not recommended. + When working directly with the UnitOfWork API, respect methods + marked as INTERNAL by not using them and carefully read the API + documentation. + + +Entity State +~~~~~~~~~~~~ + +As outlined in the architecture overview an entity can be in one of +four possible states: NEW, MANAGED, REMOVED, DETACHED. If you +explicitly need to find out what the current state of an entity is +in the context of a certain ``EntityManager`` you can ask the +underlying ``UnitOfWork``: + +:: + + getUnitOfWork()->getEntityState($entity)) { + case UnitOfWork::MANAGED: + ... + case UnitOfWork::REMOVED: + ... + case UnitOfWork::DETACHED: + ... + case UnitOfWork::NEW: + ... + } + +An entity is in MANAGED state if it is associated with an +``EntityManager`` and it is not REMOVED. + +An entity is in REMOVED state after it has been passed to +``EntityManager#remove()`` until the next flush operation of the +same EntityManager. A REMOVED entity is still associated with an +``EntityManager`` until the next flush operation. + +An entity is in DETACHED state if it has persistent state and +identity but is currently not associated with an +``EntityManager``. + +An entity is in NEW state if has no persistent state and identity +and is not associated with an ``EntityManager`` (for example those +just created via the "new" operator). + +Querying +-------- + +Doctrine 2 provides the following ways, in increasing level of +power and flexibility, to query for persistent objects. You should +always start with the simplest one that suits your needs. + +By Primary Key +~~~~~~~~~~~~~~ + +The most basic way to query for a persistent object is by its +identifier / primary key using the +``EntityManager#find($entityName, $id)`` method. Here is an +example: + +:: + + find('MyProject\Domain\User', $id); + +The return value is either the found entity instance or null if no +instance could be found with the given identifier. + +Essentially, ``EntityManager#find()`` is just a shortcut for the +following: + +:: + + getRepository('MyProject\Domain\User')->find($id); + +``EntityManager#getRepository($entityName)`` returns a repository +object which provides many ways to retrieve entities of the +specified type. By default, the repository instance is of type +``Doctrine\ORM\EntityRepository``. You can also use custom +repository classes as shown later. + +By Simple Conditions +~~~~~~~~~~~~~~~~~~~~ + +To query for one or more entities based on several conditions that +form a logical conjunction, use the ``findBy`` and ``findOneBy`` +methods on a repository as follows: + +:: + + getRepository('MyProject\Domain\User')->findBy(array('age' => 20)); + + // All users that are 20 years old and have a surname of 'Miller' + $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller')); + + // A single user by its nickname + $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); + +An EntityRepository also provides a mechanism for more concise +calls through its use of ``__call``. Thus, the following two +examples are equivalent: + +:: + + getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); + + // A single user by its nickname (__call magic) + $user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb'); + +By Eager Loading +~~~~~~~~~~~~~~~~ + +Whenever you query for an entity that has persistent associations +and these associations are mapped as EAGER, they will automatically +be loaded together with the entity being queried and is thus +immediately available to your application. + +By Lazy Loading +~~~~~~~~~~~~~~~ + +Whenever you have a managed entity instance at hand, you can +traverse and use any associations of that entity that are +configured LAZY as if they were in-memory already. Doctrine will +automatically load the associated objects on demand through the +concept of lazy-loading. + +By DQL +~~~~~~ + +The most powerful and flexible method to query for persistent +objects is the Doctrine Query Language, an object query language. +DQL enables you to query for persistent objects in the language of +objects. DQL understands classes, fields, inheritance and +associations. DQL is syntactically very similar to the familiar SQL +but *it is not SQL*. + +A DQL query is represented by an instance of the +``Doctrine\ORM\Query`` class. You create a query using +``EntityManager#createQuery($dql)``. Here is a simple example: + +:: + + createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30"); + $users = $q->getResult(); + +Note that this query contains no knowledge about the relational +schema, only about the object model. DQL supports positional as +well as named parameters, many functions, (fetch) joins, +aggregates, subqueries and much more. Detailed information about +DQL and its syntax as well as the Doctrine class can be found in +`the dedicated chapter `_. +For programmatically building up queries based on conditions that +are only known at runtime, Doctrine provides the special +``Doctrine\ORM\QueryBuilder`` class. More information on +constructing queries with a QueryBuilder can be found +`in the dedicated chapter `_. + +By Native Queries +~~~~~~~~~~~~~~~~~ + +As an alternative to DQL or as a fallback for special SQL +statements native queries can be used. Native queries are built by +using a hand-crafted SQL query and a ResultSetMapping that +describes how the SQL result set should be transformed by Doctrine. +More information about native queries can be found in +`the dedicated chapter `_. + +Custom Repositories +~~~~~~~~~~~~~~~~~~~ + +By default the EntityManager returns a default implementation of +``Doctrine\ORM\EntityRepository`` when you call +``EntityManager#getRepository($entityClass)``. You can overwrite +this behaviour by specifying the class name of your own Entity +Repository in the Annotation, XML or YAML metadata. In large +applications that require lots of specialized DQL queries using a +custom repository is one recommended way of grouping these queries +in a central location. + +:: + + _em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"') + ->getResult(); + } + } + +You can access your repository now by calling: + +:: + + getRepository('MyDomain\Model\User')->getAllAdminUsers(); + + diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 0decce091..ad0a943ec 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -18,7 +18,7 @@ are lost. > > Not calling `EntityManager#flush()` will lead to all changes during that request being lost. -++ Entities and the Identity Map +## Entities and the Identity Map Entities are objects with identity. Their identity has a conceptual meaning inside your domain. In a CMS application each article has a unique id. You can uniquely identify each article @@ -27,7 +27,7 @@ by that id. Take the following example, where you find an article with the headline "Hello World" with the ID 1234: - [php] + find('CMS\Article', 1234); $article->setHeadline('Hello World dude!'); @@ -45,7 +45,7 @@ In the previous example the echo prints "Hello World dude!" to the screen. You c even verify that `$article` and `$article2` are indeed pointing to the same instance by running the following code: - [php] + find('Article', 1); // accessing a method of the user instance triggers the lazy-load @@ -127,7 +127,7 @@ This lazy-loading process happens behind the scenes, hidden from your code. See A slice of the generated proxy classes code looks like the following piece of code. A real proxy class override ALL public methods along the lines of the `getName()` method shown below: - [php] + of SQL queries and will perform badly if used to heavily. Make sure to use DQL > to fetch-join all the parts of the object-graph that you need as efficiently as possible. -++ Persisting entities +## Persisting entities An entity can be made persistent by passing it to the `EntityManager#persist($entity)` method. By applying the persist operation on some entity, that entity becomes MANAGED, @@ -167,7 +167,7 @@ synchronized with the database when `EntityManager#flush()` is invoked. Example: - [php] + setName('Mr.Right'); $em->persist($user); @@ -188,7 +188,7 @@ The semantics of the persist operation, applied on an entity X, are as follows: * If X is a detached entity, an exception will be thrown on flush. -++ Removing entities +## Removing entities An entity can be removed from persistent storage by passing it to the `EntityManager#remove($entity)` method. By applying the `remove` operation on some entity, that entity becomes REMOVED, which means that its persistent state will be deleted once `EntityManager#flush()` is invoked. @@ -199,7 +199,7 @@ An entity can be removed from persistent storage by passing it to the `EntityMan Example: - [php] + remove($user); $em->flush(); @@ -238,7 +238,7 @@ ways with very different performance impacts. any foreign key `onDelete=CASCADE` option, because Doctrine will fetch and remove all associated entities explicitly nevertheless. -++ Detaching entities +## Detaching entities An entity is detached from an EntityManager and thus no longer managed by invoking the `EntityManager#detach($entity)` method on it or by cascading @@ -250,7 +250,7 @@ Doctrine will not hold on to any references to a detached entity. Example: - [php] + detach($entity); The semantics of the detach operation, applied to an entity X are as follows: @@ -267,7 +267,7 @@ There are several situations in which an entity is detached automatically withou The `detach` operation is usually not as frequently needed and used as `persist` and `remove`. -++ Merging entities +## Merging entities Merging entities refers to the merging of (usually detached) entities into the context of an EntityManager so that they become managed again. To merge the @@ -277,7 +277,7 @@ this entity and this copy will subsequently be returned. Example: - [php] + merge($detachedEntity); // $entity now refers to the fully managed copy returned by the merge operation. @@ -314,7 +314,7 @@ and you want to modify and persist such an entity. > there is no need to use `merge`. I.e. you can simply pass detached objects from a cache > directly to the view. -++ Synchronization with the Database +## Synchronization with the Database The state of persistent entities is synchronized with the database on flush of an `EntityManager` which commits the underlying `UnitOfWork`. The synchronization involves writing any updates to @@ -325,7 +325,7 @@ in the Association Mapping chapter. When `EntityManager#flush()` is called, Doctrine inspects all managed, new and removed entities and will perform the following operations. -+++ Synchronizing New and Managed Entities +### Synchronizing New and Managed Entities The flush operation applies to a managed entity with the following semantics: @@ -348,18 +348,18 @@ associated entity X: * If X is detached and persist operations are configured to cascade on the relationship, an exception will be thrown (This is semantically the same as passing X to persist()). -+++ Synchronizing Removed Entities +### Synchronizing Removed Entities The flush operation applies to a removed entity by deleting its persistent state from the database. No cascade options are relevant for removed entities on flush, the cascade remove option is already executed during `EntityManager#remove($entity)`. -+++ The size of a Unit of Work +### The size of a Unit of Work The size of a Unit of Work mainly refers to the number of managed entities at a particular point in time. -+++ The cost of flushing +### The cost of flushing How costly a flush operation is, mainly depends on two factors: @@ -368,7 +368,7 @@ How costly a flush operation is, mainly depends on two factors: You can get the size of a UnitOfWork as follows: - [php] + getUnitOfWork()->size(); The size represents the number of managed entities in the Unit of Work. This @@ -383,12 +383,12 @@ may want to check it from time to time during development. > and call `flush` when you are done. While serving a single HTTP request there should > be usually no need for invoking `flush` more than 0-2 times. -+++ Direct access to a Unit of Work +### Direct access to a Unit of Work You can get direct access to the Unit of Work by calling `EntityManager#getUnitOfWork()`. This will return the UnitOfWork instance the EntityManager is currently using. - [php] + getUnitOfWork(); > **NOTE** @@ -396,14 +396,14 @@ This will return the UnitOfWork instance the EntityManager is currently using. > UnitOfWork API, respect methods marked as INTERNAL by not using them and carefully read > the API documentation. -+++ Entity State +### Entity State As outlined in the architecture overview an entity can be in one of four possible states: NEW, MANAGED, REMOVED, DETACHED. If you explicitly need to find out what the current state of an entity is in the context of a certain `EntityManager` you can ask the underlying `UnitOfWork`: - [php] + getUnitOfWork()->getEntityState($entity)) { case UnitOfWork::MANAGED: ... @@ -428,15 +428,15 @@ An entity is in NEW state if has no persistent state and identity and is not ass `EntityManager` (for example those just created via the "new" operator). -++ Querying +## Querying Doctrine 2 provides the following ways, in increasing level of power and flexibility, to query for persistent objects. You should always start with the simplest one that suits your needs. -+++ By Primary Key +### By Primary Key The most basic way to query for a persistent object is by its identifier / primary key using the `EntityManager#find($entityName, $id)` method. Here is an example: - [php] + find('MyProject\Domain\User', $id); @@ -444,17 +444,17 @@ The return value is either the found entity instance or null if no instance coul Essentially, `EntityManager#find()` is just a shortcut for the following: - [php] + getRepository('MyProject\Domain\User')->find($id); `EntityManager#getRepository($entityName)` returns a repository object which provides many ways to retrieve entities of the specified type. By default, the repository instance is of type `Doctrine\ORM\EntityRepository`. You can also use custom repository classes as shown later. -+++ By Simple Conditions +### By Simple Conditions To query for one or more entities based on several conditions that form a logical conjunction, use the `findBy` and `findOneBy` methods on a repository as follows: - [php] + getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); @@ -476,24 +476,24 @@ An EntityRepository also provides a mechanism for more concise calls through its $user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb'); -+++ By Eager Loading +### By Eager Loading Whenever you query for an entity that has persistent associations and these associations are mapped as EAGER, they will automatically be loaded together with the entity being queried and is thus immediately available to your application. -+++ By Lazy Loading +### By Lazy Loading Whenever you have a managed entity instance at hand, you can traverse and use any associations of that entity that are configured LAZY as if they were in-memory already. Doctrine will automatically load the associated objects on demand through the concept of lazy-loading. -+++ By DQL +### By DQL The most powerful and flexible method to query for persistent objects is the Doctrine Query Language, an object query language. DQL enables you to query for persistent objects in the language of objects. DQL understands classes, fields, inheritance and associations. DQL is syntactically very similar to the familiar SQL but *it is not SQL*. A DQL query is represented by an instance of the `Doctrine\ORM\Query` class. You create a query using `EntityManager#createQuery($dql)`. Here is a simple example: - [php] + getRepository('MyDomain\Model\User')->getAllAdminUsers(); diff --git a/manual/en/xml-mapping.rst b/manual/en/xml-mapping.rst new file mode 100644 index 000000000..b384dd638 --- /dev/null +++ b/manual/en/xml-mapping.rst @@ -0,0 +1,716 @@ +The XML mapping driver enables you to provide the ORM metadata in +form of XML documents. + +The XML driver is backed by an XML Schema document that describes +the structure of a mapping document. The most recent version of the +XML Schema document is available online at +`http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd `_. +In order to point to the latest version of the document of a +particular stable release branch, just append the release number, +i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with +XML mapping files is to use an IDE/editor that can provide +code-completion based on such an XML Schema document. The following +is an outline of a XML mapping document with the proper xmlns/xsi +setup for the latest code in trunk. + +:: + + [xml] + + + ... + + + +The XML mapping document of a class is loaded on-demand the first +time it is requested and subsequently stored in the metadata cache. +In order to work, this requires certain conventions: + + +- Each entity/mapped superclass must get its own dedicated XML + mapping document. +- The name of the mapping document must consist of the fully + qualified name of the class, where namespace separators are + replaced by dots (.). For example an Entity with the fully + qualified class-name "MyProject" would require a mapping file + "MyProject.Entities.User.dcm.xml" unless the extension is changed. +- All mapping documents should get the extension ".dcm.xml" to + identify it as a Doctrine mapping file. This is more of a + convention and you are not forced to do this. You can change the + file extension easily enough. + +- + +:: + + setFileExtension('.xml'); + +It is recommended to put all XML mapping documents in a single +folder but you can spread the documents over several folders if you +want to. In order to tell the XmlDriver where to look for your +mapping documents, supply an array of paths as the first argument +of the constructor, like this: + +:: + + setMetadataDriverImpl($driver); + +Example +------- + +As a quick start, here is a small example document that makes use +of several common elements: + +:: + + [xml] + // Doctrine.Tests.ORM.Mapping.User.dcm.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Be aware that class-names specified in the XML files should be +fully qualified. + +XML-Element Reference +--------------------- + +The XML-Element reference explains all the tags and attributes that +the Doctrine Mapping XSD Schema defines. You should read the +Basic-, Association- and Inheritance Mapping chapters to understand +what each of this definitions means in detail. + +Defining an Entity +~~~~~~~~~~~~~~~~~~ + +Each XML Mapping File contains the definition of one entity, +specified as the ```` element as a direct child of the +```` element: + +:: + + [xml] + + + + + + +Required attributes: + + +- name - The fully qualified class-name of the entity. + +Optional attributes: + + +- table - The Table-Name to be used for this entity. Otherwise the + Unqualified Class-Name is used by default. +- repository-class - The fully qualified class-name of an + alternative ``Doctrine\ORM\EntityRepository`` implementation to be + used with this entity. +- inheritance-type - The type of inheritance, defaults to none. A + more detailed description follows in the + *Defining Inheritance Mappings* section. + +Defining Fields +~~~~~~~~~~~~~~~ + +Each entity class can contain zero to infinite fields that are +managed by Doctrine. You can define them using the ```` +element as a children to the ```` element. The field +element is only used for primitive types that are not the ID of the +entity. For the ID mapping you have to use the ```` element. + +:: + + [xml] + + + + + + + + + +Required attributes: + + +- name - The name of the Property/Field on the given Entity PHP + class. + +Optional attributes: + + +- type - The ``Doctrine\DBAL\Types\Type`` name, defaults to + "string" +- column - Name of the column in the database, defaults to the + field name. +- length - The length of the given type, for use with strings + only. +- unique - Should this field contain a unique value across the + table? Defaults to false. +- nullable - Should this field allow NULL as a value? Defaults to + false. +- version - Should this field be used for optimistic locking? Only + works on fields with type integer or datetime. +- scale - Scale of a decimal type. +- precision - Precision of a decimal type. +- column-definition - Optional alternative SQL representation for + this column. This definition begin after the field-name and has to + specify the complete column definition. Using this feature will + turn this field dirty for Schema-Tool update commands at all + times. + +Defining Identity and Generator Strategies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An entity has to have at least one ```` element. For +composite keys you can specify more than one id-element, however +surrogate keys are recommended for use with Doctrine 2. The Id +field allows to define properties of the identifier and allows a +subset of the ```` element attributes: + +:: + + [xml] + + + + +Required attributes: + + +- name - The name of the Property/Field on the given Entity PHP + class. +- type - The ``Doctrine\DBAL\Types\Type`` name, preferably + "string" or "integer". + +Optional attributes: + + +- column - Name of the column in the database, defaults to the + field name. + +Using the simplified definition above Doctrine will use no +identifier strategy for this entity. That means you have to +manually set the identifier before calling +``EntityManager#persist($entity)``. This is the so called +``ASSIGNED`` strategy. + +If you want to switch the identifier generation strategy you have +to nest a ```` element inside the id-element. This of +course only works for surrogate keys. For composite keys you always +have to use the ``ASSIGNED`` strategy. + +:: + + [xml] + + + + + + +The following values are allowed for the ```` strategy +attribute: + + +- AUTO - Automatic detection of the identifier strategy based on + the preferred solution of the database vendor. +- IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs + available to Doctrine AFTER the INSERT statement has been executed. +- SEQUENCE - Use of a database sequence to retrieve the + entity-ids. This is possible before the INSERT statement is + executed. + +If you are using the SEQUENCE strategy you can define an additional +element to describe the sequence: + +:: + + [xml] + + + + + + + +Required attributes for ````: + + +- sequence-name - The name of the sequence + +Optional attributes for ````: + + +- allocation-size - By how much steps should the sequence be + incremented when a value is retrieved. Defaults to 1 +- initial-value - What should the initial value of the sequence + be. + + **NOTE** + + If you want to implement a cross-vendor compatible application you + have to specify and additionally define the + element, if Doctrine chooses the sequence strategy for a + platform. + + +Defining a Mapped Superclass +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes you want to define a class that multiple entities inherit +from, which itself is not an entity however. The chapter on +*Inheritance Mapping* describes a Mapped Superclass in detail. You +can define it in XML using the ```` tag. + +:: + + [xml] + + + + + + + +Required attributes: + + +- name - Class name of the mapped superclass. + +You can nest any number of ```` and unidirectional +```` or ```` associations inside a +mapped superclass. + +Defining Inheritance Mappings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are currently two inheritance persistence strategies that you +can choose from when defining entities that inherit from each +other. Single Table inheritance saves the fields of the complete +inheritance hierarchy in a single table, joined table inheritance +creates a table for each entity combining the fields using join +conditions. + +You can specify the inheritance type in the ```` element +and then use the ```` and +```` attributes. + +:: + + [xml] + + + + + + + + + +The allowed values for inheritance-type attribute are ``JOINED`` or +``SINGLE_TABLE``. + + **NOTE** + + All inheritance related definitions have to be defined on the root + entity of the hierarchy. + + +Defining Lifecycle Callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can define the lifecycle callback methods on your entities +using the ```` element: + +:: + + [xml] + + + + + + + +Defining One-To-One Relations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can define One-To-One Relations/Associations using the +```` element. The required and optional attributes +depend on the associations being on the inverse or owning side. + +For the inverse side the mapping is as simple as: + +:: + + [xml] + + + + +Required attributes for inverse One-To-One: + + +- field - Name of the property/field on the entity's PHP class. +- target-entity - Name of the entity associated entity class. If + this is not qualified the namespace of the current class is + prepended. *IMPORTANT:* No leading backslash! +- mapped-by - Name of the field on the owning side (here Address + entity) that contains the owning side association. + +For the owning side this mapping would look like: + +:: + + [xml] + + + + +Required attributes for owning One-to-One: + + +- field - Name of the property/field on the entity's PHP class. +- target-entity - Name of the entity associated entity class. If + this is not qualified the namespace of the current class is + prepended. *IMPORTANT:* No leading backslash! + +Optional attributes for owning One-to-One: + + +- inversed-by - If the association is bidirectional the + inversed-by attribute has to be specified with the name of the + field on the inverse entity that contains the back-reference. +- orphan-removal - If true, the inverse side entity is always + deleted when the owning side entity is. Defaults to false. +- fetch - Either LAZY or FETCH, defaults to LAZY. This attribute + makes only sense on the owning side, the inverse side *ALWAYS* has + to use the ``FETCH`` strategy. + +The definition for the owning side relies on a bunch of mapping +defaults for the join column names. Without the nested +```` element Doctrine assumes to foreign key to be +called ``user_id`` on the Address Entities table. This is because +the ``MyProject\Address`` entity is the owning side of this +association, which means it contains the foreign key. + +The completed explicitly defined mapping is: + +:: + + [xml] + + + + + + +Defining Many-To-One Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The many-to-one association is *ALWAYS* the owning side of any +bidirectional association. This simplifies the mapping compared to +the one-to-one case. The minimal mapping for this association looks +like: + +:: + + [xml] + + + + +Required attributes: + + +- field - Name of the property/field on the entity's PHP class. +- target-entity - Name of the entity associated entity class. If + this is not qualified the namespace of the current class is + prepended. *IMPORTANT:* No leading backslash! + +Optional attributes: + + +- inversed-by - If the association is bidirectional the + inversed-by attribute has to be specified with the name of the + field on the inverse entity that contains the back-reference. +- orphan-removal - If true the entity on the inverse side is + always deleted when the owning side entity is and it is not + connected to any other owning side entity anymore. Defaults to + false. +- fetch - Either LAZY or FETCH, defaults to LAZY. + +This definition relies on a bunch of mapping defaults with regards +to the naming of the join-column/foreign key. The explicitly +defined mapping includes a ```` tag nested inside +the many-to-one association tag: + +:: + + [xml] + + + + + + +The join-column attribute ``name`` specifies the column name of the +foreign key and the ``referenced-column-name`` attribute specifies +the name of the primary key column on the User entity. + +Defining One-To-Many Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The one-to-many association is *ALWAYS* the inverse side of any +association. There exists no such thing as a uni-directional +one-to-many association, which means this association only ever +exists for bi-directional associations. + +:: + + [xml] + + + + +Required attributes: + + +- field - Name of the property/field on the entity's PHP class. +- target-entity - Name of the entity associated entity class. If + this is not qualified the namespace of the current class is + prepended. *IMPORTANT:* No leading backslash! +- mapped-by - Name of the field on the owning side (here + Phonenumber entity) that contains the owning side association. + +Optional attributes: + + +- fetch - Either LAZY or FETCH, defaults to LAZY. + +Defining Many-To-Many Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +From all the associations the many-to-many has the most complex +definition. When you rely on the mapping defaults you can omit many +definitions and rely on their implicit values. + +:: + + [xml] + + + + +Required attributes: + + +- field - Name of the property/field on the entity's PHP class. +- target-entity - Name of the entity associated entity class. If + this is not qualified the namespace of the current class is + prepended. *IMPORTANT:* No leading backslash! + +Optional attributes: + + +- mapped-by - Name of the field on the owning side that contains + the owning side association if the defined many-to-many association + is on the inverse side. +- inversed-by - If the association is bidirectional the + inversed-by attribute has to be specified with the name of the + field on the inverse entity that contains the back-reference. +- fetch - Either LAZY or FETCH, defaults to LAZY. + +The mapping defaults would lead to a join-table with the name +"User\_Group" being created that contains two columns "user\_id" +and "group\_id". The explicit definition of this mapping would be: + +:: + + [xml] + + + + + + + + + + + + + +Here both the ```` and ```` +tags are necessary to tell Doctrine for which side the specified +join-columns apply. These are nested inside a ```` +attribute which allows to specify the table name of the +many-to-many join-table. + +Cascade Element +~~~~~~~~~~~~~~~ + +Doctrine allows cascading of several UnitOfWork operations to +related entities. You can specify the cascade operations in the +```` element inside any of the association mapping +tags. + +:: + + [xml] + + + + + + + + +Besides ```` the following operations can be +specified by their respective tags: + + +- ```` +- ```` +- ```` +- ```` + +Join Column Element +~~~~~~~~~~~~~~~~~~~ + +In any explicitly defined association mapping you will need the +```` tag. It defines how the foreign key and primary +key names are called that are used for joining two entities. + +Required attributes: + + +- name - The column name of the foreign key. +- referenced-column-name - The column name of the associated + entities primary key + +Optional attributes: + + +- unique - If the join column should contain a UNIQUE constraint. + This makes sense for Many-To-Many join-columns only to simulate a + one-to-many unidirectional using a join-table. +- nullable - should the join column be nullable, defaults to true. +- on-delete - Foreign Key Cascade action to perform when entity is + deleted, defaults to NO ACTION/RESTRICT but can be set to + "CASCADE". + +Defining Order of To-Many Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can require one-to-many or many-to-many associations to be +retrieved using an additional ``ORDER BY``. + +:: + + [xml] + + + + + + + + +Defining Indexes or Unique Constraints +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To define additional indexes or unique constraints on the entities +table you can use the ```` and +```` elements: + +:: + + [xml] + + + + + + + + + + + + +You have to specify the column and not the entity-class field names +in the index and unique-constraint definitions. + + diff --git a/manual/en/xml-mapping.txt b/manual/en/xml-mapping.txt index 086484406..2dd1806b7 100644 --- a/manual/en/xml-mapping.txt +++ b/manual/en/xml-mapping.txt @@ -23,18 +23,18 @@ The XML mapping document of a class is loaded on-demand the first time it is req - - [php] + setFileExtension('.xml'); It is recommended to put all XML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the XmlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: - [php] + setMetadataDriverImpl($driver); -++ Example +## Example As a quick start, here is a small example document that makes use of several common elements: @@ -105,13 +105,13 @@ As a quick start, here is a small example document that makes use of several com Be aware that class-names specified in the XML files should be fully qualified. -++ XML-Element Reference +## XML-Element Reference The XML-Element reference explains all the tags and attributes that the Doctrine Mapping XSD Schema defines. You should read the Basic-, Association- and Inheritance Mapping chapters to understand what each of this definitions means in detail. -+++ Defining an Entity +### Defining an Entity Each XML Mapping File contains the definition of one entity, specified as the `` element as a direct child of the `` element: @@ -133,7 +133,7 @@ Optional attributes: * repository-class - The fully qualified class-name of an alternative `Doctrine\ORM\EntityRepository` implementation to be used with this entity. * inheritance-type - The type of inheritance, defaults to none. A more detailed description follows in the *Defining Inheritance Mappings* section. -+++ Defining Fields +### Defining Fields Each entity class can contain zero to infinite fields that are managed by Doctrine. You can define them using the `` element as a children to the `` element. The field element is only @@ -167,7 +167,7 @@ Optional attributes: field-name and has to specify the complete column definition. Using this feature will turn this field dirty for Schema-Tool update commands at all times. -+++ Defining Identity and Generator Strategies +### Defining Identity and Generator Strategies An entity has to have at least one `` element. For composite keys you can specify more than one id-element, however surrogate keys are recommended for use with Doctrine 2. The Id field allows to define properties of @@ -232,7 +232,7 @@ Optional attributes for ``: > If you want to implement a cross-vendor compatible application you have to specify and > additionally define the element, if Doctrine chooses the sequence strategy for a platform. -+++ Defining a Mapped Superclass +### Defining a Mapped Superclass Sometimes you want to define a class that multiple entities inherit from, which itself is not an entity however. The chapter on *Inheritance Mapping* describes a Mapped Superclass in detail. You can define it in XML using @@ -253,7 +253,7 @@ Required attributes: You can nest any number of `` and unidirectional `` or `` associations inside a mapped superclass. -+++ Defining Inheritance Mappings +### Defining Inheritance Mappings There are currently two inheritance persistence strategies that you can choose from when defining entities that inherit from each other. Single Table inheritance saves the fields of the complete inheritance hierarchy in a single table, @@ -278,7 +278,7 @@ The allowed values for inheritance-type attribute are `JOINED` or `SINGLE_TABLE` > > All inheritance related definitions have to be defined on the root entity of the hierarchy. -+++ Defining Lifecycle Callbacks +### Defining Lifecycle Callbacks You can define the lifecycle callback methods on your entities using the `` element: @@ -290,7 +290,7 @@ You can define the lifecycle callback methods on your entities using the ` -+++ Defining One-To-One Relations +### Defining One-To-One Relations You can define One-To-One Relations/Associations using the `` element. The required and optional attributes depend on the associations being on the inverse or owning side. @@ -340,7 +340,7 @@ The completed explicitly defined mapping is: -+++ Defining Many-To-One Associations +### Defining Many-To-One Associations The many-to-one association is *ALWAYS* the owning side of any bidirectional association. This simplifies the mapping compared to the one-to-one case. The minimal mapping for this association looks like: @@ -375,7 +375,7 @@ The join-column attribute `name` specifies the column name of the foreign key an the `referenced-column-name` attribute specifies the name of the primary key column on the User entity. -+++ Defining One-To-Many Associations +### Defining One-To-Many Associations The one-to-many association is *ALWAYS* the inverse side of any association. There exists no such thing as a uni-directional one-to-many association, which means this association only ever exists for bi-directional associations. @@ -395,7 +395,7 @@ Optional attributes: * fetch - Either LAZY or FETCH, defaults to LAZY. -+++ Defining Many-To-Many Associations +### Defining Many-To-Many Associations From all the associations the many-to-many has the most complex definition. When you rely on the mapping defaults you can omit many definitions and rely on their implicit values. @@ -437,7 +437,7 @@ Here both the `` and `` tags are necessary t specified join-columns apply. These are nested inside a `` attribute which allows to specify the table name of the many-to-many join-table. -+++ Cascade Element +### Cascade Element Doctrine allows cascading of several UnitOfWork operations to related entities. You can specify the cascade operations in the `` element inside any of the association mapping tags. @@ -458,7 +458,7 @@ Besides `` the following operations can be specified by their res * `` * `` -+++ Join Column Element +### Join Column Element In any explicitly defined association mapping you will need the `` tag. It defines how the foreign key and primary key names are called that are used for joining two entities. @@ -474,7 +474,7 @@ Optional attributes: * nullable - should the join column be nullable, defaults to true. * on-delete - Foreign Key Cascade action to perform when entity is deleted, defaults to NO ACTION/RESTRICT but can be set to "CASCADE". -+++ Defining Order of To-Many Associations +### Defining Order of To-Many Associations You can require one-to-many or many-to-many associations to be retrieved using an additional `ORDER BY`. @@ -487,7 +487,7 @@ You can require one-to-many or many-to-many associations to be retrieved using a -+++ Defining Indexes or Unique Constraints +### Defining Indexes or Unique Constraints To define additional indexes or unique constraints on the entities table you can use the `` and `` elements: diff --git a/manual/en/yaml-mapping.rst b/manual/en/yaml-mapping.rst new file mode 100644 index 000000000..54245c5c2 --- /dev/null +++ b/manual/en/yaml-mapping.rst @@ -0,0 +1,90 @@ +The YAML mapping driver enables you to provide the ORM metadata in +form of YAML documents. + +The YAML mapping document of a class is loaded on-demand the first +time it is requested and subsequently stored in the metadata cache. +In order to work, this requires certain conventions: + + +- Each entity/mapped superclass must get its own dedicated YAML + mapping document. +- The name of the mapping document must consist of the fully + qualified name of the class, where namespace separators are + replaced by dots (.). +- All mapping documents should get the extension ".dcm.yml" to + identify it as a Doctrine mapping file. This is more of a + convention and you are not forced to do this. You can change the + file extension easily enough. + +- + +:: + + setFileExtension('.yml'); + +It is recommended to put all YAML mapping documents in a single +folder but you can spread the documents over several folders if you +want to. In order to tell the YamlDriver where to look for your +mapping documents, supply an array of paths as the first argument +of the constructor, like this: + +:: + + setMetadataDriverImpl($driver); + +Example +------- + +As a quick start, here is a small example document that makes use +of several common elements: + +:: + + [yml] + # Doctrine.Tests.ORM.Mapping.User.dcm.yml + Doctrine\Tests\ORM\Mapping\User: + type: entity + table: cms_users + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string + length: 50 + oneToOne: + address: + targetEntity: Address + joinColumn: + name: address_id + referencedColumnName: id + oneToMany: + phonenumbers: + targetEntity: Phonenumber + mappedBy: user + cascade: cascadePersist + manyToMany: + groups: + targetEntity: Group + joinTable: + name: cms_users_groups + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + group_id: + referencedColumnName: id + lifecycleCallbacks: + prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] + postPersist: [ doStuffOnPostPersist ] + +Be aware that class-names specified in the YAML files should be +fully qualified. + + diff --git a/manual/en/yaml-mapping.txt b/manual/en/yaml-mapping.txt index a5c6b29c7..e043eb121 100644 --- a/manual/en/yaml-mapping.txt +++ b/manual/en/yaml-mapping.txt @@ -8,18 +8,18 @@ The YAML mapping document of a class is loaded on-demand the first time it is re - - [php] + setFileExtension('.yml'); It is recommended to put all YAML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the YamlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: - [php] + setMetadataDriverImpl($driver); -++ Example +## Example As a quick start, here is a small example document that makes use of several common elements: From aa25b7cc0aad7fc98516e7222b809185481ff412 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 1 Nov 2010 21:21:01 +0100 Subject: [PATCH 129/430] Add Sphinx Configs --- cookbook/en/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cookbook/en/conf.py b/cookbook/en/conf.py index 58e36054e..18997a35d 100644 --- a/cookbook/en/conf.py +++ b/cookbook/en/conf.py @@ -51,7 +51,7 @@ release = '2.0.0-BETA4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -83,6 +83,8 @@ exclude_trees = ['_build'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' +highlight_language = 'php' + # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] From e7e1f62f72fc5f4fea30476784f1effaf2326753 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 1 Nov 2010 21:21:07 +0100 Subject: [PATCH 130/430] Add Sphinx Configs --- manual/en/Makefile | 89 ++++++++++++++++++++ manual/en/conf.py | 196 ++++++++++++++++++++++++++++++++++++++++++++ manual/en/index.rst | 20 +++++ manual/en/make.bat | 113 +++++++++++++++++++++++++ 4 files changed, 418 insertions(+) create mode 100644 manual/en/Makefile create mode 100644 manual/en/conf.py create mode 100644 manual/en/index.rst create mode 100644 manual/en/make.bat diff --git a/manual/en/Makefile b/manual/en/Makefile new file mode 100644 index 000000000..a6f6fce62 --- /dev/null +++ b/manual/en/Makefile @@ -0,0 +1,89 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORM.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORM.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/manual/en/conf.py b/manual/en/conf.py new file mode 100644 index 000000000..b972b640f --- /dev/null +++ b/manual/en/conf.py @@ -0,0 +1,196 @@ +# -*- coding: utf-8 -*- +# +# Doctrine 2 ORM documentation build configuration file, created by +# sphinx-quickstart on Mon Nov 1 21:19:39 2010. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Doctrine 2 ORM' +copyright = u'2010, Roman Borschel, Guilherme Blanco, Benjamin Eberlei, Jonathan Wage' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '2.0' +# The full version, including alpha/beta/rc tags. +release = '2.0.0-BETA4' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +language = 'en' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +highlight_language = 'php' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Doctrine2ORMdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Doctrine2ORM.tex', u'Doctrine 2 ORM Documentation', + u'Roman Borschel, Guilherme Blanco, Benjamin Eberlei, Jonathan Wage', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/manual/en/index.rst b/manual/en/index.rst new file mode 100644 index 000000000..af4ddee9a --- /dev/null +++ b/manual/en/index.rst @@ -0,0 +1,20 @@ +.. Doctrine 2 ORM documentation master file, created by + sphinx-quickstart on Mon Nov 1 21:19:39 2010. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Doctrine 2 ORM's documentation! +========================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/manual/en/make.bat b/manual/en/make.bat new file mode 100644 index 000000000..53c40c912 --- /dev/null +++ b/manual/en/make.bat @@ -0,0 +1,113 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +set SPHINXBUILD=sphinx-build +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end From a5a0dfa96e3ac01ac0794a05f05c52d5b3190792 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 1 Nov 2010 22:03:50 +0100 Subject: [PATCH 131/430] Converted ORM Docs into ReST --- generate-docs.sh | 2 +- manual/en/annotations-reference.rst | 93 +- manual/en/annotations-reference.txt | 611 ----------- manual/en/architecture.rst | 3 + manual/en/architecture.txt | 82 -- manual/en/association-mapping.rst | 60 +- manual/en/association-mapping.txt | 756 -------------- manual/en/basic-mapping.rst | 29 +- manual/en/basic-mapping.txt | 321 ------ manual/en/batch-processing.rst | 7 +- manual/en/batch-processing.txt | 122 --- manual/en/best-practices.rst | 11 +- manual/en/best-practices.txt | 77 -- manual/en/caching.rst | 3 + manual/en/caching.txt | 350 ------- manual/en/change-tracking-policies.rst | 2 +- manual/en/change-tracking-policies.txt | 128 --- manual/en/configuration.rst | 17 +- manual/en/configuration.txt | 307 ------ manual/en/dql-doctrine-query-language.rst | 47 +- manual/en/dql-doctrine-query-language.txt | 1096 -------------------- manual/en/events.rst | 19 +- manual/en/events.txt | 467 --------- manual/en/improving-performance.rst | 7 +- manual/en/improving-performance.txt | 25 - manual/en/index.rst | 27 + manual/en/inheritance-mapping.rst | 12 +- manual/en/inheritance-mapping.txt | 159 --- manual/en/introduction.txt | 309 ------ manual/en/limitations-and-known-issues.rst | 12 +- manual/en/limitations-and-known-issues.txt | 203 ---- manual/en/metadata-drivers.rst | 3 + manual/en/metadata-drivers.txt | 155 --- manual/en/native-sql.rst | 7 +- manual/en/native-sql.txt | 242 ----- manual/en/partial-objects.rst | 7 +- manual/en/partial-objects.txt | 49 - manual/en/php-mapping.rst | 3 + manual/en/php-mapping.txt | 199 ---- manual/en/query-builder.rst | 2 +- manual/en/query-builder.txt | 408 -------- manual/en/tools.rst | 15 +- manual/en/tools.txt | 254 ----- manual/en/transactions-and-concurrency.rst | 3 + manual/en/transactions-and-concurrency.txt | 249 ----- manual/en/working-with-associations.rst | 10 +- manual/en/working-with-associations.txt | 417 -------- manual/en/working-with-objects.rst | 27 +- manual/en/working-with-objects.txt | 549 ---------- manual/en/xml-mapping.rst | 65 +- manual/en/xml-mapping.txt | 509 --------- manual/en/yaml-mapping.rst | 6 +- manual/en/yaml-mapping.txt | 66 -- 53 files changed, 317 insertions(+), 8292 deletions(-) delete mode 100644 manual/en/annotations-reference.txt delete mode 100644 manual/en/architecture.txt delete mode 100644 manual/en/association-mapping.txt delete mode 100644 manual/en/basic-mapping.txt delete mode 100644 manual/en/batch-processing.txt delete mode 100644 manual/en/best-practices.txt delete mode 100644 manual/en/caching.txt delete mode 100644 manual/en/change-tracking-policies.txt delete mode 100644 manual/en/configuration.txt delete mode 100644 manual/en/dql-doctrine-query-language.txt delete mode 100644 manual/en/events.txt delete mode 100644 manual/en/improving-performance.txt delete mode 100644 manual/en/inheritance-mapping.txt delete mode 100644 manual/en/introduction.txt delete mode 100644 manual/en/limitations-and-known-issues.txt delete mode 100644 manual/en/metadata-drivers.txt delete mode 100644 manual/en/native-sql.txt delete mode 100644 manual/en/partial-objects.txt delete mode 100644 manual/en/php-mapping.txt delete mode 100644 manual/en/query-builder.txt delete mode 100644 manual/en/tools.txt delete mode 100644 manual/en/transactions-and-concurrency.txt delete mode 100644 manual/en/working-with-associations.txt delete mode 100644 manual/en/working-with-objects.txt delete mode 100644 manual/en/xml-mapping.txt delete mode 100644 manual/en/yaml-mapping.txt diff --git a/generate-docs.sh b/generate-docs.sh index 14fdb721b..07aaf048f 100755 --- a/generate-docs.sh +++ b/generate-docs.sh @@ -1,2 +1,2 @@ #!/bin/bash -sphinx-build reference/en /var/www/docs \ No newline at end of file +sphinx-build manual/en /var/www/docs diff --git a/manual/en/annotations-reference.rst b/manual/en/annotations-reference.rst index d7515c38a..3b5cefddd 100644 --- a/manual/en/annotations-reference.rst +++ b/manual/en/annotations-reference.rst @@ -1,3 +1,6 @@ +Annotations Reference +===================== + In this chapter a reference of every Doctrine 2 Annotation is given with short explanations on their context and usage. @@ -38,7 +41,8 @@ Index Reference --------- -### @Column +@Column +~~~~~~~ Marks an annotated instance variable as "persistent". It has to be inside the instance variables PHP DocBlock comment. Any value hold @@ -97,7 +101,8 @@ Examples: */ protected $height; -### @ChangeTrackingPolicy +@ChangeTrackingPolicy +~~~~~~~~~~~~~~~~~~~~~ The Change Tracking Policy annotation allows to specify how the Doctrine 2 UnitOfWork should detect changes in properties of @@ -125,7 +130,8 @@ Example: */ class User {} -### @DiscrimnatorColumn +@DiscrimnatorColumn +~~~~~~~~~~~~~~~~~~~~~ This annotation is a required annotation for the topmost/super class of an inheritance hierarchy. It specifies the details of the @@ -144,7 +150,8 @@ Optional attributes: - type - By default this is string. - length - By default this is 255. -### @DiscriminatorMap +@DiscriminatorMap +~~~~~~~~~~~~~~~~~~~~~ The discriminator map is a required annotation on the top-most/super class in an inheritance hierarchy. It takes an array @@ -167,7 +174,8 @@ depending if the classes are in the namespace or not. // ... } -### @Entity +@Entity +~~~~~~~ Required annotation to mark a PHP class as Entity. Doctrine manages the persistence of all classes marked as entity. @@ -193,7 +201,8 @@ Example: //... } -### @GeneratedValue +@GeneratedValue +~~~~~~~~~~~~~~~~~~~~~ Specifies which strategy is used for identifier generation for an instance variable which is annotated by `@Id <#ann_id>`_. This @@ -221,7 +230,8 @@ Example: */ protected $id = null; -### @HasLifecycleCallbacks +@HasLifecycleCallbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Annotation which has to be set on the entity-class PHP DocBlock to notify Doctrine that this entity has entity life-cycle callback @@ -247,7 +257,8 @@ Example: public function sendOptinMail() {} } -### @Index +@Index +~~~~~~~ Annotation is used inside the `@Table <#ann_table>`_ annotation on the entity-class level. It allows to hint the SchemaTool to @@ -273,7 +284,8 @@ Example: { } -### @Id +@Id +~~~~~~~ The annotated instance variable will be marked as entity identifier, the primary key in the database. This annotation is a @@ -292,7 +304,8 @@ Example: */ protected $id = null; -### @InheritanceType +@InheritanceType +~~~~~~~~~~~~~~~~~~~~~ In an inheritance hierarchy you have to use this annotation on the topmost/super class to define which strategy should be used for @@ -330,7 +343,8 @@ Examples: // ... } -### @JoinColumn +@JoinColumn +~~~~~~~~~~~~~~ This annotation is used in the context of relations in `@ManyToOne <#ann_manytoone>`_, `@OneToOne <#ann_onetoone>`_ fields @@ -379,13 +393,15 @@ Example: */ private $customer; -### @JoinColumns +@JoinColumns +~~~~~~~~~~~~~~ An array of @JoinColumn annotations for a `@ManyToOne <#ann_manytoone>`_ or `@OneToOne <#ann_onetoone>`_ relation with an entity that has multiple identifiers. -### @JoinTable +@JoinTable +~~~~~~~~~~~~~~ Using `@OneToMany <#ann_onetomany>`_ or `@ManyToMany <#ann_manytomany>`_ on the owning side of the relation @@ -423,7 +439,8 @@ Example: */ public $phonenumbers; -### @ManyToOne +@ManyToOne +~~~~~~~~~~~~~~ Defines that the annotated instance variable holds a reference that describes a many-to-one relationship between two entities. @@ -453,7 +470,8 @@ Example: */ private $cart; -### @ManyToMany +@ManyToMany +~~~~~~~~~~~~~~ Defines an instance variable holds a many-to-many relationship between two entities. `@JoinTable <#ann_jointable>`_ is an @@ -508,7 +526,8 @@ Example: */ private $features; -### @MappedSuperclass +@MappedSuperclass +~~~~~~~~~~~~~~~~~~~~~ An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information for its subclasses, @@ -519,7 +538,8 @@ The @MappedSuperclass annotation cannot be used in conjunction with @Entity. See the Inheritance Mapping section for `more details on the restrictions of mapped superclasses `_. -### @OneToOne +@OneToOne +~~~~~~~~~~~~~~ The @OneToOne annotation works almost exactly as the `@ManyToOne <#ann_manytoone>`_ with one additional option that can @@ -556,7 +576,8 @@ Example: */ private $customer; -### @OneToMany +@OneToMany +~~~~~~~~~~~~~~ Required attributes: @@ -586,7 +607,8 @@ Example: */ public $phonenumbers; -### @OrderBy +@OrderBy +~~~~~~~~~~~~~~ Optional annotation that can be specified with a `@ManyToMany <#ann_manytomany>`_ or `@OneToMany <#ann_onetomany>`_ @@ -613,49 +635,57 @@ positional statement. Multiple Fields are separated by a comma (,). The referenced field names have to exist on the ``targetEntity`` class of the ``@ManyToMany`` or ``@OneToMany`` annotation. -### @PostLoad +@PostLoad +~~~~~~~~~~~~~~ Marks a method on the entity to be called as a @PostLoad event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -### @PostPersist +@PostPersist +~~~~~~~~~~~~~~ Marks a method on the entity to be called as a @PostPersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -### @PostRemove +@PostRemove +~~~~~~~~~~~~~~ Marks a method on the entity to be called as a @PostRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -### @PostUpdate +@PostUpdate +~~~~~~~~~~~~~~ Marks a method on the entity to be called as a @PostUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -### @PrePersist +@PrePersist +~~~~~~~~~~~~~~ Marks a method on the entity to be called as a @PrePersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -### @PreRemove +@PreRemove +~~~~~~~~~~~~~~ Marks a method on the entity to be called as a @PreRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -### @PreUpdate +@PreUpdate +~~~~~~~~~~~~~~ Marks a method on the entity to be called as a @PreUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. -### @SequenceGenerator +@SequenceGenerator +~~~~~~~~~~~~~~~~~~~~~ For the use with @generatedValue(strategy="SEQUENCE") this annotation allows to specify details about the sequence, such as @@ -688,7 +718,8 @@ Example: */ protected $id = null; -### @Table +@Table +~~~~~~~ Annotation describes the table an entity is persisted in. It is placed on the entity-class PHP DocBlock and is optional. If it is @@ -721,7 +752,8 @@ Example: */ class User { } -### @UniqueConstraint +@UniqueConstraint +~~~~~~~~~~~~~~~~~~~~~ Annotation is used inside the `@Table <#ann_table>`_ annotation on the entity-class level. It allows to hint the SchemaTool to @@ -748,7 +780,8 @@ Example: { } -### @Version +@Version +~~~~~~~~~~~~~~ Marker annotation that defines a specified column as version attribute used in an optimistic locking scenario. It only works on diff --git a/manual/en/annotations-reference.txt b/manual/en/annotations-reference.txt deleted file mode 100644 index a95362e9e..000000000 --- a/manual/en/annotations-reference.txt +++ /dev/null @@ -1,611 +0,0 @@ -In this chapter a reference of every Doctrine 2 Annotation is given with short explanations on their context and usage. - -## Index - -* [@Column](#ann_column) -* [@ChangeTrackingPolicy](#ann_changetrackingpolicy) -* [@DiscriminatorColumn](#ann_discriminatorcolumn) -* [@DiscriminatorMap](#ann_discriminatormap) -* [@Entity](#ann_entity) -* [@GeneratedValue](#ann_generatedvalue) -* [@HasLifecycleCallbacks](#ann_haslifecyclecallbacks) -* [@Index](#ann_indexes) -* [@Id](#ann_id) -* [@InheritanceType](#ann_inheritancetype) -* [@JoinColumn](#ann_joincolumn) -* [@JoinTable](#ann_jointable) -* [@ManyToOne](#ann_manytoone) -* [@ManyToMany](#ann_manytomany) -* [@MappedSuperclass](#ann_mappedsuperclass) -* [@OneToOne](#ann_onetoone) -* [@OneToMany](#ann_onetomany) -* [@OrderBy](#ann_orderby) -* [@PostLoad](#ann_postload) -* [@PostPersist](#ann_postpersist) -* [@PostRemove](#ann_postremove) -* [@PostUpdate](#ann_postupdate) -* [@PrePersist](#ann_prepersist) -* [@PreRemove](#ann_preremove) -* [@PreUpdate](#ann_preupdate) -* [@SequenceGenerator](#ann_sequencegenerator) -* [@Table](#ann_table) -* [@UniqueConstraint](#ann_uniqueconstraint) -* [@Version](#ann_version) - -## Reference - - -### @Column - -Marks an annotated instance variable as "persistent". It has to be inside the instance variables PHP DocBlock comment. -Any value hold inside this variable will be saved to and loaded from the database as part of the lifecycle of the instance variables entity-class. - -Required attributes: - -* type - Name of the Doctrine Type which is converted between PHP and Database representation. - -Optional attributes: - -* name - By default the property name is used for the database column name also, however the 'name' attribute allows you to determine the column name. -* length - Used by the "string" type to determine its maximum length in the database. Doctrine does not validate the length of a string values for you. -* precision - The precision for a decimal (exact numeric) column (Applies only for decimal column) -* scale - The scale for a decimal (exact numeric) column (Applies only for decimal column) -* unique - Boolean value to determine if the value of the column should be unique across all rows of the underlying entities table. -* nullable - Determines if NULL values allowed for this column. -* columnDefinition - DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. However you should make careful use of this feature and the consequences. Additionally you should remember that the "type" attribute still handles the conversion between PHP and Database values. If you use this attribute on a column that is used for joins between tables you should also take a look at [@JoinColumn](#ann_joincolumn). - -Examples: - - -### @ChangeTrackingPolicy - -The Change Tracking Policy annotation allows to specify how the Doctrine 2 UnitOfWork should detect changes -in properties of entities during flush. By default each entity is checked according to a deferred implicit -strategy, which means upon flush UnitOfWork compares all the properties of an entity to a previously stored -snapshot. This works out of the box, however you might want to tweak the flush performance where using -another change tracking policy is an interesting option. - -The [details on all the available change tracking policies](/../configuration#change-tracking-policies) -can be found in the configuration section. - -Example: - - -### @DiscrimnatorColumn - -This annotation is a required annotation for the topmost/super class of an inheritance hierarchy. It specifies -the details of the column which saves the name of the class, which the entity is actually instantiated as. - -Required attributes: - -* name - The column name of the discriminator. This name is also used during Array hydration as key to specify the class-name. - -Optional attributes: - -* type - By default this is string. -* length - By default this is 255. - - -### @DiscriminatorMap - -The discriminator map is a required annotation on the top-most/super class in an inheritance hierarchy. It takes -an array as only argument which defines which class should be saved under which name in the database. Keys -are the database value and values are the classes, either as fully- or as unqualified class names depending -if the classes are in the namespace or not. - - -### @Entity - -Required annotation to mark a PHP class as Entity. Doctrine manages the persistence of all classes marked as entity. - -Optional attributes: - -* repositoryClass - Specifies the FQCN of a subclass of the Doctrine\ORM\EntityRepository. Use of repositories for entities is encouraged to keep specialized DQL and SQL operations separated from the Model/Domain Layer. - -Example: - - -### @GeneratedValue - -Specifies which strategy is used for identifier generation for an instance variable which is annotated by [@Id](#ann_id). -This annotation is optional and only has meaning when used in conjunction with @Id. - -If this annotation is not specified with @Id the NONE strategy is used as default. - -Required attributes: - -* strategy - Set the name of the identifier generation strategy. Valid values are AUTO, SEQUENCE, TABLE, IDENTITY and NONE. - -Example: - - -### @HasLifecycleCallbacks - -Annotation which has to be set on the entity-class PHP DocBlock to notify Doctrine that this entity has entity life-cycle -callback annotations set on at least one of its methods. Using @PostLoad, @PrePersist, @PostPersist, @PreRemove, @PostRemove, -@PreUpdate or @PostUpdate without this marker annotation will make Doctrine ignore the callbacks. - -Example: - - -### @Index - -Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the -SchemaTool to generate a database index on the specified table columns. It only has meaning in the SchemaTool -schema generation context. - -Required attributes: - -* name - Name of the Index -* columns - Array of columns. - -Example: - - -### @Id - -The annotated instance variable will be marked as entity identifier, the primary key in the database. -This annotation is a marker only and has no required or optional attributes. For entities that have multiple -identifier columns each column has to be marked with @Id. - -Example: - - -### @InheritanceType - -In an inheritance hierarchy you have to use this annotation on the topmost/super class to define which -strategy should be used for inheritance. Currently Single Table and Class Table Inheritance are supported. - -This annotation has always been used in conjunction with the [@DiscriminatorMap](#ann_discriminatormap) and -[@DiscriminatorColumn](#ann_discriminatorcolumn) annotations. - -Examples: - - -### @JoinColumn - -This annotation is used in the context of relations in [@ManyToOne](#ann_manytoone), [@OneToOne](#ann_onetoone) fields -and in the Context of [@JoinTable](#ann_jointable) nested inside a @ManyToMany. This annotation is not required. -If its not specified the attributes *name* and *referencedColumnName* are inferred from the table and primary key names. - -Required attributes: - -* name - Column name that holds the foreign key identifier for this relation. In the context of @JoinTable it specifies the column name in the join table. -* referencedColumnName - Name of the primary key identifier that is used for joining of this relation. - -Optional attributes: - -* unique - Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false. -* nullable - Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true. -* onDelete - Cascade Action (Database-level) -* onUpdate - Cascade Action (Database-level) -* columnDefinition - DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on @JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a "columnDefinition" attribute on [@Column](#ann_column) also sets the related @JoinColumn's columnDefinition. This is necessary to make foreign keys work. - -Example: - - -### @JoinColumns - -An array of @JoinColumn annotations for a [@ManyToOne](#ann_manytoone) or [@OneToOne](#ann_onetoone) relation -with an entity that has multiple identifiers. - - -### @JoinTable - -Using [@OneToMany](#ann_onetomany) or [@ManyToMany](#ann_manytomany) on the owning side of the relation requires to specify -the @JoinTable annotation which describes the details of the database join table. If you do not specify @JoinTable on -these relations reasonable mapping defaults apply using the affected table and the column names. - -Required attributes: - -* name - Database name of the join-table -* joinColumns - An array of @JoinColumn annotations describing the join-relation between the owning entities table and the join table. -* inverseJoinColumns - An array of @JoinColumn annotations describing the join-relation between the inverse entities table and the join table. - -Optional attributes: - -* schema - Database schema name of this table. - -Example: - - -### @ManyToOne - -Defines that the annotated instance variable holds a reference that describes a many-to-one relationship between two entities. - -Required attributes: - -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! - -Optional attributes: - -* cascade - Cascade Option -* fetch - One of LAZY or EAGER -* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. - -Example: - - -### @ManyToMany - -Defines an instance variable holds a many-to-many relationship between two entities. [@JoinTable](#ann_jointable) -is an additional, optional annotation that has reasonable default configuration values using the table -and names of the two related entities. - -Required attributes: - -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! - -Optional attributes: - -* mappedBy - This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship. -* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. -* cascade - Cascade Option -* fetch - One of LAZY or EAGER - -> **NOTE** -> For ManyToMany bidirectional relationships either side may be the owning side (the side -> that defines the @JoinTable and/or does not make use of the mappedBy attribute, thus -> using a default join table). - -Example: - - -### @MappedSuperclass - -An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information -for its subclasses, but which is not itself an entity. This annotation is specified on the Class docblock -and has no additional attributes. - -The @MappedSuperclass annotation cannot be used in conjunction with @Entity. See the Inheritance Mapping -section for [more details on the restrictions of mapped superclasses](/../inheritance-mapping#mapped-superclasses). - - -### @OneToOne - -The @OneToOne annotation works almost exactly as the [@ManyToOne](#ann_manytoone) with one additional option -that can be specified. The configuration defaults for [@JoinColumn](#ann_joincolumn) using the target entity table and primary key column names -apply here too. - -Required attributes: - -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! - -Optional attributes: - -* cascade - Cascade Option -* fetch - One of LAZY or EAGER -* orphanRemoval - Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any -owning instance, should be removed by Doctrine. Defaults to false. -* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. - -Example: - - -### @OneToMany - -Required attributes: - -* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! - -Optional attributes: - -* cascade - Cascade Option -* orphanRemoval - Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any -owning instance, should be removed by Doctrine. Defaults to false. -* mappedBy - This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship. - -Example: - - -### @OrderBy - -Optional annotation that can be specified with a [@ManyToMany](#ann_manytomany) or [@OneToMany](#ann_onetomany) -annotation to specify by which criteria the collection should be retrieved from the database by using an ORDER BY -clause. - -This annotation requires a single non-attributed value with an DQL snippet: - -Example: - - -### @PostLoad - -Marks a method on the entity to be called as a @PostLoad event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -### @PostPersist - -Marks a method on the entity to be called as a @PostPersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -### @PostRemove - -Marks a method on the entity to be called as a @PostRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -### @PostUpdate - -Marks a method on the entity to be called as a @PostUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -### @PrePersist - -Marks a method on the entity to be called as a @PrePersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -### @PreRemove - -Marks a method on the entity to be called as a @PreRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -### @PreUpdate - -Marks a method on the entity to be called as a @PreUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock. - - -### @SequenceGenerator - -For the use with @generatedValue(strategy="SEQUENCE") this annotation allows to specify details about the sequence, -such as the increment size and initial values of the sequence. - -Required attributes: - -* sequenceName - Name of the sequence - -Optional attributes: - -* allocationSize - Increment the sequence by the allocation size when its fetched. A value larger than 1 allows to optimize for scenarios where you create more than one new entity per request. Defaults to 10 -* initialValue - Where does the sequence start, defaults to 1. - -Example: - - -### @Table - -Annotation describes the table an entity is persisted in. It is placed on the entity-class PHP DocBlock and is optional. -If it is not specified the table name will default to the entities unqualified classname. - -Required attributes: - -* name - Name of the table - -Optional attributes: - -* schema - Database schema name of this table. -* indexes - Array of @Index annotations -* uniqueConstraints - Array of @UniqueConstraint annotations. - -Example: - - -### @UniqueConstraint - -Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the -SchemaTool to generate a database unique constraint on the specified table columns. It only has meaning in the SchemaTool -schema generation context. - -Required attributes: - -* name - Name of the Index -* columns - Array of columns. - -Example: - - -### @Version - -Marker annotation that defines a specified column as version attribute used in an optimistic locking scenario. -It only works on [@Column](#ann_column) annotations that have the type integer or datetime. - -Example: - - **TIP** -> The constructor of an entity is only ever invoked when *you* construct a new instance -> with the *new* keyword. Doctrine never calls entity constructors, thus you are free to use -> them as you wish and even have it require arguments of any type. - -### Entity states - -An entity instance can be characterized as being NEW, MANAGED, DETACHED or REMOVED. - -* A NEW entity instance has no persistent identity, and is not yet associated with an EntityManager and a UnitOfWork (i.e. those just created with the "new" operator). -* A MANAGED entity instance is an instance with a persistent identity that is associated with an EntityManager and whose persistence is thus managed. -* A DETACHED entity instance is an instance with a persistent identity that is not (or no longer) associated with an EntityManager and a UnitOfWork. -* A REMOVED entity instance is an instance with a persistent identity, associated with an EntityManager, that will be removed from the database upon transaction commit. - -### Persistent fields - -The persistent state of an entity is represented by instance variables. An -instance variable must be directly accessed only from within the methods of the -entity by the entity instance itself. Instance variables must not be accessed by -clients of the entity. The state of the entity is available to clients only through -the entity’s methods, i.e. accessor methods (getter/setter methods) or other -business methods. - -Collection-valued persistent fields and properties must be defined in terms of -the `Doctrine\Common\Collections\Collection` interface. The collection -implementation type may be used by the application to initialize fields or -properties before the entity is made persistent. Once the entity becomes -managed (or detached), subsequent access must be through the interface type. - -### Serializing entities - -Serializing entities can be problematic and is not really recommended, at least not as long as an -entity instance still holds references to proxy objects or is still managed by an EntityManager. -If you intend to serialize (and unserialize) entity instances that still hold references to proxy objects -you may run into problems with private properties because of technical limitations. -Proxy objects implement `__sleep` and it is not possible for `__sleep` to return names of -private properties in parent classes. On the other hand it is not a solution for proxy objects -to implement `Serializable` because Serializable does not work well with any potential cyclic -object references (at least we did not find a way yet, if you did, please contact us). - -## The EntityManager - -The `EntityManager` class is a central access point to the ORM functionality -provided by Doctrine 2. The `EntityManager` API is used to manage the persistence -of your objects and to query for persistent objects. - -### Transactional write-behind - -An `EntityManager` and the underlying `UnitOfWork` employ a strategy called -"transactional write-behind" that delays the execution of SQL statements in -order to execute them in the most efficient way and to execute them at the end -of a transaction so that all write locks are quickly released. You should see -Doctrine as a tool to synchronize your in-memory objects with the database in -well defined units of work. Work with your objects and modify them as usual and -when you're done call `EntityManager#flush()` to make your changes persistent. - -### The Unit of Work - -Internally an `EntityManager` uses a `UnitOfWork`, which is a typical -implementation of the [Unit of Work pattern](http://martinfowler.com/eaaCatalog/unitOfWork.html), to keep track of all the things that need to be done -the next time `flush` is invoked. You usually do not directly interact with -a `UnitOfWork` but with the `EntityManager` instead. \ No newline at end of file diff --git a/manual/en/association-mapping.rst b/manual/en/association-mapping.rst index de7b72790..6b1631905 100644 --- a/manual/en/association-mapping.rst +++ b/manual/en/association-mapping.rst @@ -1,3 +1,6 @@ +Association Mapping +=================== + This chapter explains how associations between entities are mapped with Doctrine. We start out with an explanation of the concept of owning and inverse sides which is important to understand when @@ -65,7 +68,9 @@ The owning side of a bidirectional association is the side Doctrine consequently whether there is anything to do to update the association in the database. - **NOTE** "Owning side" and "inverse side" are technical concepts of +.. note:: + + "Owning side" and "inverse side" are technical concepts of the ORM technology, not concepts of your domain model. What you consider as the owning side in your domain model can be different from what the owning side is for Doctrine. These are unrelated. @@ -96,7 +101,9 @@ and more importantly because you can not pass this collection to all the useful PHP array functions, which makes it very hard to work with. - **CAUTION** The Collection interface and ArrayCollection class, +.. warning:: + + The Collection interface and ArrayCollection class, like everything else in the Doctrine namespace, are neither part of the ORM, nor the DBAL, it is a plain PHP class that has no outside dependencies apart from dependencies on PHP itself (and the SPL). @@ -280,7 +287,7 @@ Or you can trigger the validation manually: If the mapping is invalid the errors array contains a positive number of elements with error messages. - **NOTE** +.. note:: One common error is to use a backlash in front of the fully-qualified class-name. Whenever a FQCN is represented inside a @@ -325,9 +332,8 @@ as the defaults would be the same. Generated MySQL Schema: -:: +.. code-block:: sql - [sql] CREATE TABLE Product ( id INT AUTO_INCREMENT NOT NULL, shipping_id INT DEFAULT NULL, @@ -381,9 +387,8 @@ as the defaults would be the same. Generated MySQL Schema: -:: +.. code-block:: sql - [sql] CREATE TABLE Cart ( id INT AUTO_INCREMENT NOT NULL, customer_id INT DEFAULT NULL, @@ -426,9 +431,8 @@ as the defaults would be the same. With the generated MySQL Schema: -:: +.. code-block:: sql - [sql] CREATE TABLE Student ( id INT AUTO_INCREMENT NOT NULL, mentor_id INT DEFAULT NULL, @@ -476,15 +480,16 @@ association: // ... } - **NOTE** One-To-Many uni-directional relations with join-table only +.. note:: + + One-To-Many uni-directional relations with join-table only work using the @ManyToMany annotation and a unique-constraint. Generates the following MySQL Schema: -:: +.. code-block:: sql - [sql] CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) @@ -532,16 +537,17 @@ with the following: // ... } - **TIP** The above ``@JoinColumn`` is optional as it would default +.. note:: + + The above ``@JoinColumn`` is optional as it would default to ``address_id`` and ``id`` anyways. You can omit it and let it use the defaults. Generated MySQL Schema: -:: +.. code-block:: sql - [sql] CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, address_id INT DEFAULT NULL, @@ -597,9 +603,8 @@ as the defaults would be the same. Generated MySQL Schema: -:: +.. code-block:: sql - [sql] CREATE TABLE Product ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) @@ -649,9 +654,8 @@ as the defaults would be the same. Generated MySQL Schema: -:: +.. code-block:: sql - [sql] CREATE TABLE Category ( id INT AUTO_INCREMENT NOT NULL, parent_id INT DEFAULT NULL, @@ -706,9 +710,8 @@ entities: Generated MySQL Schema: -:: +.. code-block:: sql - [sql] CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) @@ -868,9 +871,8 @@ field named ``$friendsWithMe`` and ``$myFriends``. Generated MySQL Schema: -:: +.. code-block:: sql - [sql] CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) @@ -940,30 +942,26 @@ ORDER BY, since g is not fetch joined: However the following: -:: +.. code-block:: sql - [sql] SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ...would internally be rewritten to: -:: +.. code-block:: sql - [sql] SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC You can't reverse the order with an explicit DQL ORDER BY: -:: +.. code-block:: sql - [sql] SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC ...is internally rewritten to: -:: +.. code-block:: sql - [sql] SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC diff --git a/manual/en/association-mapping.txt b/manual/en/association-mapping.txt deleted file mode 100644 index fb22869f2..000000000 --- a/manual/en/association-mapping.txt +++ /dev/null @@ -1,756 +0,0 @@ -This chapter explains how associations between entities are mapped with Doctrine. We start out with an explanation of the concept of owning and inverse sides which is important to understand when working with bidirectional associations. Please read these explanations carefully. - -## Owning Side and Inverse Side - -When mapping bidirectional associations it is important to understand the concept of the owning and inverse sides. The following general rules apply: - -* Relationships may be bidirectional or unidirectional. -* A bidirectional relationship has both an owning side and an inverse side. -* A unidirectional relationship only has an owning side. -* The owning side of a relationship determines the updates to the relationship in the database. - -The following rules apply to *bidirectional* associations: - -* The inverse side of a bidirectional relationship must refer to its owning side by use of the mappedBy attribute of the OneToOne, OneToMany, or ManyToMany mapping declaration. The mappedBy attribute designates the field in the entity that is the owner of the relationship. -* The owning side of a bidirectional relationship must refer to its inverse side by use of the inversedBy attribute of the OneToOne, ManyToOne, or ManyToMany mapping declaration. The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. -* The many side of OneToMany/ManyToOne bidirectional relationships *must* be the owning side, hence the mappedBy element can not be specified on the ManyToOne side. -* For OneToOne bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key (@JoinColumn(s)). -* For ManyToMany bidirectional relationships either side may be the owning side (the side that defines the @JoinTable and/or does not make use of the mappedBy attribute, thus using a default join table). - -Especially important is the following: - -**The owning side of a relationship determines the updates to the relationship in the database**. - -To fully understand this, remember how bidirectional associations are maintained -in the object world. There are 2 references on each side of the association -and these 2 references both represent the same association but can change -independently of one another. Of course, in a correct application the semantics -of the bidirectional association are properly maintained by the application -developer (that's his responsibility). Doctrine needs to know which of -these two in-memory references is the one that should be persisted and which -not. This is what the owning/inverse concept is mainly used for. - -**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)** - -The owning side of a bidirectional association is the side Doctrine "looks at" when determining -the state of the association, and consequently whether there is anything to do to update the association -in the database. - -> **NOTE** -> "Owning side" and "inverse side" are technical concepts of the ORM technology, not concepts -> of your domain model. What you consider as the owning side in your domain model can be different -> from what the owning side is for Doctrine. These are unrelated. - -## Collections - -In all the examples of many-valued associations in this manual we will make use of a `Collection` interface and a corresponding default implementation `ArrayCollection` that are defined in the `Doctrine\Common\Collections` namespace. Why do we need that? Doesn't that couple my domain model to Doctrine? Unfortunately, PHP arrays, while being great for many things, do not make up for good collections of business objects, especially not in the context of an ORM. The reason is that plain PHP arrays can not be transparently extended / instrumented in PHP code, which is necessary for a lot of advanced ORM features. The classes / interfaces that come closest to an OO collection are ArrayAccess and ArrayObject but until instances of these types can be used in all places where a plain array can be used (something that may happen in PHP6) their usability is fairly limited. You "can" type-hint on `ArrayAccess` instead of `Collection`, since the Collection interface extends `ArrayAccess`, but this will severely limit you in the way you can work with the collection, because the `ArrayAccess` API is (intentionally) very primitive and more importantly because you can not pass this collection to all the useful PHP array functions, which makes it very hard to work with. - -> **CAUTION** -> The Collection interface and ArrayCollection class, like everything else in the -> Doctrine\Common namespace, are neither part of the ORM, nor the DBAL, it is a plain PHP -> class that has no outside dependencies apart from dependencies on PHP itself (and the -> SPL). Therefore using this class in your domain classes and elsewhere does not introduce -> a coupling to the persistence layer. The Collection class, like everything else in the -> Common namespace, is not part of the persistence layer. You could even copy that class -> over to your project if you want to remove Doctrine from your project and all your -> domain classes will work the same as before. - -## Mapping Defaults - -Before we introduce all the association mappings in detail, you should note that the @JoinColumn and @JoinTable -definitions are usually optional and have sensible default values. -The defaults for a join column in a one-to-one/many-to-one association is as follows: - - name: "_id" - referencedColumnName: "id" - -As an example, consider this mapping: - - groups; - } - } - -With this code alone the `$groups` field only contains an instance of `Doctrine\Common\Collections\Collection` if the user is retrieved from -Doctrine, however not after you instantiated a fresh instance of the User. When your user entity is still new `$groups` will obviously be null. - -This is why we recommend to initialize all collection fields to an empty `ArrayCollection` in your entities constructor: - - groups = new ArrayCollection(); - } - - public function getGroups() - { - return $this->groups; - } - } - -Now the following code will be working even if the Entity hasn't been associated with an EntityManager yet: - - find('Group', $groupId); - $user = new User(); - $user->getGroups()->add($group); - -## Runtime vs Development Mapping Validation - -For performance reasons Doctrine 2 has to skip some of the necessary validation of association mappings. -You have to execute this validation in your development workflow to verify the associations are correctly -defined. - -You can either use the Doctrine Command Line Tool: - - doctrine orm:validate-schema - -Or you can trigger the validation manually: - - use Doctrine\ORM\Tools\SchemaValidator; - - $validator = new SchemaValidator($entityManager); - $errors = $validator->validateMapping(); - - if (count($errors) > 0) { - // Lots of errors! - echo implode("\n\n", $errors); - } - -If the mapping is invalid the errors array contains a positive number of elements with error messages. - -> **NOTE** -> -> One common error is to use a backlash in front of the fully-qualified class-name. Whenever a FQCN is represented -> inside a string (such as in your mapping definitions) you have to drop the prefix backslash. PHP does this with -> `get_class()` or Reflection methods for backwards compatibility reasons. - -## One-To-One, Unidirectional - -A unidirectional one-to-one association is very common. Here is an example of a `Product` that has one `Shipping` object associated to it. The `Shipping` side does not reference back to the `Product` so it is unidirectional. - - phonenumbers = new \Doctrine\Common\Collections\ArrayCollection(); - } - - // ... - } - - /** @Entity */ - class Phonenumber - { - // ... - } - -> **NOTE** -> One-To-Many uni-directional relations with join-table only work using the @ManyToMany annotation and a unique-constraint. - -Generates the following MySQL Schema: - - [sql] - CREATE TABLE User ( - id INT AUTO_INCREMENT NOT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - - CREATE TABLE users_phonenumbers ( - user_id INT NOT NULL, - phonenumber_id INT NOT NULL, - UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id), - PRIMARY KEY(user_id, phonenumber_id) - ) ENGINE = InnoDB; - - CREATE TABLE Phonenumber ( - id INT AUTO_INCREMENT NOT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - - ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id); - ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id); - -## Many-To-One, Unidirectional - -You can easily implement a many-to-one unidirectional association with the following: - - **TIP** -> The above `@JoinColumn` is optional as it would default to `address_id` and `id` -> anyways. You can omit it and let it use the defaults. - -Generated MySQL Schema: - - [sql] - CREATE TABLE User ( - id INT AUTO_INCREMENT NOT NULL, - address_id INT DEFAULT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - - CREATE TABLE Address ( - id INT AUTO_INCREMENT NOT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - - ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id); - -## One-To-Many, Bidirectional - -Bidirectional one-to-many associations are very common. The following code shows an example with a Product and a Feature class: - - features = new \Doctrine\Common\Collections\ArrayCollection(); - } - } - - /** @Entity */ - class Feature - { - // ... - /** - * @ManyToOne(targetEntity="Product", inversedBy="features") - * @JoinColumn(name="product_id", referencedColumnName="id") - */ - private $product; - // ... - } - -Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. - -Generated MySQL Schema: - - [sql] - CREATE TABLE Product ( - id INT AUTO_INCREMENT NOT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - CREATE TABLE Feature ( - id INT AUTO_INCREMENT NOT NULL, - product_id INT DEFAULT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id); - -## One-To-Many, Self-referencing - -You can also setup a one-to-many association that is self-referencing. In this example we -setup a hierarchy of `Category` objects by creating a self referencing relationship. -This effectively models a hierarchy of categories and from the database perspective is known as an adjacency list approach. - - children = new \Doctrine\Common\Collections\ArrayCollection(); - } - } - -Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. - -Generated MySQL Schema: - - [sql] - CREATE TABLE Category ( - id INT AUTO_INCREMENT NOT NULL, - parent_id INT DEFAULT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - ALTER TABLE Category ADD FOREIGN KEY (parent_id) REFERENCES Category(id); - -## Many-To-Many, Unidirectional - -Real many-to-many associations are less common. The following example shows a unidirectional association between User and Group entities: - - groups = new \Doctrine\Common\Collections\ArrayCollection(); - } - } - - /** @Entity */ - class Group - { - // ... - } - -> **NOTE** -> Why are many-to-many associations less common? Because frequently you want to associate -> additional attributes with an association, in which case you introduce an association -> class. Consequently, the direct many-to-many association disappears and is replaced -> by one-to-many/many-to-one associations between the 3 participating classes. - -Generated MySQL Schema: - - [sql] - CREATE TABLE User ( - id INT AUTO_INCREMENT NOT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - CREATE TABLE users_groups ( - user_id INT NOT NULL, - group_id INT NOT NULL, - PRIMARY KEY(user_id, group_id) - ) ENGINE = InnoDB; - CREATE TABLE Group ( - id INT AUTO_INCREMENT NOT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - ALTER TABLE users_groups ADD FOREIGN KEY (user_id) REFERENCES User(id); - ALTER TABLE users_groups ADD FOREIGN KEY (group_id) REFERENCES Group(id); - -## Many-To-Many, Bidirectional - -Here is a similar many-to-many relationship as above except this one is bidirectional. - - groups = new \Doctrine\Common\Collections\ArrayCollection(); - } - - // ... - } - - /** @Entity */ - class Group - { - // ... - /** - * @ManyToMany(targetEntity="User", mappedBy="groups") - */ - private $users; - - public function __construct() { - $this->users = new \Doctrine\Common\Collections\ArrayCollection(); - } - - // ... - } - -The MySQL schema is exactly the same as for the Many-To-Many uni-directional case above. - -### Picking Owning and Inverse Side - -For Many-To-Many associations you can chose which entity is the owning and which the inverse side. There is -a very simple semantic rule to decide which side is more suitable to be the owning side from a developers perspective. -You only have to ask yourself, which entity is responsible for the connection management and pick that as the owning side. - -Take an example of two entities `Article` and `Tag`. Whenever you want to connect an Article to a Tag and vice-versa, it is mostly -the Article that is responsible for this relation. Whenever you add a new article, you want to connect it with existing or new tags. -Your create Article form will probably support this notion and allow to specify the tags directly. This is why you should -pick the Article as owning side, as it makes the code more understandable: - - addArticle($this); // synchronously updating inverse side - $this->tags[] = $tag; - } - } - - class Tag - { - private $articles; - - public function addArticle(Article $article) - { - $this->articles[] = $article; - } - } - -This allows to group the tag adding on the `Article` side of the association: - - addTag($tagA); - $article->addTag($tagB); - -## Many-To-Many, Self-referencing - -You can even have a self-referencing many-to-many association. A common scenario is where a `User` has friends and the target entity of that relationship is a `User` so it is self referencing. In this example it is bidirectional so `User` has a field named `$friendsWithMe` and `$myFriends`. - - friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection(); - $this->myFriends = new \Doctrine\Common\Collections\ArrayCollection(); - } - - // ... - } - -Generated MySQL Schema: - - [sql] - CREATE TABLE User ( - id INT AUTO_INCREMENT NOT NULL, - PRIMARY KEY(id) - ) ENGINE = InnoDB; - CREATE TABLE friends ( - user_id INT NOT NULL, - friend_user_id INT NOT NULL, - PRIMARY KEY(user_id, friend_user_id) - ) ENGINE = InnoDB; - ALTER TABLE friends ADD FOREIGN KEY (user_id) REFERENCES User(id); - ALTER TABLE friends ADD FOREIGN KEY (friend_user_id) REFERENCES User(id); - -## Ordering To-Many Collections - -In many use-cases you will want to sort collections when they are retrieved from the database. -In userland you do this as long as you haven't initially saved an entity with its associations -into the database. To retrieve a sorted collection from the database you can use the -`@OrderBy` annotation with an collection that specifies an DQL snippet that is appended -to all queries with this collection. - -Additional to any `@OneToMany` or `@ManyToMany` annotation you can specify the `@OrderBy` -in the following way: - - 10 - -However the following: - - [sql] - SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 - -...would internally be rewritten to: - - [sql] - SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC - -You can't reverse the order with an explicit DQL ORDER BY: - - [sql] - SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC - -...is internally rewritten to: - - [sql] - SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC \ No newline at end of file diff --git a/manual/en/basic-mapping.rst b/manual/en/basic-mapping.rst index 7d8378f4e..506337092 100644 --- a/manual/en/basic-mapping.rst +++ b/manual/en/basic-mapping.rst @@ -1,3 +1,6 @@ +Basic Mapping +============= + This chapter explains the basic mapping of objects and properties. Mapping of associations will be covered in the next chapter "Association Mapping". @@ -17,7 +20,9 @@ This manual usually uses docblock annotations in all the examples that are spread throughout all chapters. There are dedicated chapters for XML and YAML mapping, respectively. - **NOTE** If you're wondering which mapping driver gives the best +.. note:: + + If you're wondering which mapping driver gives the best performance, the answer is: None. Once the metadata of a class has been read from the source (annotations, xml or yaml) it is stored in an instance of the ``Doctrine\ORM\Mapping\ClassMetadata`` class @@ -127,11 +132,14 @@ built-in mapping types: PHP double. *IMPORTANT*: Works only with locale settings that use decimal points as separator. - **NOTE** Doctrine Mapping Types are NOT SQL types and NOT PHP +.. note:: + + Doctrine Mapping Types are NOT SQL types and NOT PHP types! They are mapping types between 2 types. +.. warning:: - **CAUTION** Mapping types are *case-sensitive*. For example, using + Mapping types are *case-sensitive*. For example, using a DateTime column will NOT match the datetime type that ships with Doctrine 2! @@ -259,7 +267,9 @@ know about it. This can be achieved through the ``Doctrine\DBAL\Configuration#setCustomTypes(array $types)`` method. - **NOTE** ``Doctrine\ORM\Configuration`` is a subclass of +.. note:: + + ``Doctrine\ORM\Configuration`` is a subclass of ``Doctrine\DBAL\Configuration``, so the methods are available on your ORM Configuration instance as well. @@ -428,7 +438,9 @@ need to access the sequence once to generate the identifiers for INCREMENT BY value, otherwise you may get duplicate keys. - **TIP** It is possible to use strategy="AUTO" and at the same time +.. note:: + + It is possible to use strategy="AUTO" and at the same time specifying a @SequenceGenerator. In such a case, your custom sequence settings are used in the case where the preferred strategy of the underlying platform is SEQUENCE, such as for Oracle and @@ -467,11 +479,14 @@ backticks. Here is an example: Doctrine will then quote this column name in all SQL statements according to the used database platform. - **CAUTION** Identifier Quoting is not supported for join column +.. warning:: + + Identifier Quoting is not supported for join column names or discriminator column names. +.. warning:: - **CAUTION** Identifier Quoting is a feature that is mainly intended + Identifier Quoting is a feature that is mainly intended to support legacy database schemas. The use of reserved words and identifier quoting is generally discouraged. Identifier quoting should not be used to enable the use non-standard-characters such diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt deleted file mode 100644 index 15eeb81dc..000000000 --- a/manual/en/basic-mapping.txt +++ /dev/null @@ -1,321 +0,0 @@ -This chapter explains the basic mapping of objects and properties. Mapping of associations will be covered in the next chapter "Association Mapping". - -## Mapping Drivers - -Doctrine provides several different ways for specifying object-relational mapping metadata: - -* Docblock Annotations -* XML -* YAML - -This manual usually uses docblock annotations in all the examples that are spread throughout all chapters. There are dedicated chapters for XML and YAML mapping, respectively. - -> **NOTE** -> If you're wondering which mapping driver gives the best performance, the answer is: -> None. Once the metadata of a class has been read from the source (annotations, xml or -> yaml) it is stored in an instance of the `Doctrine\ORM\Mapping\ClassMetadata` class -> and these instances are stored in the metadata cache. Therefore at the end of the day -> all drivers perform equally well. If you're not using a metadata cache (not -> recommended!) then the XML driver might have a slight edge in performance due to the -> powerful native XML support in PHP. - -## Introduction to Docblock Annotations - -You've probably used docblock annotations in some form already, most likely to provide documentation metadata for a tool like `PHPDocumentor` (@author, @link, ...). Docblock annotations are a tool to embed metadata inside the documentation section which can then be processed by some tool. Doctrine 2 generalizes the concept of docblock annotations so that they can be used for any kind of metadata and so that it is easy to define new docblock annotations. In order to allow more involved annotation values and to reduce the chances of clashes with other docblock annotations, the Doctrine 2 docblock annotations feature an alternative syntax that is heavily inspired by the Annotation syntax introduced in Java 5. - -The implementation of these enhanced docblock annotations is located in the `Doctrine\Common\Annotations` namespace and therefore part of the Common package. Doctrine 2 docblock annotations support namespaces and nested annotations among other things. The Doctrine 2 ORM defines its own set of docblock annotations for supplying object-relational mapping metadata. - -> **NOTE** -> If you're not comfortable with the concept of docblock annotations, don't worry, as -> mentioned earlier Doctrine 2 provides XML and YAML alternatives and you could easily -> implement your own favourite mechanism for defining ORM metadata. - -## Persistent classes - -In order to mark a class for object-relational persistence it needs to be designated as an entity. This can be done through the `@Entity` marker annotation. - - **NOTE** -> Doctrine Mapping Types are NOT SQL types and NOT PHP types! They are mapping types -> between 2 types. - -> **CAUTION** -> Mapping types are *case-sensitive*. For example, using a DateTime column will NOT match the datetime type -> that ships with Doctrine 2! - -## Property Mapping - -After a class has been marked as an entity it can specify mappings for its instance fields. Here we will only look at simple fields that hold scalar values like strings, numbers, etc. Associations to other objects are covered in the chapter "Association Mapping". - -To mark a property for relational persistence the `@Column` docblock annotation is used. This annotation usually requires at least 1 attribute to be set, the `type`. The `type` attribute specifies the Doctrine Mapping Type to use for the field. If the type is not specified, 'string' is used as the default mapping type since it is the most flexible. - -Example: - - **NOTE** -> `Doctrine\ORM\Configuration` is a subclass of `Doctrine\DBAL\Configuration`, so the -> methods are available on your ORM Configuration instance as well. - -Here is an example: - - getConnection(); - $conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype'); - -Now using Schema-Tool, whenever it detects a column having the `db_mytype` it will convert it into a `mytype` -Doctrine Type instance for Schema representation. Keep in mind that you can easily produce clashes this way, -each database type can only map to exactly one Doctrine mapping type. - -## Identifiers / Primary Keys - -Every entity class needs an identifier/primary key. You designate the field that serves as the identifier with the `@Id` marker annotation. Here is an example: - - **CAUTION** -> The allocationSize is detected by SchemaTool and transformed into an "INCREMENT BY " clause -> in the CREATE SEQUENCE statement. For a database schema created manually (and not SchemaTool) you have to -> make sure that the allocationSize configuration option is never larger than the actual sequences INCREMENT BY value, -> otherwise you may get duplicate keys. - -> **TIP** -> It is possible to use strategy="AUTO" and at the same time specifying a @SequenceGenerator. -> In such a case, your custom sequence settings are used in the case where the preferred -> strategy of the underlying platform is SEQUENCE, such as for Oracle and PostgreSQL. - - -### Composite Keys - -Doctrine 2 allows to use composite primary keys. There are however some restrictions opposed to using a single identifier. -The use of the `@GeneratedValue` annotation is only supported for simple (not composite) primary keys, which means -you can only use composite keys if you generate the primary key values yourself before calling `EntityManager#persist()` -on the entity. - -To designate a composite primary key / identifier, simply put the @Id marker annotation on all fields that make up the primary key. - -## Quoting Reserved Words - -It may sometimes be necessary to quote a column or table name because it conflicts with a reserved word of the particular RDBMS in use. This is often referred to as "Identifier Quoting". To let Doctrine know that you would like a table or column name to be quoted in all SQL statements, enclose the table or column name in backticks. Here is an example: - - **CAUTION** -> Identifier Quoting is not supported for join column names or discriminator column names. - -> **CAUTION** -> Identifier Quoting is a feature that is mainly intended to support legacy database -> schemas. The use of reserved words and identifier quoting is generally discouraged. -> Identifier quoting should not be used to enable the use non-standard-characters such -> as a dash in a hypothetical column `test-name`. Also Schema-Tool will likely have -> troubles when quoting is used for case-sensitivity reasons (in Oracle for example). \ No newline at end of file diff --git a/manual/en/batch-processing.rst b/manual/en/batch-processing.rst index 5e04e435b..b47906d6a 100644 --- a/manual/en/batch-processing.rst +++ b/manual/en/batch-processing.rst @@ -1,9 +1,14 @@ +Batch Processing +================ + This chapter shows you how to accomplish bulk inserts, updates and deletes with Doctrine in an efficient way. The main problem with bulk operations is usually not to run out of memory and this is especially what the strategies presented here provide help with. - **CAUTION** An ORM tool is not primarily well-suited for mass +.. warning:: + + An ORM tool is not primarily well-suited for mass inserts, updates or deletions. Every RDBMS has its own, most effective way of dealing with such operations and if the options outlined below are not sufficient for your purposes we recommend diff --git a/manual/en/batch-processing.txt b/manual/en/batch-processing.txt deleted file mode 100644 index fdc44bd45..000000000 --- a/manual/en/batch-processing.txt +++ /dev/null @@ -1,122 +0,0 @@ -This chapter shows you how to accomplish bulk inserts, updates and deletes with Doctrine in an efficient way. The main problem with bulk operations is usually not to run out of memory and this is especially what the strategies presented here provide help with. - -> **CAUTION** -> An ORM tool is not primarily well-suited for mass inserts, updates or deletions. -> Every RDBMS has its own, most effective way of dealing with such operations and if -> the options outlined below are not sufficient for your purposes we recommend you -> use the tools for your particular RDBMS for these bulk operations. - -## Bulk Inserts - -Bulk inserts in Doctrine are best performed in batches, taking advantage of the transactional write-behind behavior of an `EntityManager`. The following code shows an example for inserting 10000 objects with a batch size of 20. You may need to experiment with the batch size to find the size that works best for you. Larger batch sizes mean more prepared statement reuse internally but also mean more work during `flush`. - - setStatus('user'); - $user->setUsername('user' . $i); - $user->setName('Mr.Smith-' . $i); - $em->persist($user); - if (($i % $batchSize) == 0) { - $em->flush(); - $em->clear(); // Detaches all objects from Doctrine! - } - } - -## Bulk Updates - -There are 2 possibilities for bulk updates with Doctrine. - -### DQL UPDATE - -The by far most efficient way for bulk updates is to use a DQL UPDATE query. Example: - - createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9'); - $numUpdated = $q->execute(); - -### Iterating results - -An alternative solution for bulk updates is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this, combining the iteration with the batching strategy that was already used for bulk inserts: - - createQuery('select u from MyProject\Model\User u'); - $iterableResult = $q->iterate(); - foreach($iterableResult AS $row) { - $user = $row[0]; - $user->increaseCredit(); - $user->calculateNewBonuses(); - if (($i % $batchSize) == 0) { - $em->flush(); // Executes all updates. - $em->clear(); // Detaches all objects from Doctrine! - } - ++$i; - } - -> **NOTE** -> Iterating results is not possible with queries that fetch-join a collection-valued -> association. The nature of such SQL result sets is not suitable for incremental -> hydration. - -## Bulk Deletes - -There are two possibilities for bulk deletes with Doctrine. You can either issue -a single DQL DELETE query or you can iterate over results removing them one at a time. - -### DQL DELETE - -The by far most efficient way for bulk deletes is to use a DQL DELETE query. - -Example: - - createQuery('delete from MyProject\Model\Manager m where m.salary > 100000'); - $numDeleted = $q->execute(); - -### Iterating results - -An alternative solution for bulk deletes is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this: - - createQuery('select u from MyProject\Model\User u'); - $iterableResult = $q->iterate(); - while (($row = $iterableResult->next()) !== false) { - $em->remove($row[0]); - if (($i % $batchSize) == 0) { - $em->flush(); // Executes all deletions. - $em->clear(); // Detaches all objects from Doctrine! - } - ++$i; - } - -> **NOTE** -> Iterating results is not possible with queries that fetch-join a collection-valued -> association. The nature of such SQL result sets is not suitable for incremental -> hydration. - -## Iterating Large Results for Data-Processing - -You can use the `iterate()` method just to iterate over a large result and no UPDATE or DELETE -intention. The `IterableResult` instance returned from `$query->iterate()` implements the -Iterator interface so you can process a large result without memory problems using the -following approach: - - _em->createQuery('select u from MyProject\Model\User u'); - $iterableResult = $q->iterate(); - foreach ($iterableResult AS $row) { - // do stuff with the data in the row, $row[0] is always the object - - // detach from Doctrine, so that it can be Garbage-Collected immediately - $this->_em->detach($row[0]); - } - -> **NOTE** -> Iterating results is not possible with queries that fetch-join a collection-valued -> association. The nature of such SQL result sets is not suitable for incremental -> hydration. \ No newline at end of file diff --git a/manual/en/best-practices.rst b/manual/en/best-practices.rst index 48a8771c7..28709666f 100644 --- a/manual/en/best-practices.rst +++ b/manual/en/best-practices.rst @@ -1,7 +1,10 @@ - **NOTE** The best practices mentioned here that affect database - design generally refer to best practices when working with Doctrine - and do not necessarily reflect best practices for database design - in general. +Best Practices +============== + +The best practices mentioned here that affect database +design generally refer to best practices when working with Doctrine +and do not necessarily reflect best practices for database design +in general. Don't use public properties on entities diff --git a/manual/en/best-practices.txt b/manual/en/best-practices.txt deleted file mode 100644 index 1d4cee690..000000000 --- a/manual/en/best-practices.txt +++ /dev/null @@ -1,77 +0,0 @@ - -> **NOTE** -> The best practices mentioned here that affect database design generally refer to best -> practices when working with Doctrine and do not necessarily reflect best practices for -> database design in general. - -## Don't use public properties on entities - -It is very important that you don't map public properties on entities, but only protected or private ones. -The reason for this is simple, whenever you access a public property of a proxy object that hasn't been initialized -yet the return value will be null. Doctrine cannot hook into this process and magically make the entity lazy load. - -This can create situations where it is very hard to debug the current failure. We therefore urge you to map only -private and protected properties on entities and use getter methods or magic __get() to access them. - -## Constrain relationships as much as possible - -It is important to constrain relationships as much as possible. This means: - -* Impose a traversal direction (avoid bidirectional associations if possible) -* Eliminate nonessential associations - -This has several benefits: - -* Reduced coupling in your domain model -* Simpler code in your domain model (no need to maintain bidirectionality properly) -* Less work for Doctrine - -## Avoid composite keys - -Even though Doctrine fully supports composite keys it is best not to use them if possible. Composite keys require additional work by Doctrine and thus have a higher probability of errors. - -## Use events judiciously - -The event system of Doctrine is great and fast. Even though making heavy use of events, especially lifecycle events, can have a negative impact on the performance of your application. Thus you should use events judiciously. - -## Use cascades judiciously - -Automatic cascades of the persist/remove/merge/etc. operations are very handy but should be used wisely. Do NOT simply add all cascades to all associations. Think about which cascades actually do make sense for you for a particular association, given the scenarios it is most likely used in. - -## Don't use special characters - -Avoid using any non-ASCII characters in class, field, table or column names. Doctrine itself is not unicode-safe in many places and will not be until PHP itself is fully unicode-aware (PHP6). - -## Don't use identifier quoting - -Identifier quoting is a workaround for using reserved words that often causes problems in edge cases. Do not use identifier quoting and avoid using reserved words as table or column names. - -## Initialize collections in the constructor - -It is recommended best practice to initialize any business collections in entities in the constructor. Example: - - addresses = new ArrayCollection; - $this->articles = new ArrayCollection; - } - } - -## Don't map foreign keys to fields in an entity - -Foreign keys have no meaning whatsoever in an object model. Foreign keys are how a relational database establishes relationships. Your object model establishes relationships through object references. Thus mapping foreign keys to object fields heavily leaks details of the relational model into the object model, something you really should not do. - -## Use explicit transaction demarcation - -While Doctrine will automatically wrap all DML operations in a transaction on flush(), it is considered best practice to explicitly set the transaction boundaries yourself. -Otherwise every single query is wrapped in a small transaction (Yes, SELECT queries, too) since you can not talk to your database outside of a transaction. -While such short transactions for read-only (SELECT) queries generally don't have any noticeable performance impact, it is still preferable to use fewer, well-defined transactions -that are established through explicit transaction boundaries. - diff --git a/manual/en/caching.rst b/manual/en/caching.rst index 8c4ee7a4d..62601af86 100644 --- a/manual/en/caching.rst +++ b/manual/en/caching.rst @@ -1,3 +1,6 @@ +Caching +======= + Doctrine provides cache drivers in the ``Common`` package for some of the most popular caching implementations such as APC, Memcache and Xcache. We also provide an ``ArrayCache`` driver which stores diff --git a/manual/en/caching.txt b/manual/en/caching.txt deleted file mode 100644 index 7a2909ac1..000000000 --- a/manual/en/caching.txt +++ /dev/null @@ -1,350 +0,0 @@ -Doctrine provides cache drivers in the `Common` package for some of the most -popular caching implementations such as APC, Memcache and Xcache. We also provide -an `ArrayCache` driver which stores the data in a PHP array. Obviously, the cache -does not live between requests but this is useful for testing in a development -environment. - -## Cache Drivers - -The cache drivers follow a simple interface that is defined in `Doctrine\Common\Cache\Cache`. -All the cache drivers extend a base class `Doctrine\Common\Cache\AbstractCache` -which implements the before mentioned interface. - -The interface defines the following methods for you to publicly use. - - * fetch($id) - Fetches an entry from the cache. - * contains($id) - Test if an entry exists in the cache. - * save($id, $data, $lifeTime = false) - Puts data into the cache. - * delete($id) - Deletes a cache entry. - -Each driver extends the `AbstractCache` class which defines a few abstract -protected methods that each of the drivers must implement. - - * _doFetch($id) - * _doContains($id) - * _doSave($id, $data, $lifeTime = false) - * _doDelete($id) - -The public methods `fetch()`, `contains()`, etc. utilize the above protected methods -that are implemented by the drivers. The code is organized this way so that the -protected methods in the drivers do the raw interaction with the cache implementation -and the `AbstractCache` can build custom functionality on top of these methods. - -### APC - -In order to use the APC cache driver you must have it compiled and enabled in -your php.ini. You can read about APC [here](http://us2.php.net/apc) on the PHP -website. It will give you a little background information about what it is and -how you can use it as well as how to install it. - -Below is a simple example of how you could use the APC cache driver by itself. - - save('cache_id', 'my_data'); - -### Memcache - -In order to use the Memcache cache driver you must have it compiled and enabled in -your php.ini. You can read about Memcache [here](http://us2.php.net/memcache) on -the PHP website. It will give you a little background information about what it is -and how you can use it as well as how to install it. - -Below is a simple example of how you could use the Memcache cache driver by itself. - - connect('memcache_host', 11211); - - $cacheDriver = new \Doctrine\Common\Cache\MemcacheCache(); - $cacheDriver->setMemcache() - $cacheDriver->save('cache_id', 'my_data'); - -### Xcache - -In order to use the Xcache cache driver you must have it compiled and enabled in -your php.ini. You can read about Xcache [here](http://xcache.lighttpd.net/). It -will give you a little background information about what it is and how you can -use it as well as how to install it. - -Below is a simple example of how you could use the Xcache cache driver by itself. - - save('cache_id', 'my_data'); - -## Using Cache Drivers - -In this section we'll describe how you can fully utilize the API of the cache -drivers to save cache, check if some cache exists, fetch the cached data and -delete the cached data. We'll use the `ArrayCache` implementation as our -example here. - - save('cache_id', 'my_data'); - -The `save()` method accepts three arguments which are described below. - - * `$id` - The cache id - * `$data` - The cache entry/data. - * `$lifeTime` - The lifetime. If != false, sets a specific lifetime for this cache entry (null => infinite lifeTime). - -You can save any type of data whether it be a string, array, object, etc. - - 'value1', - 'key2' => 'value2' - ); - $cacheDriver->save('my_array', $array); - -### Checking - -Checking whether some cache exists is very simple, just use the `contains()` method. -It accepts a single argument which is the ID of the cache entry. - - contains('cache_id')) { - echo 'cache exists'; - } else { - echo 'cache does not exist'; - } - -### Fetching - -Now if you want to retrieve some cache entry you can use the `fetch()` method. It -also accepts a single argument just like `contains()` which is the ID of the cache entry. - - fetch('my_array'); - -### Deleting - -As you might guess, deleting is just as easy as saving, checking and fetching. -We have a few ways to delete cache entries. You can delete by an individual ID, -regular expression, prefix, suffix or you can delete all entries. - -#### By Cache ID - - delete('my_array'); - -You can also pass wild cards to the `delete()` method and it will return an array -of IDs that were matched and deleted. - - delete('users_*'); - -#### By Regular Expression - -If you need a little more control than wild cards you can use a PHP regular -expression to delete cache entries. - - deleteByRegex('/users_.*/'); - -#### By Prefix - -Because regular expressions are kind of slow, if simply deleting by a prefix or -suffix is sufficient, it is recommended that you do that instead of using a regular -expression because it will be much faster if you have many cache entries. - - deleteByPrefix('users_'); - -#### By Suffix - -Just like we did above with the prefix you can do the same with a suffix. - - deleteBySuffix('_my_account'); - -#### All - -If you simply want to delete all cache entries you can do so with the `deleteAll()` -method. - - deleteAll(); - -### Counting - -If you want to count how many entries are stored in the cache driver instance -you can use the `count()` method. - - count(); - -> **NOTE** -> In order to use `deleteByRegex()`, `deleteByPrefix()`, `deleteBySuffix()`, -> `deleteAll()`, `count()` or `getIds()` you must enable an option for the cache -> driver to manage your cache IDs internally. This is necessary because APC, -> Memcache, etc. don't have any advanced functionality for fetching and deleting. -> We add some functionality on top of the cache drivers to maintain an index of -> all the IDs stored in the cache driver so that we can allow more granular deleting -> operations. -> -> $cacheDriver->setManageCacheIds(true); - -### Namespaces - -If you heavily use caching in your application and utilize it in multiple parts -of your application, or use it in different applications on the same server you -may have issues with cache naming collisions. This can be worked around by using -namespaces. You can set the namespace a cache driver should use by using the -`setNamespace()` method. - - setNamespace('my_namespace_'); - -## Integrating with the ORM - -The Doctrine ORM package is tightly integrated with the cache drivers to allow -you to improve performance of various aspects of Doctrine by just simply making -some additional configurations and method calls. - -### Query Cache - -It is highly recommended that in a production environment you cache the -transformation of a DQL query to its SQL counterpart. It doesn't make sense to -do this parsing multiple times as it doesn't change unless you alter the DQL -query. - -This can be done by configuring the query cache implementation to use on your ORM -configuration. - - setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache()); - -### Result Cache - -The result cache can be used to cache the results of your queries so that we -don't have to query the database or hydrate the data again after the first time. -You just need to configure the result cache implementation. - - setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache()); - -Now when you're executing DQL queries you can configure them to use the result cache. - - createQuery('select u from \Entities\User u'); - $query->useResultCache(true); - -You can also configure an individual query to use a different result cache driver. - - setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache()); - -> **NOTE** -> Setting the result cache driver on the query will automatically enable the -> result cache for the query. If you want to disable it pass false to -> `useResultCache()`. -> -> $query->useResultCache(false); - -If you want to set the time the cache has to live you can use the `setResultCacheLifetime()` -method. - - setResultCacheLifetime(3600); - -The ID used to store the result set cache is a hash which is automatically generated -for you if you don't set a custom ID yourself with the `setResultCacheId()` method. - - setResultCacheId('my_custom_id'); - -You can also set the lifetime and cache ID by passing the values as the second -and third argument to `useResultCache()`. - - useResultCache(true, 3600, 'my_custom_id'); - -### Metadata Cache - -Your class metadata can be parsed from a few different sources like YAML, XML, -Annotations, etc. Instead of parsing this information on each request we should -cache it using one of the cache drivers. - -Just like the query and result cache we need to configure it first. - - setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); - -Now the metadata information will only be parsed once and stored in the cache -driver. - -## Clearing the Cache - -We've already shown you previously how you can use the API of the cache drivers -to manually delete cache entries. For your convenience we offer a command line task -for you to help you with clearing the query, result and metadata cache. - -From the Doctrine command line you can run the following command. - - $ ./doctrine clear-cache - -Running this task with no arguments will clear all the cache for all the configured -drivers. If you want to be more specific about what you clear you can use the -following options. - -To clear the query cache use the `--query` option. - - $ ./doctrine clear-cache --query - -To clear the metadata cache use the `--metadata` option. - - $ ./doctrine clear-cache --metadata - -To clear the result cache use the `--result` option. - - $ ./doctrine clear-cache --result - -When you use the `--result` option you can use some other options to be more -specific about what queries result sets you want to clear. - -Just like the API of the cache drivers you can clear based on an ID, regular -expression, prefix or suffix. - - $ ./doctrine clear-cache --result --id=cache_id - -Or if you want to clear based on a regular expressions. - - $ ./doctrine clear-cache --result --regex=users_.* - -Or with a prefix. - - $ ./doctrine clear-cache --result --prefix=users_ - -And finally with a suffix. - - $ ./doctrine clear-cache --result --suffix=_my_account - -> **NOTE** -> Using the `--id`, `--regex`, etc. options with the `--query` and `--metadata` -> are not allowed as it is not necessary to be specific about what you clear. -> You only ever need to completely clear the cache to remove stale entries. - -## Cache Slams - -Something to be careful of when utilizing the cache drivers is cache slams. If -you have a heavily trafficked website with some code that checks for the existence -of a cache record and if it does not exist it generates the information and saves -it to the cache. Now if 100 requests were issued all at the same time and each one -sees the cache does not exist and they all try and insert the same cache entry -it could lock up APC, Xcache, etc. and cause problems. Ways exist to work around -this, like pre-populating your cache and not letting your users requests populate -the cache. - -You can read more about cache slams [here](http://t3.dotgnu.info/blog/php/user-cache-timebomb). \ No newline at end of file diff --git a/manual/en/change-tracking-policies.rst b/manual/en/change-tracking-policies.rst index 97b3508fb..25f32d051 100644 --- a/manual/en/change-tracking-policies.rst +++ b/manual/en/change-tracking-policies.rst @@ -1,5 +1,5 @@ Change Tracking Policies ------------------------- +======================== Change tracking is the process of determining what has changed in managed entities since the last time they were synchronized with diff --git a/manual/en/change-tracking-policies.txt b/manual/en/change-tracking-policies.txt deleted file mode 100644 index 11fec2e4d..000000000 --- a/manual/en/change-tracking-policies.txt +++ /dev/null @@ -1,128 +0,0 @@ -## 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: - - _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: - - _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/configuration.rst b/manual/en/configuration.rst index 585108f6e..a3a3a3987 100644 --- a/manual/en/configuration.rst +++ b/manual/en/configuration.rst @@ -1,3 +1,6 @@ +Configuration +============= + Bootstrapping ------------- @@ -32,7 +35,9 @@ namespace and where there is a common root namespace. The following example shows the setup of a ``ClassLoader`` for the different types of Doctrine Installations: - **NOTE** This assumes you've created some kind of script to test +.. note:: + + This assumes you've created some kind of script to test the following code in. Something like a ``test.php`` file. @@ -370,7 +375,8 @@ lazy-loading capabilities to them. Doctrine can then give you an instance of such a proxy class whenever you request an object of the class being proxied. This happens in two situations: -**Reference Proxies** +Reference Proxies +~~~~~~~~~~~~~~~~~ The method ``EntityManager#getReference($entityName, $identifier)`` lets you obtain a reference to an entity for which the identifier @@ -395,7 +401,8 @@ for the Item class but your code does not need to care. In fact it **should not care**. Proxy objects should be transparent to your code. -**Association proxies** +Association proxies +~~~~~~~~~~~~~~~~~~~ The second most important situation where Doctrine uses proxy objects is when querying for objects. Whenever you query for an @@ -405,7 +412,9 @@ query, Doctrine puts proxy objects in place where normally the associated object would be. Just like other proxies it will transparently initialize itself on first access. - **NOTE** Joining an association in a DQL or native query +.. note:: + + Joining an association in a DQL or native query essentially means eager loading of that association in that query. This will override the 'fetch' option specified in the mapping for that association, but only for that query. diff --git a/manual/en/configuration.txt b/manual/en/configuration.txt deleted file mode 100644 index 717032b0a..000000000 --- a/manual/en/configuration.txt +++ /dev/null @@ -1,307 +0,0 @@ -## Bootstrapping - -Bootstrapping Doctrine is a relatively simple procedure that roughly exists of -just 2 steps: - -* Making sure Doctrine class files can be loaded on demand. -* Obtaining an EntityManager instance. - -### Class loading - -Lets start with the class loading setup. We need to set up some class loaders -(often called "autoloader") so that Doctrine class files are loaded on demand. -The Doctrine\Common namespace contains a very fast and minimalistic class loader -that can be used for Doctrine and any other libraries where the coding standards -ensure that a class's location in the directory tree is reflected by its name -and namespace and where there is a common root namespace. - -> **NOTE** -> You are not forced to use the Doctrine class loader to load Doctrine -> classes. Doctrine does not care how the classes are loaded, if you want to use a -> different class loader or your own to load Doctrine classes, just do that. -> Along the same lines, the class loader in the Doctrine\Common namespace is not -> meant to be only used for Doctrine classes, too. It is a generic class loader that can -> be used for any classes that follow some basic naming standards as described above. - -The following example shows the setup of a `ClassLoader` for the different types -of Doctrine Installations: - -> **NOTE** -> This assumes you've created some kind of script to test the following code in. -> Something like a `test.php` file. - -#### PEAR or Tarball Download - - register(); // register on SPL autoload stack - -#### Git - -The Git bootstrap assumes that you have fetched the related packages through `git submodule update --init` - - register(); - - $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\DBAL', $lib . 'vendor/doctrine-dbal/lib'); - $classLoader->register(); - - $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\ORM', $lib); - $classLoader->register(); - -#### Additional Symfony Components - -If you don't use Doctrine2 in combination with Symfony2 you have to register an additional namespace to be able to use -the Doctrine-CLI Tool or the YAML Mapping driver: - - register(); - - // Git Setup - $classloader = new \Doctrine\Common\ClassLoader('Symfony', $lib . 'vendor/'); - $classloader->register(); - -For best class loading performance it is recommended that you keep your include_path short, ideally it should only contain the path to the PEAR libraries, and any other class libraries should be registered with their full base path. - -### Obtaining an EntityManager - -Once you have prepared the class loading, you acquire an *EntityManager* instance. -The EntityManager class is the primary access point to ORM functionality provided by Doctrine. - -A simple configuration of the EntityManager requires a `Doctrine\ORM\Configuration` -instance as well as some database connection parameters: - - setMetadataCacheImpl($cache); - $driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); - $config->setMetadataDriverImpl($driverImpl); - $config->setQueryCacheImpl($cache); - $config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); - $config->setProxyNamespace('MyProject\Proxies'); - - if ($applicationMode == "development") { - $config->setAutoGenerateProxyClasses(true); - } else { - $config->setAutoGenerateProxyClasses(false); - } - - $connectionOptions = array( - 'driver' => 'pdo_sqlite', - 'path' => 'database.sqlite' - ); - - $em = EntityManager::create($connectionOptions, $config); - -> **CAUTION** -> Do not use Doctrine without a metadata and query cache! Doctrine is highly -> optimized for working with caches. The main parts in Doctrine that are optimized -> for caching are the metadata mapping information with the metadata cache and the -> DQL to SQL conversions with the query cache. These 2 caches require only an absolute -> minimum of memory yet they heavily improve the runtime performance of Doctrine. -> The recommended cache driver to use with Doctrine is [APC](http://www.php.net/apc). -> APC provides you with an opcode-cache (which is highly recommended anyway) and -> a very fast in-memory cache storage that you can use for the metadata and query -> caches as seen in the previous code snippet. - -## Configuration Options - -The following sections describe all the configuration options available on a `Doctrine\ORM\Configuration` instance. - -### Proxy Directory (***REQUIRED***) - - setProxyDir($dir); - $config->getProxyDir(); - -Gets or sets the directory where Doctrine generates any proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. - -### Proxy Namespace (***REQUIRED***) - - setProxyNamespace($namespace); - $config->getProxyNamespace(); - -Gets or sets the namespace to use for generated proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. - -### Metadata Driver (***REQUIRED***) - - setMetadataDriverImpl($driver); - $config->getMetadataDriverImpl(); - -Gets or sets the metadata driver implementation that is used by Doctrine to acquire the object-relational metadata for your classes. - -There are currently 4 available implementations: - - * `Doctrine\ORM\Mapping\Driver\AnnotationDriver` - * `Doctrine\ORM\Mapping\Driver\XmlDriver` - * `Doctrine\ORM\Mapping\Driver\YamlDriver` - * `Doctrine\ORM\Mapping\Driver\DriverChain` - -Throughout the most part of this manual the AnnotationDriver is used in the examples. For information on the usage of the XmlDriver or YamlDriver please refer to the dedicated chapters `XML Mapping` and `YAML Mapping`. - -The annotation driver can be configured with a factory method on the `Doctrine\ORM\Configuration`: - - newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); - $config->setMetadataDriverImpl($driverImpl); - -The path information to the entities is required for the annotation driver, because otherwise -mass-operations on all entities through the console could not work correctly. All of metadata -drivers accept either a single directory as a string or an array of directories. With this feature a -single driver can support multiple directories of Entities. - -### Metadata Cache (***RECOMMENDED***) - - setMetadataCacheImpl($cache); - $config->getMetadataCacheImpl(); - -Gets or sets the cache implementation to use for caching metadata information, that is, all the information you supply via annotations, xml or yaml, so that they do not need to be parsed and loaded from scratch on every single request which is a waste of resources. -The cache implementation must implement the `Doctrine\Common\Cache\Cache` interface. - -Usage of a metadata cache is highly recommended. - -The recommended implementations for production are: - - * `Doctrine\Common\Cache\ApcCache` - * `Doctrine\Common\Cache\MemcacheCache` - * `Doctrine\Common\Cache\XcacheCache` - -For development you should use the `Doctrine\Common\Cache\ArrayCache` which only caches data on a per-request basis. - -### Query Cache (***RECOMMENDED***) - - setQueryCacheImpl($cache); - $config->getQueryCacheImpl(); - -Gets or sets the cache implementation to use for caching DQL queries, that is, the result of a DQL parsing process that includes the final SQL as well as meta information about how to process the SQL result set of a query. Note that the query cache does not affect query results. You do not get stale data. This is a pure optimization cache without any negative side-effects (except some minimal memory usage in your cache). - -Usage of a query cache is highly recommended. - -The recommended implementations for production are: - - * `Doctrine\Common\Cache\ApcCache` - * `Doctrine\Common\Cache\MemcacheCache` - * `Doctrine\Common\Cache\XcacheCache` - -For development you should use the `Doctrine\Common\Cache\ArrayCache` which only caches data on a per-request basis. - -### SQL Logger (***Optional***) - - setSQLLogger($logger); - $config->getSQLLogger(); - -Gets or sets the logger to use for logging all SQL statements executed by Doctrine. The logger class must implement the `Doctrine\DBAL\Logging\SqlLogger` interface. A simple default implementation that logs to the standard output using `echo` and `var_dump` can be found at `Doctrine\DBAL\Logging\EchoSqlLogger`. - -### Auto-generating Proxy Classes (***OPTIONAL***) - - setAutoGenerateProxyClasses($bool); - $config->getAutoGenerateProxyClasses(); - -Gets or sets whether proxy classes should be generated automatically at runtime by Doctrine. If set to `FALSE`, proxy classes must be generated manually through the doctrine command line task `generate-proxies`. The strongly recommended value for a production environment is `FALSE`. - -## Development vs Production Configuration - -You should code your Doctrine2 bootstrapping with two different runtime models in mind. There are some serious -benefits of using APC or Memcache in production. In development however this will frequently give you fatal -errors, when you change your entities and the cache still keeps the outdated metadata. That is why we recommend -the `ArrayCache` for development. - -Furthermore you should have the Auto-generating Proxy Classes option to true in development and to false -in production. If this option is set to `TRUE` it can seriously hurt your script performance if several proxy -classes are re-generated during script execution. Filesystem calls of that magnitude can even slower than all -the database queries Doctrine issues. Additionally writing a proxy sets an exclusive file lock which can cause -serious performance bottlenecks in systems with regular concurrent requests. - -## Connection Options - -The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array -or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the -DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The DBAL configuration is explained -in the [DBAL section](./../../../../../dbal/2.0/docs/reference/configuration/en). - -## Proxy Objects - -A proxy object is an object that is put in place or used instead of the "real" object. A proxy object can add behavior to the object being proxied without that object being aware of it. In Doctrine 2, proxy objects are used to realize several features but mainly for transparent lazy-loading. - -Proxy objects with their lazy-loading facilities help to keep the subset of objects that are already in memory connected to the rest of the objects. This is an essential property as without it there would always be fragile partial objects at the outer edges of your object graph. - -Doctrine 2 implements a variant of the proxy pattern where it generates classes that extend your entity classes and adds lazy-loading capabilities to them. Doctrine can then give you an instance of such a proxy class whenever you request an object of the class being proxied. This happens in two situations: - -**Reference Proxies** - -The method `EntityManager#getReference($entityName, $identifier)` lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier. You could simply do this: - - getReference('MyProject\Model\Item', $itemId); - $cart->addItem($item); - -Here, we added an Item to a Cart without loading the Item from the database. If you invoke any method on the Item instance, it would fully initialize its state transparently from the database. Here $item is actually an instance of the proxy class that was generated for the Item class but your code does not need to care. In fact it **should not care**. Proxy objects should be transparent to your code. - -**Association proxies** - -The second most important situation where Doctrine uses proxy objects is when querying for objects. Whenever you query for an object that has a single-valued association to another object that is configured LAZY, without joining that association in the same query, Doctrine puts proxy objects in place where normally the associated object would be. -Just like other proxies it will transparently initialize itself on first access. - -> **NOTE** -> Joining an association in a DQL or native query essentially means eager loading of that -> association in that query. This will override the 'fetch' option specified in -> the mapping for that association, but only for that query. - -### Generating Proxy classes - -Proxy classes can either be generated manually through the Doctrine Console or automatically by Doctrine. The configuration option that controls this behavior is: - - setAutoGenerateProxyClasses($bool); - $config->getAutoGenerateProxyClasses(); - -The default value is `TRUE` for convenient development. However, this setting is not optimal for performance and therefore not recommended for a production environment. -To eliminate the overhead of proxy class generation during runtime, set this configuration option to `FALSE`. When you do this in a development environment, note that you may get class/file not found errors if certain proxy classes are not available or failing lazy-loads if new methods were added to the entity class that are not yet in the proxy class. In such a case, simply use the Doctrine Console to (re)generate the proxy classes like so: - - $ ./doctrine orm:generate-proxies - -## Multiple Metadata Sources - -When using different components using Doctrine 2 you may end up with them using two different metadata drivers, -for example XML and YAML. You can use the DriverChain Metadata implementations to aggregate these drivers -based on namespaces: - - addDriver($xmlDriver, 'Doctrine\Tests\Models\Company'); - $chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping'); - -Based on the namespace of the entity the loading of entities is delegated to the appropriate driver. The chain -semantics come from the fact that the driver loops through all namespaces and matches the entity class name -against the namespace using a `strpos() === 0` call. This means you need to order the drivers correctly if -sub-namespaces use different metadata driver implementations. diff --git a/manual/en/dql-doctrine-query-language.rst b/manual/en/dql-doctrine-query-language.rst index 2ec388c59..e99916d2c 100644 --- a/manual/en/dql-doctrine-query-language.rst +++ b/manual/en/dql-doctrine-query-language.rst @@ -1,5 +1,5 @@ -DQL Explained -------------- +Doctrine Query Language +=========================== DQL stands for **D**octrine **Q**uery **L**anguage and is an Object Query Language derivate that is very similar to the **H**ibernate @@ -433,9 +433,8 @@ starting with 0. However with INDEX BY you can specify any other column to be the key of your result, it really only makes sense with primary or unique fields though: -:: +.. code-block:: sql - [sql] SELECT u.id, u.status, upper(u.name) nameUpper FROM User u INDEX BY u.id JOIN u.phonenumbers p INDEX BY p.phonenumber @@ -470,15 +469,17 @@ can also execute bulk updates on a set of entities using an DQL-UPDATE query. The Syntax of an UPDATE query works as expected, as the following example shows: -:: +.. code-block:: sql UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) References to related entities are only possible in the WHERE clause and using sub-selects. - **CAUTION** DQL UPDATE statements are ported directly into a - Database UPDATE statement and therefore bypass any locking scheme +.. warning:: + + DQL UPDATE statements are ported directly into a + Database UPDATE statement and therefore bypass any locking scheme, events and do not increment the version column. Entities that are already loaded into the persistence context will *NOT* be synced with the updated database state. It is recommended to call @@ -492,14 +493,16 @@ DELETE queries DELETE queries can also be specified using DQL and their syntax is as simple as the UPDATE syntax: -:: +.. code-block:: sql DELETE MyProject\Model\User u WHERE u.id = 4 The same restrictions apply for the reference of related entities. - **CAUTION** DQL DELETE statements are ported directly into a - Database DELETE statement and therefore bypass any checks for the +.. warning:: + + DQL DELETE statements are ported directly into a + Database DELETE statement and therefore bypass any events and checks for the version column if they are not explicitly added to the WHERE clause of the query. Additionally Deletes of specifies entities are *NOT* cascaded to related entities even if specified in the metadata. @@ -540,7 +543,7 @@ Arithmetic operators You can do math in DQL using numeric values, for example: -:: +.. warning:: SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 @@ -694,9 +697,8 @@ scenario it is a generic Person and Employee example: First notice that the generated SQL to create the tables for these entities looks like the following: -:: +.. code-block:: sql - [sql] CREATE TABLE Person (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(50) NOT NULL, discr VARCHAR(255) NOT NULL, department VARCHAR(50) NOT NULL) Now when persist a new ``Employee`` instance it will set the @@ -714,18 +716,16 @@ discriminator value for us automatically: Now lets run a simple query to retrieve the ``Employee`` we just created: -:: +.. code-block:: sql - [sql] SELECT e FROM Entities\Employee e WHERE e.name = 'test' If we check the generated SQL you will notice it has some special conditions added to ensure that we will only get back ``Employee`` entities: -:: +.. code-block:: sql - [sql] SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, p0_.discr AS discr3 FROM Person p0_ WHERE (p0_.name = ?) AND p0_.discr IN ('employee') Class Table Inheritance @@ -761,9 +761,8 @@ table, you just need to change the inheritance type from Now take a look at the SQL which is generated to create the table, you'll notice some differences: -:: +.. code-block:: sql - [sql] CREATE TABLE Person (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(50) NOT NULL, discr VARCHAR(255) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB; CREATE TABLE Employee (id INT NOT NULL, department VARCHAR(50) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB; ALTER TABLE Employee ADD FOREIGN KEY (id) REFERENCES Person(id) ON DELETE CASCADE @@ -777,9 +776,8 @@ Now if were to insert the same ``Employee`` as we did in the generate different SQL joining the ``Person`` information automatically for you: -:: +.. code-block:: sql - [sql] SELECT p0_.id AS id0, p0_.name AS name1, e1_.department AS department2, p0_.discr AS discr3 FROM Employee e1_ INNER JOIN Person p0_ ON e1_.id = p0_.id WHERE p0_.name = ? The Query class @@ -892,9 +890,8 @@ structure: To better understand mixed results, consider the following DQL query: -:: +.. code-block:: sql - [sql] SELECT u, UPPER(u.name) nameUpper FROM MyProject\Model\User u This query makes use of the ``UPPER`` DQL function that returns a @@ -1186,7 +1183,9 @@ number of results: - ``Query::setMaxResults($maxResults)`` - ``Query::setFirstResult($offset)`` - **NOTE** If your query contains a fetch-joined collection +.. note:: + + If your query contains a fetch-joined collection specifying the result limit methods are not working as you would expect. Set Max Results restricts the number of database result rows, however in the case of fetch-joined collections one root diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt deleted file mode 100644 index 1a3f6ab6c..000000000 --- a/manual/en/dql-doctrine-query-language.txt +++ /dev/null @@ -1,1096 +0,0 @@ -## DQL Explained - -DQL stands for **D**octrine **Q**uery **L**anguage and is an Object Query Language derivate that is very similar to the **H**ibernate **Q**uery **L**anguage (HQL) or the **J**ava **P**ersistence **Q**uery **L**anguage (JPQL). - -In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects. - -> **CAUTION** -> A common mistake for beginners is to mistake DQL for being just some form of SQL -> and therefore trying to use table names and column names or join arbitrary tables -> together in a query. You need to think about DQL as a query language for your object -> model, not for your relational schema. - -DQL is case in-sensitive, except for namespace, class and field names, which are case sensitive. - -## Types of DQL queries - -DQL as a query language has SELECT, UPDATE and DELETE constructs that map to their corresponding -SQL statement types. INSERT statements are not allowed in DQL, because entities and their relations -have to be introduced into the persistence context through `EntityManager#persist()` to ensure -consistency of your object model. - -DQL SELECT statements are a very powerful way of retrieving parts of your domain model that are -not accessible via associations. Additionally they allow to retrieve entities and their associations -in one single sql select statement which can make a huge difference in performance in contrast -to using several queries. - -DQL UPDATE and DELETE statements offer a way to execute bulk changes on the entities of your -domain model. This is often necessary when you cannot load all the affected entities of a bulk -update into memory. - -## SELECT queries - -### DQL SELECT clause - -The select clause of a DQL query specifies what appears in the query result. The composition of all the expressions in the select clause also influences the nature of the query result. - -Here is an example that selects all users with an age > 20: - - createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20'); - $users = $query->getResult(); - -Lets examine the query: - -* `u` is a so called identification variable or alias that refers to the `MyProject\Model\User` class. By placing this alias in the SELECT clause we specify that we want all instances of the User class that are matched by this query appear in the query result. -* The FROM keyword is always followed by a fully-qualified class name which in turn is followed by an identification variable or alias for that class name. This class designates a root of our query from which we can navigate further via joins (explained later) and path expressions. -* The expression `u.age` in the WHERE clause is a path expression. Path expressions in DQL are easily identified by the use of the '.' operator that is used for constructing paths. The path expression `u.age` refers to the `age` field on the User class. - -The result of this query would be a list of User objects where all users are older than 20. - -The SELECT clause allows to specify both class identification variables that signal the hydration -of a complete entity class or just fields of the entity using the syntax `u.name`. -Combinations of both are also allowed and it is possible to wrap both fields and -identification values into aggregation and DQL functions. Numerical fields can -be part of computations using mathematical operations. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn) -on more information. - -### Joins - -A SELECT query can contain joins. There are 2 types of JOINs: "Regular" Joins and "Fetch" Joins. - -**Regular Joins**: Used to limit the results and/or compute aggregate values. - -**Fetch Joins**: In addition to the uses of regular joins: Used to fetch related entities and include them in the hydrated result of a query. - -There is no special DQL keyword that distinguishes a regular join from a fetch join. A join (be it an inner or outer join) becomes a "fetch join" as soon as fields of the joined entity appear in the SELECT part of the DQL query outside of an aggregate function. Otherwise its a "regular join". - -Example: - -Regular join of the address: - - createQuery("SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); - $users = $query->getResult(); - -Fetch join of the address: - - createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); - $users = $query->getResult(); - -When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the -root level of the result array. In the previous example an array of User instances is returned -and the address of each user is fetched and hydrated into the `User#address` variable. If you access -the address Doctrine does not need to lazy load the association with another query. - -> **NOTE** -> Doctrine allows you to walk all the associations between all the objects in your domain model. -> Objects that were not already loaded from the database are replaced with lazy load proxy instances. -> Non-loaded Collections are also replaced by lazy-load instances that fetch all the contained objects upon -> first access. However relying on the lazy-load mechanism leads to many small queries executed -> against the database, which can significantly affect the performance of your application. -> **Fetch Joins** are the solution to hydrate most or all of the entities that you -> need in a single SELECT query. - -### Named and Positional Parameters - -DQL supports both named and positional parameters, however in contrast to many SQL dialects -positional parameters are specified with numbers, for example "?1", "?2" and so on. -Named parameters are specified with ":name1", ":name2" and so on. - -### DQL SELECT Examples - -This section contains a large set of DQL queries and some explanations of what is happening. -The actual result also depends on the hydration mode. - -Hydrate all User entities: - - createQuery('SELECT u FROM MyProject\Model\User u'); - $users = $query->getResult(); // array of User objects - -Retrieve the IDs of all CmsUsers: - - createQuery('SELECT u.id FROM CmsUser u'); - $ids = $query->getResult(); // array of CmsUser ids - -Retrieve the IDs of all users that have written an article: - - createQuery('SELECT DISTINCT u.id FROM CmsArticle a JOIN a.user u'); - $ids = $query->getResult(); // array of CmsUser ids - -Retrieve all articles and sort them by the name of the articles users instance: - - createQuery('SELECT a FROM CmsArticle a JOIN a.user u ORDER BY u.name ASC'); - $articles = $query->getResult(); // array of CmsArticle objects - -Retrieve the Username and Name of a CmsUser: - - createQuery('SELECT u.username, u.name FROM CmsUser u'); - $users = $query->getResults(); // array of CmsUser username and id values - echo $users[0]['username']; - -Retrieve a ForumUser and his single associated entity: - - createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a'); - $users = $query->getResult(); // array of ForumUser objects with the avatar association loaded - echo get_class($users[0]->getAvatar()); - -Retrieve a CmsUser and fetch join all the phonenumbers he has: - - createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p'); - $users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded - $phonenumbers = $users[0]->getPhonenumbers(); - -Hydrate a result in Ascending: - - createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC'); - $users = $query->getResult(); // array of ForumUser objects - -Or in Descending Order: - - createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC'); - $users = $query->getResult(); // array of ForumUser objects - -Using Aggregate Functions: - - createQuery('SELECT COUNT(u.id) FROM Entities\User u'); - $count = $query->getSingleScalarResult(); - -With WHERE Clause and Positional Parameter: - - createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1'); - $users = $query->getResult(); // array of ForumUser objects - -With WHERE Clause and Named Parameter: - - createQuery('SELECT u FROM ForumUser u WHERE u.username = :name'); - $users = $query->getResult(); // array of ForumUser objects - -With Nested Conditions in WHERE Clause: - - createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id'); - $users = $query->getResult(); // array of ForumUser objects - -With COUNT DISTINCT: - - createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser'); - $users = $query->getResult(); // array of ForumUser objects - -With Arithmetic Expression in WHERE clause: - - createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); - $users = $query->getResult(); // array of ForumUser objects - -Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids: - - createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a'); - $results = $query->getResult(); // array of user ids and every article_id for each user - -Restricting a JOIN clause by additional conditions: - - createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%'"); - $users = $query->getResult(); - -Using several Fetch JOINs: - - createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c'); - $users = $query->getResult(); - -BETWEEN in WHERE clause: - - createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2'); - $usernames = $query->getResult(); - -DQL Functions in WHERE clause: - - createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'"); - $usernames = $query->getResult(); - -IN() Expression: - - createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)'); - $usernames = $query->getResult(); - - $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id IN (1, 2)'); - $users = $query->getResult(); - - $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id NOT IN (1)'); - $users = $query->getResult(); - -CONCAT() DQL Function: - - createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); - $ids = $query->getResult(); - - $query = $em->createQuery('SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1'); - $idUsernames = $query->getResult(); - -EXISTS in WHERE clause with correlated Subquery - - createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.user = u.id)'); - $ids = $query->getResult(); - -Get all users who are members of $group. - - createQuery('SELECT u.id FROM CmsUser u WHERE :groupId MEMBER OF u.groups'); - $query->setParameter(':groupId', $group); - $ids = $query->getResult(); - -Get all users that have more than 1 phonenumber - - createQuery('SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1'); - $users = $query->getResult(); - -Get all users that have no phonenumber - - createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); - $users = $query->getResult(); - -Get all instances of a specific type, for use with inheritance hierarchies: - - createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee'); - $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1'); - $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1'); - -#### Partial Object Syntax - -By default when you run a DQL query in Doctrine and select only a subset of the -fields for a given entity, you do not receive objects back. Instead, you receive -only arrays as a flat rectangular result set, similar to how you would if you -were just using SQL directly and joining some data. - -If you want to select partial objects you can use the `partial` DQL keyword: - - createQuery('SELECT partial u.{id, username} FROM CmsUser u'); - $users = $query->getResult(); // array of partially loaded CmsUser objects - -You use the partial syntax when joining as well: - - createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a'); - $users = $query->getResult(); // array of partially loaded CmsUser objects - -### Using INDEX BY - -The INDEX BY construct is nothing that directly translates into SQL but that affects -object and array hydration. After each FROM and JOIN clause you specify by which field -this class should be indexed in the result. By default a result is incremented -by numerical keys starting with 0. However with INDEX BY you can specify any -other column to be the key of your result, it really only makes sense with primary -or unique fields though: - - [sql] - SELECT u.id, u.status, upper(u.name) nameUpper FROM User u INDEX BY u.id - JOIN u.phonenumbers p INDEX BY p.phonenumber - -Returns an array of the following kind, indexed by both user-id then phonenumber-id: - - array - 0 => - array - 1 => - object(stdClass)[299] - public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) - public 'id' => int 1 - .. - 'nameUpper' => string 'ROMANB' (length=6) - 1 => - array - 2 => - object(stdClass)[298] - public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) - public 'id' => int 2 - ... - 'nameUpper' => string 'JWAGE' (length=5) - -## UPDATE queries - -DQL not only allows to select your Entities using field names, you can also execute bulk updates on a set -of entities using an DQL-UPDATE query. The Syntax of an UPDATE query works as expected, as the following -example shows: - - UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) - -References to related entities are only possible in the WHERE clause and using sub-selects. - -> **CAUTION** -> DQL UPDATE statements are ported directly into a Database UPDATE statement and therefore bypass -> any locking scheme and do not increment the version column. Entities that are already -> loaded into the persistence context will *NOT* be synced with the updated database state. It -> is recommended to call `EntityManager#clear()` and retrieve new instances of any affected entity. - -## DELETE queries - -DELETE queries can also be specified using DQL and their syntax is as simple as the UPDATE syntax: - - DELETE MyProject\Model\User u WHERE u.id = 4 - -The same restrictions apply for the reference of related entities. - -> **CAUTION** -> DQL DELETE statements are ported directly into a Database DELETE statement and therefore -> bypass any checks for the version column if they are not explicitly added to the WHERE -> clause of the query. Additionally Deletes of specifies entities are *NOT* cascaded -> to related entities even if specified in the metadata. - -## Functions, Operators, Aggregates - - -### DQL Functions - -The following functions are supported in SELECT, WHERE and HAVING clauses: - -* ABS(arithmetic_expression) -* CONCAT(str1, str2) -* CURRENT_DATE() - Return the current date -* CURRENT_TIME() - Returns the current time -* CURRENT_TIMESTAMP() - Returns a timestamp of the current date and time. -* LENGTH(str) - Returns the length of the given string -* LOCATE(needle, haystack [, offset]) - Locate the first occurrence of the substring in the string. -* LOWER(str) - returns the string lowercased. -* MOD(a, b) - Return a MOD b. -* SIZE(collection) - Return the number of elements in the specified collection -* SQRT(q) - Return the square-root of q. -* SUBSTRING(str, start [, length]) - Return substring of given string. -* TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str) - Trim the string by the given trim char, defaults to whitespaces. -* UPPER(str) - Return the upper-case of the given string. - -### Arithmetic operators - -You can do math in DQL using numeric values, for example: - - SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 - -### Aggregate Functions - -The following aggregate functions are allowed in SELECT and GROUP BY clauses: AVG, COUNT, MIN, MAX, SUM - -### Other Expressions - -DQL offers a wide-range of additional expressions that are known from SQL, here is a list of -all the supported constructs: - -* `ALL/ANY/SOME` - Used in a WHERE clause followed by a sub-select this works like the equivalent constructs in SQL. -* `BETWEEN a AND b` and `NOT BETWEEN a AND b` can be used to match ranges of arithmetic values. -* `IN (x1, x2, ...)` and `NOT IN (x1, x2, ..)` can be used to match a set of given values. -* `LIKE ..` and `NOT LIKE ..` match parts of a string or text using % as a wildcard. -* `IS NULL` and `IS NOT NULL` to check for null values -* `EXISTS` and `NOT EXISTS` in combination with a sub-select - -### Adding your own functions to the DQL language - -By default DQL comes with functions that are part of a large basis of underlying databases. However you will most likely -choose a database platform at the beginning of your project and most likely never change it. For this cases you can -easily extend the DQL parser with own specialized platform functions. - -You can register custom DQL functions in your ORM Configuration: - - addCustomStringFunction($name, $class); - $config->addCustomNumericFunction($name, $class); - $config->addCustomDatetimeFunction($name, $class); - - $em = EntityManager::create($dbParams, $config); - -The functions have to return either a string, numeric or datetime value depending on the registered function type. As an example -we will add a MySQL specific FLOOR() functionality. All the given classes have to implement the base class -\Doctrine\ORM\Query\AST\Functions\FunctionNode: - - walkSimpleArithmeticExpression( - $this->simpleArithmeticExpression - ) . ')'; - } - - public function parse(\Doctrine\ORM\Query\Parser $parser) - { - $lexer = $parser->getLexer(); - - $parser->match(Lexer::T_ABS); - $parser->match(Lexer::T_OPEN_PARENTHESIS); - - $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); - - $parser->match(Lexer::T_CLOSE_PARENTHESIS); - } - } - -We will register the function by calling and can then use it: - - setName('test'); - $employee->setDepartment('testing'); - $em->persist($employee); - $em->flush(); - -Now lets run a simple query to retrieve the `Employee` we just created: - - [sql] - SELECT e FROM Entities\Employee e WHERE e.name = 'test' - -If we check the generated SQL you will notice it has some special conditions added to -ensure that we will only get back `Employee` entities: - - [sql] - SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, p0_.discr AS discr3 FROM Person p0_ WHERE (p0_.name = ?) AND p0_.discr IN ('employee') - -### Class Table Inheritance - -[Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html) is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. -Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hierarchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance. - -The example for class table inheritance is the same as single table, you just need to -change the inheritance type from `SINGLE_TABLE` to `JOINED`: - - createQuery('select u from MyProject\Model\User u'); - - // example2: using setDql - $q = $em->createQuery(); - $q->setDql('select u from MyProject\Model\User u'); - -### Query Result Formats - -The format in which the result of a DQL SELECT query is returned can be influenced by a so-called `hydration mode`. A hydration mode specifies a particular way in which an SQL result set is transformed. Each hydration mode has its own dedicated method on the Query class. Here they are: - -* `Query#getResult()`: Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array where the objects are nested in the result rows (mixed). -* `Query#getSingleResult()`: Retrieves a single object. If the result contains more than one object, an exception is thrown. The pure/mixed distinction does not apply. -* `Query#getArrayResult()`: Retrieves an array graph (a nested array) that is largely interchangeable with the object graph generated by `Query#getResultList()` for read-only purposes. - -> **NOTE** -> An array graph can differ from the corresponding object graph in -> certain scenarios due to the difference of the identity semantics between arrays and -> objects. - -* `Query#getScalarResult()`: Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The pure/mixed distinction does not apply. -* `Query#getSingleScalarResult()`: Retrieves a single scalar value from the result returned by the dbms. If the result contains more than a single scalar value, an exception is thrown. The pure/mixed distinction does not apply. - -Instead of using these methods, you can alternatively use the general-purpose method `Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)`. Using this method you can directly supply the hydration mode as the second parameter via one of the Query constants. In fact, the methods mentioned earlier are just convenient shortcuts for the execute method. For example, the method `Query#getResultList()` internally invokes execute, passing in `Query::HYDRATE_OBJECT` as the hydration mode. - -The use of the methods mentioned earlier is generally preferred as it leads to more concise code. - -### Pure and Mixed Results - -The nature of a result returned by a DQL SELECT query retrieved through `Query#getResult()` or `Query#getArrayResult()` can be of 2 forms: **pure** and **mixed**. In the previous simple examples, you already saw a "pure" query result, with only objects. By default, the result type is **pure** but **as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. A mixed result has a different structure than a pure result in order to accommodate for the scalar values. - -A pure result usually looks like this: - - array - [0] => Object - [1] => Object - [2] => Object - ... - -A mixed result on the other hand has the following general structure: - - array - array - [0] => Object - [1] => "some scalar string" - ['count'] => 42 - // ... more scalar values, either indexed numerically or with a name - array - [0] => Object - [1] => "some scalar string" - ['count'] => 42 - // ... more scalar values, either indexed numerically or with a name - -To better understand mixed results, consider the following DQL query: - - [sql] - SELECT u, UPPER(u.name) nameUpper FROM MyProject\Model\User u - -This query makes use of the `UPPER` DQL function that returns a scalar value and because there is now a scalar value in the SELECT clause, we get a mixed result. - -Here is how the result could look like: - - array - array - [0] => User (Object) - ['nameUpper'] => "Roman" - array - [0] => User (Object) - ['nameUpper'] => "Jonathan" - ... - -And here is how you would access it in PHP code: - - getName(); - echo "Name UPPER: " . $row['nameUpper']; - } - -You may have observed that in a mixed result, the object always ends up on index 0 of a result row. - -### Hydration Modes - -Each of the Hydration Modes makes assumptions about how the result is returned to user land. You should -know about all the details to make best use of the different result formats: - -The constants for the different hydration modes are: - -* Query::HYDRATE_OBJECT -* Query::HYDRATE_ARRAY -* Query::HYDRATE_SCALAR -* Query::HYDRATE_SINGLE_SCALAR - -#### Object Hydration - -Object hydration hydrates the result set into the object graph: - - createQuery('SELECT u FROM CmsUser u'); - $users = $query->getResult(Query::HYDRATE_OBJECT); - -#### Array Hydration - -You can run the same query with array hydration and the result set is hydrated into -an array that represents the object graph: - - createQuery('SELECT u FROM CmsUser u'); - $users = $query->getResult(Query::HYDRATE_ARRAY); - -You can use the `getArrayResult()` shortcut as well: - - getArrayResult(); - -#### Scalar Hydration - -If you want to return a flat rectangular result set instead of an object graph -you can use scalar hydration: - - createQuery('SELECT u FROM CmsUser u'); - $users = $query->getResult(Query::HYDRATE_SCALAR); - echo $users[0]['u_id']; - -The following assumptions are made about selected fields using Scalar Hydration: - -1. Fields from classes are prefixed by the DQL alias in the result. A query of the kind 'SELECT u.name ..' returns a key 'u_name' in the result rows. - -#### Single Scalar Hydration - -If you a query which returns just a single scalar value you can use single scalar -hydration: - - createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); - $query->setParameter(1, 'jwage'); - $numArticles = $query->getResult(Query::HYDRATE_SINGLE_SCALAR); - -You can use the `getSingleScalarResult()` shortcut as well: - - getSingleScalarResult(); - -#### Custom Hydration Modes - -You can easily add your own custom hydration modes by first creating a class which extends `AbstractHydrator`: - - _stmt->fetchAll(PDO::FETCH_ASSOC); - } - } - -Next you just need to add the class to the ORM configuration: - - getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); - -Now the hydrator is ready to be used in your queries: - - createQuery('SELECT u FROM CmsUser u'); - $results = $query->getResult('CustomHydrator'); - -### Iterating Large Result Sets - -There are situations when a query you want to execute returns a very large result-set that needs -to be processed. All the previously described hydration modes completely load a result-set into -memory which might not be feasible with large result sets. See the [Batch Processing](batch-processing) -section on details how to iterate large result sets. - -### Functions - -The following methods exist on the `AbstractQuery` which both `Query` and `NativeQuery` extend from. - -#### Parameters - -Prepared Statements that use numerical or named wildcards require additional parameters to be executable -against the database. To pass parameters to the query the following methods can be used: - -* `AbstractQuery::setParameter($param, $value)` - Set the numerical or named wildcard to the given value. -* `AbstractQuery::setParameters(array $params)` - Set an array of parameter key-value pairs. -* `AbstractQuery::getParameter($param)` -* `AbstractQuery::getParameters()` - -#### Cache related API - -You can cache query results based either on all variables that define the result (SQL, Hydration Mode, Parameters and Hints) -or on user-defined cache keys. However by default query results are not cached at all. You have to enable -the result cache on a per query basis. The following example shows a complete workflow using the Result Cache -API: - - createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); - $query->setParameter(1, 12); - - $query->setResultCacheDriver(new ApcCache()); - - $query->useResultCache(true) - ->setResultCacheLifeTime($seconds = 3600); - - $result = $query->getResult(); // cache miss - - $query->expireResultCache(true); - $result = $query->getResult(); // forced expire, cache miss - - $query->setResultCacheId('my_query_result'); - $result = $query->getResult(); // saved in given result cache id. - - // or call useResultCache() with all parameters: - $query->useResultCache(true, $seconds = 3600, 'my_query_result'); - $result = $query->getResult(); // cache hit! - -> **TIP!** -> You can set the Result Cache Driver globally on the `Doctrine\ORM\Configuration` instance -> so that it is passed to every `Query` and `NativeQuery` instance. - -#### Query Hints - -You can pass hints to the query parser and hydrators by using the `AbstractQuery::setHint($name, $value)` method. -Currently there exist mostly internal query hints that are not be consumed in userland. However the following few hints -are to be used in userland: - -* Query::HINT_FORCE_PARTIAL_LOAD - Allows to hydrate objects although not all their columns are fetched. This query -hint can be used to handle memory consumption problems with large result-sets that contain char or binary data. -Doctrine has no way of implicitly reloading this data. Partially loaded objects have to be passed to -`EntityManager::refresh()` if they are to be reloaded fully from the database. -* Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland as well. -If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the -fields of the existing entity will be refreshed. In normal operation a result-set that loads data of an already existing -entity is discarded in favor of the already existing entity. -* Query::HINT_CUSTOM_TREE_WALKERS - An array of additional `Doctrine\ORM\Query\TreeWalker` instances that are attached -to the DQL query parsing process. - -#### Query Cache (DQL Query Only) - -Parsing a DQL query and converting it into an SQL query against the underlying database platform obviously has some overhead -in contrast to directly executing Native SQL queries. That is why there is a dedicated Query Cache for caching the -DQL parser results. In combination with the use of wildcards you can reduce the number of parsed queries in production -to zero. - -The Query Cache Driver is passed from the `Doctrine\ORM\Configuration` instance to each `Doctrine\ORM\Query` instance -by default and is also enabled by default. This also means you don't regularly need to fiddle with the parameters -of the Query Cache, however if you do there are several methods to interact with it: - -* `Query::setQueryCacheDriver($driver)` - Allows to set a Cache instance -* `Query::setQueryCacheLifeTime($seconds = 3600)` - Set lifetime of the query caching. -* `Query::expireQueryCache($bool)` - Enforce the expiring of the query cache if set to true. -* `Query::getExpireQueryCache()` -* `Query::getQueryCacheDriver()` -* `Query::getQueryCacheLifeTime()` - -#### First and Max Result Items (DQL Query Only) - -You can limit the number of results returned from a DQL query as well as specify the starting offset, Doctrine -then uses a strategy of manipulating the select query to return only the requested number of results: - -* `Query::setMaxResults($maxResults)` -* `Query::setFirstResult($offset)` - -> **NOTE** -> If your query contains a fetch-joined collection specifying the result limit methods are not working -> as you would expect. Set Max Results restricts the number of database result rows, however in the -> case of fetch-joined collections one root entity might appear in many rows, effectively hydrating -> less than the specified number of results. - -## EBNF - -The following context-free grammar, written in an EBNF variant, describes the Doctrine Query Language. You can consult this grammar whenever you are unsure about what is possible with DQL or what the correct syntax for a particular query should be. - -### Document syntax: - -* non-terminals begin with an upper case character -* terminals begin with a lower case character -* parentheses (...) are used for grouping -* square brackets [...] are used for defining an optional part, e.g. zero or one time -* curly brackets {...} are used for repetition, e.g. zero or more times -* double quotation marks "..." define a terminal string a vertical bar | represents an alternative - -### Terminals - -* identifier (name, email, ...) -* string ('foo', 'bar''s house', '%ninja%', ...) -* char ('/', '\\', ' ', ...) -* integer (-1, 0, 1, 34, ...) -* float (-0.23, 0.007, 1.245342E+8, ...) -* boolean (false, true) - -### Query Language - - QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement - -### Statements - - SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] - UpdateStatement ::= UpdateClause [WhereClause] - DeleteStatement ::= DeleteClause [WhereClause] - -### Identifiers - - /* Alias Identification usage (the "u" of "u.name") */ - IdentificationVariable ::= identifier - - /* Alias Identification declaration (the "u" of "FROM User u") */ - AliasIdentificationVariable :: = identifier - - /* identifier that must be a class name (the "User" of "FROM User u") */ - AbstractSchemaName ::= identifier - - /* identifier that must be a field (the "name" of "u.name") */ - /* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */ - FieldIdentificationVariable ::= identifier - - /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ - CollectionValuedAssociationField ::= FieldIdentificationVariable - - /* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */ - SingleValuedAssociationField ::= FieldIdentificationVariable - - /* identifier that must be an embedded class state field (for the future) */ - EmbeddedClassStateField ::= FieldIdentificationVariable - - /* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ - /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ - SimpleStateField ::= FieldIdentificationVariable - - /* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */ - AliasResultVariable = identifier - - /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ - ResultVariable = identifier - -### Path Expressions - - /* "u.Group" or "u.Phonenumbers" declarations */ - JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) - - /* "u.Group" or "u.Phonenumbers" usages */ - AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression - - /* "u.name" or "u.Group" */ - SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression - - /* "u.name" or "u.Group.name" */ - StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField - - /* "u.Group" */ - SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField - - /* "u.Group.Permissions" */ - CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField - - /* "name" */ - StateField ::= {EmbeddedClassStateField "."}* SimpleStateField - - /* "u.name" or "u.address.zip" (address = EmbeddedClassStateField) */ - SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField - - -### Clauses - - SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* - SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression - UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* - DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable - FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* - SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* - WhereClause ::= "WHERE" ConditionalExpression - HavingClause ::= "HAVING" ConditionalExpression - GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* - OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* - Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] - - -### Items - - UpdateItem ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue - OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] - GroupByItem ::= IdentificationVariable | SingleValuedPathExpression - NewValue ::= ScalarExpression | SimpleEntityExpression | "NULL" - - -### From, Join and Index by - - IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* - SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) - JoinVariableDeclaration ::= Join [IndexBy] - RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable - Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression - ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] - IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression - - -### Select Expressions - - SelectExpression ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")" | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable] - SimpleSelectExpression ::= ScalarExpression | IdentificationVariable | - (AggregateExpression [["AS"] AliasResultVariable]) - PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet - PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" - - -### Conditional Expressions - - ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* - ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* - ConditionalFactor ::= ["NOT"] ConditionalPrimary - ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" - SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression | - InExpression | NullComparisonExpression | ExistsExpression | - EmptyCollectionComparisonExpression | CollectionMemberExpression - - -### Collection Expressions - - EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" - CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression - -### Literal Values - - Literal ::= string | char | integer | float | boolean - InParameter ::= Literal | InputParameter - -### Input Parameter - - InputParameter ::= PositionalParameter | NamedParameter - PositionalParameter ::= "?" integer - NamedParameter ::= ":" string - - -### Arithmetic Expressions - - ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" - SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* - ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* - ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary - ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" - | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings - | FunctionsReturningDatetime | IdentificationVariable | InputParameter - - -### Scalar and Type Expressions - - ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression - BooleanPrimary | CaseExpression | EntityTypeExpression - CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | - CoalesceExpression | NullifExpression - GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression - "END" - WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression - SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* - "ELSE" ScalarExpression "END" - CaseOperand ::= StateFieldPathExpression | TypeDiscriminator - SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression - CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" - NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" - StringExpression ::= StringPrimary | "(" Subselect ")" - StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression - BooleanExpression ::= BooleanPrimary | "(" Subselect ")" - BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter - EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression - SimpleEntityExpression ::= IdentificationVariable | InputParameter - DatetimeExpression ::= DatetimePrimary | "(" Subselect ")" - DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression - - -### Aggregate Expressions - - AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | - "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" - -### Other Expressions - -QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS - - QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" - BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression - ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) - InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" - LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] - NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" - ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" - ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" - -### Functions - - FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime - - FunctionsReturningNumerics ::= - "LENGTH" "(" StringPrimary ")" | - "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | - "ABS" "(" SimpleArithmeticExpression ")" | "SQRT" "(" SimpleArithmeticExpression ")" | - "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | - "SIZE" "(" CollectionValuedPathExpression ")" - - FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" - - FunctionsReturningStrings ::= - "CONCAT" "(" StringPrimary "," StringPrimary ")" | - "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | - "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | - "LOWER" "(" StringPrimary ")" | - "UPPER" "(" StringPrimary ")" \ No newline at end of file diff --git a/manual/en/events.rst b/manual/en/events.rst index e899e3121..ebdeee81b 100644 --- a/manual/en/events.rst +++ b/manual/en/events.rst @@ -1,3 +1,6 @@ +Events +====== + Doctrine 2 features a lightweight event system that is part of the Common package. @@ -139,10 +142,10 @@ the life-time of their registered entities. - preRemove - The preRemove event occurs for a given entity before the respective EntityManager remove operation for that entity is - executed. + executed. It is not called for a DQL DELETE statement. - postRemove - The postRemove event occurs for an entity after the entity has been deleted. It will be invoked after the database - delete operations. + delete operations. It is not called for a DQL DELETE statement. - prePersist - The prePersist event occurs for a given entity before the respective EntityManager persist operation for that entity is executed. @@ -151,9 +154,9 @@ the life-time of their registered entities. database insert operations. Generated primary key values are available in the postPersist event. - preUpdate - The preUpdate event occurs before the database - update operations to entity data. + update operations to entity data. It is not called for a DQL UPDATE statement. - postUpdate - The postUpdate event occurs after the database - update operations to entity data. + update operations to entity data. It is not called for a DQL UPDATE statement. - postLoad - The postLoad event occurs for an entity after the entity has been loaded into the current EntityManager from the database or after the refresh operation has been applied to it. @@ -164,7 +167,9 @@ the life-time of their registered entities. managed entities are computed. This event is not a lifecycle callback. - **CAUTION** Note that the postLoad event occurs for an entity +.. warning:: + + Note that the postLoad event occurs for an entity before any associations have been initialized. Therefore it is not safe to access associations in a postLoad callback or event handler. @@ -191,7 +196,9 @@ listeners: methods that receives some kind of ``EventArgs`` instance which give access to the entity, EntityManager or other relevant data. - **NOTE** All Lifecycle events that happen during the ``flush()`` of +.. note:: + + All Lifecycle events that happen during the ``flush()`` of an EntityManager have very specific constraints on the allowed operations that can be executed. Please read the *Implementing Event Listeners* section very carefully to understand diff --git a/manual/en/events.txt b/manual/en/events.txt deleted file mode 100644 index a83340f39..000000000 --- a/manual/en/events.txt +++ /dev/null @@ -1,467 +0,0 @@ -Doctrine 2 features a lightweight event system that is part of the Common package. - -## The Event System - -The event system is controlled by the `EventManager`. It is the central point -of Doctrine's event listener system. Listeners are registered on the manager -and events are dispatched through the manager. - - addEventListener(array(self::preFoo, self::postFoo), $this); - } - - public function preFoo(EventArgs $e) - { - $this->preFooInvoked = true; - } - - public function postFoo(EventArgs $e) - { - $this->postFooInvoked = true; - } - } - - // Create a new instance - $test = new EventTest($evm); - -Events can be dispatched by using the `dispatchEvent()` method. - - dispatchEvent(EventTest::preFoo); - $evm->dispatchEvent(EventTest::postFoo); - -You can easily remove a listener with the `removeEventListener()` method. - - removeEventListener(array(self::preFoo, self::postFoo), $this); - -The Doctrine 2 event system also has a simple concept of event subscribers. We -can define a simple `TestEventSubscriber` class which implements the -`\Doctrine\Common\EventSubscriber` interface and implements a `getSubscribedEvents()` -method which returns an array of events it should be subscribed to. - - preFooInvoked = true; - } - - public function getSubscribedEvents() - { - return array(TestEvent::preFoo); - } - } - - $eventSubscriber = new TestEventSubscriber(); - $evm->addEventSubscriber($eventSubscriber); - -Now when you dispatch an event any event subscribers will be notified for that event. - - dispatchEvent(TestEvent::preFoo); - -Now you can test the `$eventSubscriber` instance to see if the `preFoo()` method was invoked. - - preFooInvoked) { - echo 'pre foo invoked!'; - } - -### Naming convention - -Events being used with the Doctrine 2 EventManager are best named with camelcase and the value of the corresponding constant should be the name of the constant itself, even with spelling. This has several reasons: - -* It is easy to read. -* Simplicity. -* Each method within an EventSubscriber is named after the corresponding constant. If constant name and constant value differ, you MUST use the new value and thus, your code might be subject to codechanges when the value changes. This contradicts the intention of a constant. - -An example for a correct notation can be found in the example `EventTest` above. - -## Lifecycle Events - -The EntityManager and UnitOfWork trigger a bunch of events during the life-time of their registered entities. - -* preRemove - The preRemove event occurs for a given entity before the respective EntityManager remove operation for that entity is executed. -* postRemove - The postRemove event occurs for an entity after the entity has been deleted. It will be invoked after the database delete operations. -* prePersist - The prePersist event occurs for a given entity before the respective EntityManager persist operation for that entity is executed. -* postPersist - The postPersist event occurs for an entity after the entity has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event. -* preUpdate - The preUpdate event occurs before the database update operations to entity data. -* postUpdate - The postUpdate event occurs after the database update operations to entity data. -* postLoad - The postLoad event occurs for an entity after the entity has been loaded into the current EntityManager from the database or after the refresh operation has been applied to it. -* loadClassMetadata - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (annotations/xml/yaml). -* onFlush - The onFlush event occurs after the change-sets of all managed entities are computed. This event is not a lifecycle callback. - -> **CAUTION** -> Note that the postLoad event occurs for an entity before any associations have been -> initialized. Therefore it is not safe to access associations in a postLoad callback -> or event handler. - -You can access the Event constants from the `Events` class in the ORM package. - - **NOTE** -> All Lifecycle events that happen during the `flush()` of an EntityManager have very specific constraints on the allowed -> operations that can be executed. Please read the *Implementing Event Listeners* section very carefully to understand -> which operations are allowed in which lifecycle event. - -## Lifecycle Callbacks - -A lifecycle event is a regular event with the additional feature of providing -a mechanism to register direct callbacks inside the corresponding entity classes -that are executed when the lifecycle event occurs. - - createdAt = date('Y-m-d H:m:s'); - } - - /** @PrePersist */ - public function doOtherStuffOnPrePersist() - { - $this->value = 'changed from prePersist callback!'; - } - - /** @PostPersist */ - public function doStuffOnPostPersist() - { - $this->value = 'changed from postPersist callback!'; - } - - /** @PostLoad */ - public function doStuffOnPostLoad() - { - $this->value = 'changed from postLoad callback!'; - } - - /** @PreUpdate */ - public function doStuffOnPreUpdate() - { - $this->value = 'changed from preUpdate callback!'; - } - } - -Note that when using annotations you have to apply the @HasLifecycleCallbacks marker annotation on the entity class. - -If you want to register lifecycle callbacks from YAML or XML you can do it with -the following. - - [yml] - User: - type: entity - fields: - # ... - name: - type: string(50) - lifecycleCallbacks: - doStuffOnPrePersist: prePersist - doStuffOnPostPersist: postPersist - -XML would look something like this: - - [xml] - - - - - - - - - - - - - - - -You just need to make sure a public `doStuffOnPrePersist()` and `doStuffOnPostPersist()` method is defined on your `User` model. - - addEventListener(array(Events::preUpdate), MyEventListener()); - $eventManager->addEventSubscriber(new MyEventSubscriber()); - - $entityManager = EntityManager::create($dbOpts, $config, $eventManager); - -You can also retrieve the event manager instance after the EntityManager was created: - - getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener()); - $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); - -## Implementing Event Listeners - -This section explains what is and what is not allowed during specific lifecycle events of the UnitOfWork. -Although you get passed the EntityManager in all of these events, you have to follow this restrictions very -carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and -lost updates/persists/removes. - -For the described events that are also lifecycle callback events the restrictions -apply as well, with the additional restriction that you do not have access to the EntityManager -or UnitOfWork APIs inside these events. - -### prePersist - -There are two ways for the `prePersist` event to be triggered. One is obviously -when you call `EntityManager#persist()`. The event is also called for all -cascaded associations. - -There is another way for `prePersist` to be called, inside the `flush()` -method when changes to associations are computed and this association -is marked as cascade persist. Any new entity found during this operation -is also persisted and `prePersist` called on it. This is called "persistence by reachability". - -In both cases you get passed a `LifecycleEventArgs` -instance which has access to the entity and the entity manager. - -The following restrictions apply to `prePersist`: - -* If you are using a PrePersist Identity Generator such as sequences the ID value - will *NOT* be available within any PrePersist events. -* Doctrine will not recognize changes made to relations in a pre persist event - called by "reachability" through a cascade persist unless you use the internal - `UnitOfWork` API. We do not recommend such operations in the persistence by - reachability context, so do this at your own risk and possibly supported by unit-tests. - -### preRemove - -The `preRemove` event is called on every entity when its passed to -the `EntityManager#remove()` method. It is cascaded for all -associations that are marked as cascade delete. - -There are no restrictions to what methods can be called inside -the `preRemove` event, except when the remove method itself was -called during a flush operation. - -### onFlush - -OnFlush is a very powerful event. It is called inside `EntityManager#flush()` -after the changes to all the managed entities and their associations have -been computed. This means, the `onFlush` event has access to the sets of: - -* Entities scheduled for insert -* Entities scheduled for update -* Entities scheduled for removal -* Collections scheduled for update -* Collections scheduled for removal - -To make use of the onFlush event you have to be familiar with the internal UnitOfWork API, -which grants you access to the previously mentioned sets. See this example: - - getEntityManager(); - $uow = $em->getUnitOfWork(); - - foreach ($uow->getScheduledEntityInsertions() AS $entity) { - - } - - foreach ($uow->getScheduledEntityUpdates() AS $entity) { - - } - - foreach ($uow->getScheduledEntityDeletions() AS $entity) { - - } - - foreach ($uow->getScheduledCollectionDeletions() AS $col) { - - } - - foreach ($uow->getScheduledCollectionUpdates() AS $col) { - - } - } - } - -The following restrictions apply to the onFlush event: - -* Calling `EntityManager#persist()` does not suffice to trigger a persist on an entity. - You have to execute an additional call to `$unitOfWork->computeChangeSet($classMetadata, $entity)`. -* Changing primitive fields or associations requires you to explicitly trigger - a re-computation of the changeset of the affected entity. This can be done - by either calling `$unitOfWork->computeChangeSet($classMetadata, $entity)` - or `$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)`. The second - method has lower overhead, but only re-computes primitive fields, never associations. - -### preUpdate - -PreUpdate is the most restrictive to use event, since it is called right before -an update statement is called for an entity inside the `EntityManager#flush()` -method. - -Changes to associations of the updated entity are never allowed in this event, since Doctrine cannot guarantee to -correctly handle referential integrity at this point of the flush operation. This -event has a powerful feature however, it is executed with a `PreUpdateEventArgs` -instance, which contains a reference to the computed change-set of this entity. - -This means you have access to all the fields that have changed for this entity -with their old and new value. The following methods are available on the `PreUpdateEventArgs`: - -* `getEntity()` to get access to the actual entity. -* `getEntityChangeSet()` to get a copy of the changeset array. Changes to this returned array do not affect updating. -* `hasChangedField($fieldName)` to check if the given field name of the current entity changed. -* `getOldValue($fieldName)` and `getNewValue($fieldName)` to access the values of a field. -* `setNewValue($fieldName, $value)` to change the value of a field to be updated. - -A simple example for this event looks like: - - getEntity() instanceof User) { - if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') { - $eventArgs->setNewValue('name', 'Bob'); - } - } - } - } - -You could also use this listener to implement validation of all the fields that have changed. -This is more efficient than using a lifecycle callback when there are expensive validations -to call: - - getEntity() instanceof Account) { - if ($eventArgs->hasChangedField('creditCard')) { - $this->validateCreditCard($eventArgs->getNewValue('creditCard')); - } - } - } - - private function validateCreditCard($no) - { - // throw an exception to interrupt flush event. Transaction will be rolled back. - } - } - -Restrictions for this event: - -* Changes to associations of the passed entities are not recognized by the flush operation anymore. -* Changes to fields of the passed entities are not recognized by the flush operation anymore, use the computed change-set passed to the event to modify primitive field values. -* Any calls to `EntityManager#persist()` or `EntityManager#remove()`, even in combination with the UnitOfWork API are strongly discouraged and don't work as expected outside the flush operation. - -### postUpdate, postRemove, postPersist - -The three post events are called inside `EntityManager#flush()`. Changes in here -are not relevant to the persistence in the database, but you can use this events -to - -### postLoad - -This event is called after an entity is constructed by the EntityManager. - -## Load ClassMetadata Event - -When the mapping information for an entity is read, it is populated in to a -`ClassMetadataInfo` instance. You can hook in to this process and manipulate -the instance. - - getMetadataFactory(); - $evm = $em->getEventManager(); - $evm->addEventListener(Events::loadClassMetadata, $test); - - class EventTest - { - public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) - { - $classMetadata = $eventArgs->getClassMetadata(); - $fieldMapping = array( - 'fieldName' => 'about', - 'type' => 'string', - 'length' => 255 - ); - $classMetadata->mapField($fieldMapping); - } - } diff --git a/manual/en/improving-performance.rst b/manual/en/improving-performance.rst index 2d9ffb57f..eee5b4b9b 100644 --- a/manual/en/improving-performance.rst +++ b/manual/en/improving-performance.rst @@ -1,3 +1,6 @@ +Improving Performance +===================== + Bytecode Cache -------------- @@ -5,7 +8,9 @@ It is highly recommended to make use of a bytecode cache like APC. A bytecode cache removes the need for parsing PHP code on every request and can greatly improve performance. - **NOTE** "If you care about performance and don't use a bytecode +.. note:: + + "If you care about performance and don't use a bytecode cache then you don't really care about performance. Please get one and start using it." (Stas Malyshev, Core Contributor to PHP and Zend Employee). diff --git a/manual/en/improving-performance.txt b/manual/en/improving-performance.txt deleted file mode 100644 index 01509bb81..000000000 --- a/manual/en/improving-performance.txt +++ /dev/null @@ -1,25 +0,0 @@ - -## Bytecode Cache - -It is highly recommended to make use of a bytecode cache like APC. A bytecode cache removes the need for parsing PHP code on every request and can greatly improve performance. - -> **NOTE** -> "If you care about performance and don't use a bytecode cache then you don't really care -> about performance. Please get one and start using it." (Stas Malyshev, Core Contributor -> to PHP and Zend Employee). - - -## Metadata and Query caches - -As already mentioned earlier in the chapter about configuring Doctrine, it is strongly discouraged to use Doctrine without a Metadata and Query cache (preferably with APC or Memcache as the cache driver). Operating Doctrine without these caches means Doctrine will need to load your mapping information on every single request and has to parse each DQL query on every single request. This is a waste of resources. - -## Alternative Query Result Formats - -Make effective use of the available alternative query result formats like nested array graphs or pure scalar results, especially in scenarios where data is loaded for read-only purposes. - -## Apply Best Practices - -A lot of the points mentioned in the Best Practices chapter will also positively affect the performance of Doctrine. - - - diff --git a/manual/en/index.rst b/manual/en/index.rst index af4ddee9a..91eb8f6b6 100644 --- a/manual/en/index.rst +++ b/manual/en/index.rst @@ -11,6 +11,33 @@ Contents: .. toctree:: :maxdepth: 2 + introduction + architecture + configuration + basic-mapping + association-mapping + inheritance-mapping + working-with-objects + working-with-associations + transactions-and-concurrency + events + batch-processing + dql-doctrine-query-language + query-builder + native-sql + change-tracking-policies + partial-objects + xml-mapping + yaml-mapping + annotations-reference + php-mapping + caching + improving-performance + tools + metadata-drivers + best-practices + limitations-and-Known-issues + Indices and tables ================== diff --git a/manual/en/inheritance-mapping.rst b/manual/en/inheritance-mapping.rst index f950577c5..3b226aad4 100644 --- a/manual/en/inheritance-mapping.rst +++ b/manual/en/inheritance-mapping.rst @@ -1,3 +1,6 @@ +Inheritance Mapping +=================== + Mapped Superclasses ------------------- @@ -11,7 +14,7 @@ Mapped superclasses, just as regular, non-mapped classes, can appear in the middle of an otherwise mapped inheritance hierarchy (through Single Table Inheritance or Class Table Inheritance). - **NOTE** +.. note:: A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped superclass must be @@ -54,9 +57,8 @@ Example: The DDL for the corresponding database schema would look something like this (this is for SQLite): -:: +.. code-block:: sql - [sql] CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id)) As you can see from this DDL snippet, there is only a single table @@ -202,7 +204,9 @@ Things to note: namespace as the entity class on which the discriminator map is applied. - **NOTE** When you do not use the SchemaTool to generate the +.. note:: + + When you do not use the SchemaTool to generate the required SQL you should know that deleting a class table inheritance makes use of the foreign key property ``ON DELETE CASCADE`` in all database implementations. A failure to diff --git a/manual/en/inheritance-mapping.txt b/manual/en/inheritance-mapping.txt deleted file mode 100644 index b1ed480c4..000000000 --- a/manual/en/inheritance-mapping.txt +++ /dev/null @@ -1,159 +0,0 @@ -## Mapped Superclasses - -An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information -for its subclasses, but which is not itself an entity. Typically, the purpose of such a mapped superclass is to define -state and mapping information that is common to multiple entity classes. - -Mapped superclasses, just as regular, non-mapped classes, can appear in the middle of an otherwise -mapped inheritance hierarchy (through Single Table Inheritance or Class Table Inheritance). - -> **NOTE** -> -> A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped -> superclass must be unidirectional. For further support of inheritance, the single or joined table inheritance -> features have to be used. - -Example: - - **NOTE** -> When you do not use the SchemaTool to generate the required SQL you should know that deleting a class table inheritance -> makes use of the foreign key property `ON DELETE CASCADE` in all database implementations. A failure to implement this -> yourself will lead to dead rows in the database. - -### Design-time considerations - -Introducing a new type to the hierarchy, at any level, simply involves interjecting a new table into the schema. Subtypes of that type will automatically join with that new type at runtime. Similarly, modifying any entity type in the hierarchy by adding, modifying or removing fields affects only the immediate table mapped to that type. This mapping strategy provides the greatest flexibility at design time, since changes to any type are always limited to that type's dedicated table. - -### Performance impact - -This strategy inherently requires multiple JOIN operations to perform just about any query which can have a negative impact on performance, especially with large tables and/or large hierarchies. When partial objects are allowed, either globally or on the specific query, then querying for any type will not cause the tables of subtypes to be OUTER JOINed which can increase performance but the resulting partial objects will not fully load themselves on access of any subtype fields, so accessing fields of subtypes after such a query is not safe. - -There is a general performance consideration with Class Table Inheritance: If you use a CTI entity as a many-to-one or one-to-one entity you should never use one of the classes at the upper levels of the inheritance hierachy as "targetEntity", only those that have no subclasses. Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly. - -### SQL Schema considerations - -For each entity in the Class-Table Inheritance hierarchy all the mapped fields have to be columns on the -table of this entity. Additionally each child table has to have an id column that matches the id column -definition on the root table (except for any sequence or auto-increment details). Furthermore each child table has -to have a foreign key pointing from the id column to the root table id column and cascading on delete. diff --git a/manual/en/introduction.txt b/manual/en/introduction.txt deleted file mode 100644 index 4515ead00..000000000 --- a/manual/en/introduction.txt +++ /dev/null @@ -1,309 +0,0 @@ -## Welcome - -Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides -transparent persistence for PHP objects. It sits on top of a powerful database -abstraction layer (DBAL). Object-Relational Mappers primary task is the transparent -translation between (PHP) objects and relational database rows. - -One of Doctrines key features is the option to write database queries in a -proprietary object oriented SQL dialect called Doctrine -Query Language (DQL), inspired by Hibernates HQL. Besides DQLs slight -differences to SQL it abstracts the mapping between database rows and -objects considerably, allowing developers to write powerful queries -in a simple and flexible fashion. - -## Disclaimer - -This is the Doctrine 2 reference documentation. Introductory guides and tutorials -that you can follow along from start to finish, like the "Guide to Doctrine" book -known from the Doctrine 1.x series, will be available at a later date. - -## Using an Object-Relational Mapper - -As the term ORM already hints at, Doctrine 2 aims to simplify the translation -between database rows and the PHP object model. The primary use case for Doctrine -are therefore applications that utilize the Object-Oriented Programming Paradigm. -For applications that not primarily work with objects Doctrine 2 is not suited very well. - -## Requirements - -Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved performance it -is also recommended that you use APC with PHP. - -## Doctrine 2 Packages - -Doctrine 2 is divided into three main packages. - -* Common -* DBAL (includes Common) -* ORM (includes DBAL+Common) - -This manual mainly covers the ORM package, sometimes touching parts of the -underlying DBAL and Common packages. The Doctrine code base is split in to these -packages for a few reasons and they are to... - -* ...make things more maintainable and decoupled -* ...allow you to use the code in Doctrine Common without the ORM or DBAL -* ...allow you to use the DBAL without the ORM - -### The Common Package - -The Common package contains highly reusable components that have no dependencies -beyond the package itself (and PHP, of course). The root namespace of the -Common package is `Doctrine\Common`. - -### The DBAL Package - -The DBAL package contains an enhanced database abstraction layer on top of -PDO but is not strongly bound to PDO. The purpose of this layer is to provide a -single API that bridges most of the differences between the different RDBMS vendors. -The root namespace of the DBAL package is `Doctrine\DBAL`. - -### The ORM Package - -The ORM package contains the object-relational mapping toolkit that provides -transparent relational persistence for plain PHP objects. The root namespace of -the ORM package is `Doctrine\ORM`. - -## Installing - -Doctrine can be installed many different ways. We will describe all the different -ways and you can choose which one suits you best. - -### PEAR - -You can easily install any of the three Doctrine packages from the PEAR command -line installation utility. - -To install just the `Common` package you can run the following command: - - $ sudo pear install pear.doctrine-project.org/DoctrineCommon- - -If you want to use the Doctrine Database Abstraction Layer you can install it -with the following command. - - $ sudo pear install pear.doctrine-project.org/DoctrineDBAL- - -Or, if you want to get the works and go for the ORM you can install it with the -following command. - - $ sudo pear install pear.doctrine-project.org/DoctrineORM- - -> **NOTE** -> The `` tag above represents the version you want to install. For example the -> current version at the time of writing this is `2.0.0BETA3` for the ORM, so you could -> install it like the following: -> -> $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.0BETA3 - -When you have a package installed via PEAR you can require and load the -`ClassLoader` with the following code. - - **NOTE** -> -> You should not combine the Doctrine-Common, Doctrine-DBAL and Doctrine-ORM master commits -> with each other in combination. The ORM may not work with the current Common or DBAL master versions. -> Instead the ORM ships with the Git Submodules that are required. - -### Subversion - -> **NOTE** -> -> Using the SVN Mirror is not recommended. It only allows access to the latest master commit -> and does not automatically fetch the submodules. - -If you prefer subversion you can also checkout the code from GitHub.com through -the subversion protocol: - - $ svn co http://svn.github.com/doctrine/doctrine2.git doctrine2 - -However this only allows you to check out the current master of Doctrine 2, without -the Common and DBAL dependencies. You have to grab them yourself, but might run -into version incompatibilities between the different master branches of Common, DBAL -and ORM. - -## Sandbox Quickstart - -> **NOTE** -> The sandbox is only available via the Doctrine2 Github Repository or soon as a separate download on the downloads -> page. You will find it in the $root/tools/sandbox folder. - -The sandbox is a pre-configured environment for evaluating and playing -with Doctrine 2. - -### Overview - -After navigating to the sandbox directory, you should see the following structure: - - sandbox/ - Entities/ - Address.php - User.php - xml/ - Entities.Address.dcm.xml - Entities.User.dcm.xml - yaml/ - Entities.Address.dcm.yml - Entities.User.dcm.yml - cli-config.php - doctrine - doctrine.php - index.php - -Here is a short overview of the purpose of these folders and files: - - * The `Entities` folder is where any model classes are created. Two example entities are already there. - * The `xml` folder is where any XML mapping files are created (if you want to use XML mapping). Two example mapping documents for the 2 example entities are already there. - * The `yaml` folder is where any YAML mapping files are created (if you want to use YAML mapping). Two example mapping documents for the 2 example entities are already there. - * The `cli-config.php` contains bootstrap code for a configuration that is used by the Console tool `doctrine` whenever you execute a task. - * `doctrine`/`doctrine.php` is a command-line tool. - * `index.php` is a basic classical bootstrap file of a php application that uses Doctrine 2. - -### Mini-tutorial - -1) From within the tools/sandbox folder, run the following command and you should -see the same output. - - $ php doctrine orm:schema-tool:create - Creating database schema... - Database schema created successfully! - -2) Take another look into the tools/sandbox folder. A SQLite database should -have been created with the name `database.sqlite`. - -3) Open `index.php` and at the bottom edit it so it looks like the following: - - setName('Garfield'); - $em->persist($user); - $em->flush(); - - echo "User saved!"; - -Open index.php in your browser or execute it on the command line. You should see -the output "User saved!". - -4) Inspect the SQLite database. Again from within the tools/sandbox folder, -execute the following command: - - $ php doctrine dbal:run-sql "select * from users" - -You should get the following output: - - array(1) { - [0]=> - array(2) { - ["id"]=> - string(1) "1" - ["name"]=> - string(8) "Garfield" - } - } - -You just saved your first entity with a generated ID in an SQLite database. - -5) Replace the contents of index.php with the following: - - createQuery('select u from Entities\User u where u.name = ?1'); - $q->setParameter(1, 'Garfield'); - $garfield = $q->getSingleResult(); - - echo "Hello " . $garfield->getName() . "!"; - -You just created your first DQL query to retrieve the user with the name -'Garfield' from an SQLite database (Yes, there is an easier way to do it, -but we wanted to introduce you to DQL at this point. Can you **find** the easier way?). - -> **TIP** -> When you create new model classes or alter existing ones you can recreate the database -> schema with the command `doctrine orm:schema-tool --drop` followed by -> `doctrine orm:schema-tool --create`. - -6) Explore Doctrine 2! - -See the following links if you want to start with more complex tutorials rather than reading the manual: - -* Doctrine2 Cookbook: [Getting Started XML Edition](http://www.doctrine-project.org/projects/orm/2.0/docs/cookbook/getting-started-xml-edition/en) diff --git a/manual/en/limitations-and-known-issues.rst b/manual/en/limitations-and-known-issues.rst index 78072272c..8631c2d7d 100644 --- a/manual/en/limitations-and-known-issues.rst +++ b/manual/en/limitations-and-known-issues.rst @@ -1,3 +1,6 @@ +Limitations and Known Issues +============================ + We try to make using Doctrine2 a very pleasant experience. Therefore we think it is very important to be honest about the current limitations to our users. Much like every other piece of @@ -21,9 +24,8 @@ 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: -:: +.. code-block:: sql - [sql] CREATE TABLE product ( id INTEGER, name VARCHAR, @@ -41,9 +43,8 @@ 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``. -:: +.. code-block:: sql - [sql] CREATE TABLE product_attributes ( attribute_id, INTEGER, product_id INTEGER, @@ -109,9 +110,8 @@ 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: -:: +.. code-block:: sql - [sql] CREATE TABLE product ( id INTEGER, name VARCHAR, diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt deleted file mode 100644 index d65a8516d..000000000 --- a/manual/en/limitations-and-known-issues.txt +++ /dev/null @@ -1,203 +0,0 @@ -We try to make using Doctrine2 a very pleasant experience. Therefore we think it is very important -to be honest about the current limitations to our users. -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 critical known issues that -you should know about. - -## 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): - - **TIP** -> The populated `ClassMetadata` instances are also cached so in a production -> environment the parsing and populating only ever happens once. You can configure -> the metadata cache implementation using the `setMetadataCacheImpl()` method on -> the `Doctrine\ORM\Configuration` class: -> -> $em->getConfiguration()->setMetadataCacheImpl(new ApcCache()); - -If you want to use one of the included core metadata drivers you just need to -configure it. All the drivers are in the `Doctrine\ORM\Mapping\Driver` namespace: - - getConfiguration()->setMetadataDriverImpl($driver); - -## Implementing Metadata Drivers - -In addition to the included metadata drivers you can very easily implement -your own. All you need to do is define a class which implements the `Driver` -interface: - - _loadMappingFile($file); - - // populate ClassMetadataInfo instance from $data - } - - /** - * {@inheritdoc} - */ - protected function _loadMappingFile($file) - { - // parse contents of $file and return php data structure - } - } - -> **NOTE** -> When using the `AbstractFileDriver` it requires that you only have one entity -> defined per file and the file named after the class described inside where -> namespace separators are replaced by periods. So if you have an entity named -> `Entities\User` and you wanted to write a mapping file for your driver above -> you would need to name the file `Entities.User.dcm.ext` for it to be recognized. - -Now you can use your `MyMetadataDriver` implementation by setting it with the -`setMetadataDriverImpl()` method: - - getConfiguration()->setMetadataDriverImpl($driver); - -## ClassMetadata - -The last piece you need to know and understand about metadata in Doctrine 2 is -the API of the `ClassMetadata` classes. You need to be familiar with them in order -to implement your own drivers but more importantly to retrieve mapping information -for a certain entity when needed. - -You have all the methods you need to manually specify the mapping information -instead of using some mapping file to populate it from. The base `ClassMetadataInfo` -class is responsible for only data storage and is not meant for runtime use. It -does not require that the class actually exists yet so it is useful for describing some -entity before it exists and using that information to generate for example the -entities themselves. The class `ClassMetadata` extends `ClassMetadataInfo` and -adds some functionality required for runtime usage and requires that the PHP -class is present and can be autoloaded. - -You can read more about the API of the `ClassMetadata` classes in the PHP Mapping -chapter. - -## Getting ClassMetadata Instances - -If you want to get the `ClassMetadata` instance for an entity in your project -to programatically use some mapping information to generate some HTML or something -similar you can retrieve it through the `ClassMetadataFactory`: - - getMetadataFactory(); - $class = $cmf->getMetadataFor('MyEntityName'); - -Now you can learn about the entity and use the data stored in the `ClassMetadata` -instance to get all mapped fields for example and iterate over them: - - fieldMappings as $fieldMapping) { - echo $fieldMapping['fieldName'] . "\n"; - } \ No newline at end of file diff --git a/manual/en/native-sql.rst b/manual/en/native-sql.rst index 059168713..992eefc06 100644 --- a/manual/en/native-sql.rst +++ b/manual/en/native-sql.rst @@ -1,3 +1,6 @@ +Native SQL +========== + A ``NativeQuery`` lets you execute native SQL, mapping the results according to your specifications. Such a specification that describes how an SQL result set is mapped to a Doctrine result is @@ -44,7 +47,9 @@ components: root entities or joined entities must be present in the SQL query and mapped accordingly using ``ResultSetMapping#addMetaResult``. - **TIP** It might not surprise you that Doctrine uses +.. note:: + + It might not surprise you that Doctrine uses ``ResultSetMapping``s internally when you create DQL queries. As the query gets parsed and transformed to SQL, Doctrine fills a ``ResultSetMapping`` that describes how the results should be diff --git a/manual/en/native-sql.txt b/manual/en/native-sql.txt deleted file mode 100644 index 867bc523c..000000000 --- a/manual/en/native-sql.txt +++ /dev/null @@ -1,242 +0,0 @@ -A `NativeQuery` lets you execute native SQL, mapping the results according to your specifications. -Such a specification that describes how an SQL result set is mapped to a Doctrine result is -represented by a `ResultSetMapping`. It describes how each column of the database result should -be mapped by Doctrine in terms of the object graph. This allows you to map arbitrary SQL code to objects, such as -highly vendor-optimized SQL or stored-procedures. - -## The NativeQuery class - -To create a `NativeQuery` you use the method `EntityManager#createNativeQuery($sql, $resultSetMapping)`. As you can see in the signature of this method, it expects 2 ingredients: The SQL you want to execute and the `ResultSetMapping` that describes how the results will be mapped. - -Once you obtained an instance of a `NativeQuery`, you can bind parameters to it and finally execute it. - -## The ResultSetMapping - -Understanding the `ResultSetMapping` is the key to using a `NativeQuery`. -A Doctrine result can contain the following components: - - * Entity results. These represent root result elements. - * Joined entity results. These represent joined entities in associations of root entity results. - * Field results. These represent a column in the result set that maps to a field of an entity. A field result always belongs to an entity result or joined entity result. - * Scalar results. These represent scalar values in the result set that will appear in each result row. Adding scalar results to a ResultSetMapping can also cause the overall result to become **mixed** (see DQL - Doctrine Query Language) if the same ResultSetMapping also contains entity results. - * Meta results. These represent columns that contain meta-information, such as foreign keys and discriminator columns. - When querying for objects (`getResult()`), all meta columns of root entities or joined entities must be present in the SQL query - and mapped accordingly using `ResultSetMapping#addMetaResult`. - -> **TIP** -> It might not surprise you that Doctrine uses `ResultSetMapping`s internally when you -> create DQL queries. As the query gets parsed and transformed to SQL, Doctrine fills -> a `ResultSetMapping` that describes how the results should be processed by the hydration -> routines. - -We will now look at each of the result types that can appear in a ResultSetMapping in detail. - -### Entity results - -An entity result describes an entity type that appears as a root element in the transformed result. You add an entity result through `ResultSetMapping#addEntityResult()`. -Let's take a look at the method signature in detail: - - addEntityResult('User', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - - $query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm); - $query->setParameter(1, 'romanb'); - - $users = $query->getResult(); - -The result would look like this: - - array( - [0] => User (Object) - ) - -Note that this would be a partial object if the entity has more fields than just id and name. In the example above the column and field names are identical but that is not necessary, of course. Also note that the query string passed to createNativeQuery is **real native SQL**. Doctrine does not touch this SQL in any way. - -In the previous basic example, a User had no relations and the table the class is mapped to owns no foreign keys. -The next example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress, -where the User is the owning side and thus owns the foreign key. - - addEntityResult('User', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - $rsm->addMetaResult('u', 'address_id', 'address_id'); - - $query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm); - $query->setParameter(1, 'romanb'); - - $users = $query->getResult(); - -Foreign keys are used by Doctrine for lazy-loading purposes when querying for objects. -In the previous example, each user object in the result will have a proxy (a "ghost") in place -of the address that contains the address_id. When the ghost proxy is accessed, it loads itself -based on this key. - -Consequently, associations that are *fetch-joined* do not require the foreign keys to be present -in the SQL result set, only associations that are lazy. - - addEntityResult('User', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - $rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address'); - $rsm->addFieldResult('a', 'address_id', 'id'); - $rsm->addFieldResult('a', 'street', 'street'); - $rsm->addFieldResult('a', 'city', 'city'); - - $sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' . - 'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?'; - $query = $this->_em->createNativeQuery($sql, $rsm); - $query->setParameter(1, 'romanb'); - - $users = $query->getResult(); - -In this case the nested entity `Address` is registered with the `ResultSetMapping#addJoinedEntityResult` -method, which notifies Doctrine that this entity is not hydrated at the root level, but as a joined entity -somewhere inside the object graph. In this case we specify the alias 'u' as third parameter and `address` -as fourth parameter, which means the `Address` is hydrated into the `User::$address` property. - -If a fetched entity is part of a mapped hierarchy that requires a discriminator column, this -column must be present in the result set as a meta column so that Doctrine can create the -appropriate concrete type. This is shown in the following example where we assume that there -are one or more subclasses that extend User and either Class Table Inheritance or Single Table Inheritance -is used to map the hierarchy (both use a discriminator column). - - addEntityResult('User', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - $rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column - $rsm->setDiscriminatorColumn('u', 'discr'); - - $query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm); - $query->setParameter(1, 'romanb'); - - $users = $query->getResult(); - -Note that in the case of Class Table Inheritance, an example as above would result in partial objects -if any objects in the result are actually a subtype of User. When using DQL, Doctrine automatically -includes the necessary joins for this mapping strategy but with native SQL it is your responsibility. \ No newline at end of file diff --git a/manual/en/partial-objects.rst b/manual/en/partial-objects.rst index e2d353af2..f6a6a17c0 100644 --- a/manual/en/partial-objects.rst +++ b/manual/en/partial-objects.rst @@ -1,10 +1,15 @@ +Partial Objects +=============== + 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 +.. 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()``, diff --git a/manual/en/partial-objects.txt b/manual/en/partial-objects.txt deleted file mode 100644 index 83e89b233..000000000 --- a/manual/en/partial-objects.txt +++ /dev/null @@ -1,49 +0,0 @@ -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 don't 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: - - 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 diff --git a/manual/en/php-mapping.rst b/manual/en/php-mapping.rst index 4a0326524..21fb33fec 100644 --- a/manual/en/php-mapping.rst +++ b/manual/en/php-mapping.rst @@ -1,3 +1,6 @@ +PHP Mapping +=========== + Doctrine 2 also allows you to provide the ORM metadata in the form of plain PHP code using the ``ClassMetadata`` API. You can write the code in PHP files or inside of a static function named diff --git a/manual/en/php-mapping.txt b/manual/en/php-mapping.txt deleted file mode 100644 index 05ea41ca3..000000000 --- a/manual/en/php-mapping.txt +++ /dev/null @@ -1,199 +0,0 @@ -Doctrine 2 also allows you to provide the ORM metadata in the form of plain -PHP code using the `ClassMetadata` API. You can write the code in PHP files or -inside of a static function named `loadMetadata($class)` on the entity class itself. - -## PHP Files - -If you wish to write your mapping information inside PHP files that are named -after the entity and included to populate the metadata for an entity you can do -so by using the `PHPDriver`: - - getConfiguration()->setMetadataDriverImpl($driver); - -Now imagine we had an entity named `Entities\User` and we wanted to write a mapping -file for it using the above configured `PHPDriver` instance: - - mapField(array( - 'id' => true, - 'fieldName' => 'id', - 'type' => 'integer' - )); - - $metadata->mapField(array( - 'fieldName' => 'username', - 'type' => 'string' - )); - -Now we can easily retrieve the populated `ClassMetadata` instance where the `PHPDriver` -includes the file and the `ClassMetadataFactory` caches it for later retrieval: - - getMetadataFor('Entities\User'); - -## Static Function - -In addition to the PHP files you can also specify your mapping information inside -of a static function defined on the entity class itself. This is useful for cases -where you want to keep your entity and mapping information together but don't want -to use annotations. For this you just need to use the `StaticPHPDriver`: - - getConfiguration()->setMetadataDriverImpl($driver); - -Now you just need to define a static function named `loadMetadata($metadata)` on your entity: - - mapField(array( - 'id' => true, - 'fieldName' => 'id', - 'type' => 'integer' - )); - - $metadata->mapField(array( - 'fieldName' => 'username', - 'type' => 'string' - )); - } - } - -## ClassMetadataInfo API - -The `ClassMetadataInfo` class is the base data object for storing the mapping -metadata for a single entity. It contains all the getters and setters you need -populate and retrieve information for an entity. - -### General Setters - - * `setTableName($tableName)` - * `setPrimaryTable(array $primaryTableDefinition)` - * `setCustomRepositoryClass($repositoryClassName)` - * `setIdGeneratorType($generatorType)` - * `setIdGenerator($generator)` - * `setSequenceGeneratorDefinition(array $definition)` - * `setChangeTrackingPolicy($policy)` - * `setIdentifier(array $identifier)` - -### Inheritance Setters - - * `setInheritanceType($type)` - * `setSubclasses(array $subclasses)` - * `setParentClasses(array $classNames)` - * `setDiscriminatorColumn($columnDef)` - * `setDiscriminatorMap(array $map)` - -### Field Mapping Setters - - * `mapField(array $mapping)` - * `mapOneToOne(array $mapping)` - * `mapOneToMany(array $mapping)` - * `mapManyToOne(array $mapping)` - * `mapManyToMany(array $mapping)` - -### Lifecycle Callback Setters - - * `addLifecycleCallback($callback, $event)` - * `setLifecycleCallbacks(array $callbacks)` - -### Versioning Setters - - * `setVersionMapping(array &$mapping)` - * `setVersioned($bool)` - * `setVersionField()` - -### General Getters - - * `getTableName()` - * `getTemporaryIdTableName()` - -### Identifier Getters - - * `getIdentifierColumnNames()` - * `usesIdGenerator()` - * `isIdentifier($fieldName)` - * `isIdGeneratorIdentity()` - * `isIdGeneratorSequence()` - * `isIdGeneratorTable()` - * `isIdentifierNatural()` - * `getIdentifierFieldNames()` - * `getSingleIdentifierFieldName()` - * `getSingleIdentifierColumnName()` - -### Inheritance Getters - - * `isInheritanceTypeNone()` - * `isInheritanceTypeJoined()` - * `isInheritanceTypeSingleTable()` - * `isInheritanceTypeTablePerClass()` - * `isInheritedField($fieldName)` - * `isInheritedAssociation($fieldName)` - -### Change Tracking Getters - - * `isChangeTrackingDeferredExplicit()` - * `isChangeTrackingDeferredImplicit()` - * `isChangeTrackingNotify()` - -### Field & Association Getters - - * `isUniqueField($fieldName)` - * `isNullable($fieldName)` - * `getColumnName($fieldName)` - * `getFieldMapping($fieldName)` - * `getAssociationMapping($fieldName)` - * `getAssociationMappings()` - * `getFieldName($columnName)` - * `hasField($fieldName)` - * `getColumnNames(array $fieldNames = null)` - * `getTypeOfField($fieldName)` - * `getTypeOfColumn($columnName)` - * `hasAssociation($fieldName)` - * `isSingleValuedAssociation($fieldName)` - * `isCollectionValuedAssociation($fieldName)` - -### Lifecycle Callback Getters - - * `hasLifecycleCallbacks($lifecycleEvent)` - * `getLifecycleCallbacks($event)` - -## ClassMetadata API - -The `ClassMetadata` class extends `ClassMetadataInfo` and adds the runtime functionality -required by Doctrine. It adds a few extra methods related to runtime reflection -for working with the entities themselves. - - * `getReflectionClass()` - * `getReflectionProperties()` - * `getReflectionProperty($name)` - * `getSingleIdReflectionProperty()` - * `getIdentifierValues($entity)` - * `setIdentifierValues($entity, $id)` - * `setFieldValue($entity, $field, $value)` - * `getFieldValue($entity, $field)` \ No newline at end of file diff --git a/manual/en/query-builder.rst b/manual/en/query-builder.rst index 5b883ae39..240cf6113 100644 --- a/manual/en/query-builder.rst +++ b/manual/en/query-builder.rst @@ -1,5 +1,5 @@ The QueryBuilder ----------------- +================ A ``QueryBuilder`` provides an API that is designed for conditionally constructing a DQL query in several steps. diff --git a/manual/en/query-builder.txt b/manual/en/query-builder.txt deleted file mode 100644 index 5f15bed7a..000000000 --- a/manual/en/query-builder.txt +++ /dev/null @@ -1,408 +0,0 @@ - -## The QueryBuilder - -A `QueryBuilder` provides an API that is designed for conditionally constructing a DQL query in several steps. - -It provides a set of classes and methods that is able to programmatically build queries, and also provides a fluent API. -This means that you can change between one methodology to the other as you want, and also pick one if you prefer. - -### Constructing a new QueryBuilder object - -The same way you build a normal Query, you build a `QueryBuilder` object, just providing the correct method name. -Here is an example how to build a `QueryBuilder` object: - - createQueryBuilder(); - -Once you have created an instance of QueryBuilder, it provides a set of useful informative functions that you can use. -One good example is to inspect what type of object the `QueryBuilder` is. - - getType(); // Prints: 0 - -There're currently 3 possible return values for `getType()`: - -* `QueryBuilder::SELECT`, which returns value 0 -* `QueryBuilder::DELETE`, returning value 1 -* `QueryBuilder::UPDATE`, which returns value 2 - -It is possible to retrieve the associated `EntityManager` of the current `QueryBuilder`, its DQL and also a `Query` object when you finish building your DQL. - - getEntityManager(); - - // example4: retrieve the DQL string of what was defined in QueryBuilder - $dql = $qb->getDql(); - - // example5: retrieve the associated Query object with the processed DQL - $q = $qb->getQuery(); - -Internally, `QueryBuilder` works with a DQL cache to increase performance. Any changes that may affect the generated DQL actually modifies the state of `QueryBuilder` to a stage we call STATE_DIRTY. -One `QueryBuilder` can be in two different states: - -* `QueryBuilder::STATE_CLEAN`, which means DQL haven't been altered since last retrieval or nothing were added since its instantiation -* `QueryBuilder::STATE_DIRTY`, means DQL query must (and will) be processed on next retrieval - -### Working with QueryBuilder - -All helper methods in `QueryBuilder` actually rely on a single one: `add()`. -This method is responsible of building every piece of DQL. It takes 3 parameters: `$dqlPartName`, `$dqlPart` and `$append` (default=false) - -* `$dqlPartName`: Where the `$dqlPart` should be placed. Possible values: select, from, where, groupBy, having, orderBy -* `$dqlPart`: What should be placed in `$dqlPartName`. Accepts a string or any instance of `Doctrine\ORM\Query\Expr\*` -* `$append`: Optional flag (default=false) if the `$dqlPart` should override all previously defined items in `$dqlPartName` or not - -- - - add('select', 'u') - ->add('from', 'User u') - ->add('where', 'u.id = ?1') - ->add('orderBy', 'u.name ASC'); - -#### Binding parameters to your query - -Doctrine supports dynamic binding of parameters to your query, similar to preparing queries. You can use both strings and numbers as placeholders, although both have a slightly different syntax. Additionally, you must make your choice: Mixing both styles is not allowed. Binding parameters can simply be achieved as follows: - - add('select', 'u') - ->add('from', 'User u') - ->add('where', 'u.id = ?1') - ->add('orderBy', 'u.name ASC'); - ->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100 - -You are not forced to enumerate your placeholders as the alternative syntax is available: - - - add('select', 'u') - ->add('from', 'User u') - ->add('where', 'u.id = :identifier') - ->add('orderBy', 'u.name ASC'); - ->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100 - -Note that numeric placeholders start with a ? followed by a number while the named placeholders start with a : followed by a string. - -If you've got several parameters to bind to your query, you can also use setParameters() instead of setParameter() with the following syntax: - - setParameters(array(1 => 'value for ?1', 2 => 'value for ?2')); - - -Getting already bound parameters is easy - simply use the above mentioned syntax with "getParameter()" or "getParameters()": - - getParameters(array(1, 2)); - // Equivalent to - $param = array($qb->getParameter(1), $qb->getParameter(2)); - -Note: If you try to get a parameter that was not bound yet, getParameter() simply returns NULL. - -#### Expr\* classes - -When you call `add()` with string, it internally evaluates to an instance of `Doctrine\ORM\Query\Expr\Expr\*` class. -Here is the same query of example 6 written using `Doctrine\ORM\Query\Expr\Expr\*` classes: - - add('select', new Expr\Select(array('u'))) - ->add('from', new Expr\From('User', 'u')) - ->add('where', new Expr\Comparison('u.id', '=', '?1')) - ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); - -Of course this is the hardest way to build a DQL query in Doctrine. To simplify some of these efforts, we introduce what we call as `Expr` helper class. - -#### The Expr class - -To workaround most of the issues that `add()` method may cause, Doctrine created a class that can be considered as a helper for building queries. -This class is called `Expr`, which provides a set of useful static methods to help building queries: - - add('select', $qb->expr()->select('u')) - ->add('from', $qb->expr()->from('User', 'u')) - ->add('where', $qb->expr()->orx( - $qb->expr()->eq('u.id', '?1'), - $qb->expr()->like('u.nickname', '?2') - )) - ->add('orderBy', $qb->expr()->orderBy('u.surname', 'ASC')); - -Although it still sounds complex, the ability to programmatically create conditions are the main feature of `Expr`. -Here it is a complete list of supported helper methods available: - - expr()->select('u') - public function select($select = null); // Returns Expr\Select instance - - // Example - $qb->expr()->from('User', 'u') - public function from($from, $alias); // Returns Expr\From instance - - // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', Expr\Join::ON, 'p.user_id = u.id AND p.country_code = 55'); - // Example - $qb->expr()->leftJoin('u. Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); - public function leftJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance - - // Example - $qb->expr()->innerJoin('u.Group', 'g', Expr\Join::WITH, 'g.manager_level = 100'); - // Example - $qb->expr()->innerJoin('u.Group', 'g', 'WITH', $qb->expr()->eq('g.manager_level', '100')); - public function innerJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance - - // Example - $qb->expr()->orderBy('u.surname', 'ASC')->add('u.firstname', 'ASC')->... - public function orderBy($sort = null, $order = null); // Returns Expr\OrderBy instance - - // Example - $qb->expr()->groupBy()->add('u.id')->... - public function groupBy($groupBy = null); // Returns Expr\GroupBy instance - - - /** Conditional objects **/ - - // Example - $qb->expr()->andx($cond1 [, $condN])->add(...)->... - public function andx($x = null); // Returns Expr\Andx instance - - // Example - $qb->expr()->orx($cond1 [, $condN])->add(...)->... - public function orx($x = null); // Returns Expr\Orx instance - - - /** Comparison objects **/ - - // Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1 - public function eq($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1 - public function neq($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1 - public function lt($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1 - public function lte($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1 - public function gt($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1 - public function gte($x, $y); // Returns Expr\Comparison instance - - - /** Arithmetic objects **/ - - // Example - $qb->expr()->prod('u.id', '2') => u.id * 2 - public function prod($x, $y); // Returns Expr\Math instance - - // Example - $qb->expr()->diff('u.id', '2') => u.id - 2 - public function diff($x, $y); // Returns Expr\Math instance - - // Example - $qb->expr()->sum('u.id', '2') => u.id + 2 - public function sum($x, $y); // Returns Expr\Math instance - - // Example - $qb->expr()->quot('u.id', '2') => u.id / 2 - public function quot($x, $y); // Returns Expr\Math instance - - - /** Pseudo-function objects **/ - - // Example - $qb->expr()->exists($qb2->getDql()) - public function exists($subquery); // Returns Expr\Func instance - - // Example - $qb->expr()->all($qb2->getDql()) - public function all($subquery); // Returns Expr\Func instance - - // Example - $qb->expr()->some($qb2->getDql()) - public function some($subquery); // Returns Expr\Func instance - - // Example - $qb->expr()->any($qb2->getDql()) - public function any($subquery); // Returns Expr\Func instance - - // Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1')) - public function not($restriction); // Returns Expr\Func instance - - // Example - $qb->expr()->in('u.id', array(1, 2, 3)) - // Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception. - // Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above) - public function in($x, $y); // Returns Expr\Func instance - - // Example - $qb->expr()->notIn('u.id', '2') - public function notIn($x, $y); // Returns Expr\Func instance - - // Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%')) - public function like($x, $y); // Returns Expr\Comparison instance - - // Example - $qb->expr()->between('u.id', '1', '10') - public function between($val, $x, $y); // Returns Expr\Func - - - /** Function objects **/ - - // Example - $qb->expr()->trim('u.firstname') - public function trim($x); // Returns Expr\Func - - // Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat(' ', 'u.lastname')) - public function concat($x, $y); // Returns Expr\Func - - // Example - $qb->expr()->substr('u.firstname', 0, 1) - public function substr($x, $from, $len); // Returns Expr\Func - - // Example - $qb->expr()->lower('u.firstname') - public function lower($x); // Returns Expr\Func - - // Example - $qb->expr()->upper('u.firstname') - public function upper($x); // Returns Expr\Func - - // Example - $qb->expr()->length('u.firstname') - public function length($x); // Returns Expr\Func - - // Example - $qb->expr()->avg('u.age') - public function avg($x); // Returns Expr\Func - - // Example - $qb->expr()->max('u.age') - public function max($x); // Returns Expr\Func - - // Example - $qb->expr()->min('u.age') - public function min($x); // Returns Expr\Func - - // Example - $qb->expr()->abs('u.currentBalance') - public function abs($x); // Returns Expr\Func - - // Example - $qb->expr()->sqrt('u.currentBalance') - public function sqrt($x); // Returns Expr\Func - - // Example - $qb->expr()->count('u.firstname') - public function count($x); // Returns Expr\Func - - // Example - $qb->expr()->countDistinct('u.surname') - public function countDistinct($x); // Returns Expr\Func - } - -#### Helper methods - -Until now we have described the lowest level (thought of as the hardcore method) of creating queries. It may be useful to work at this level for optimization purposes, but most of the time it is preferred to work at a higher level of abstraction. -To simplify even more the way you build a query in Doctrine, we can take advantage of what we call Helper methods. For all base code, there is a set of useful methods to simplify a programmer's life. -To illustrate how to work with them, here is the same example 6 re-written using `QueryBuilder` helper methods: - - select('u') - ->from('User', 'u') - ->where('u.id = ?1') - ->orderBy('u.name ASC'); - -`QueryBuilder` helper methods are considered the standard way to build DQL queries. Although it is supported, it should be avoided to use string based queries and greatly encouraged to use `$qb->expr()->*` methods. -Here is a converted example 8 to suggested standard way to build queries: - - select(array('u')) // string 'u' is converted to array internally - ->from('User', 'u') - ->where($qb->expr()->orx( - $qb->expr()->eq('u.id', '?1'), - $qb->expr()->like('u.nickname', '?2') - )) - ->orderBy('u.surname', 'ASC')); - -Here is a complete list of helper methods available in `QueryBuilder`: - - select('u') - // Example - $qb->select(array('u', 'p')) - // Example - $qb->select($qb->expr()->select('u', 'p')) - public function select($select = null); - - // Example - $qb->delete('User', 'u') - public function delete($delete = null, $alias = null); - - // Example - $qb->update('Group', 'g') - public function update($update = null, $alias = null); - - // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold')) - // Example - $qb->set('u.numChilds', 'u.numChilds + ?1') - // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1')) - public function set($key, $value); - - // Example - $qb->from('Phonenumber', 'p') - public function from($from, $alias = null); - - // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::ON, $qb->expr()->and($qb->expr()->eq('u.group_id', 'g.id'), 'g.name = ?1')) - // Example - $qb->innerJoin('u.Group', 'g', 'ON', 'u.group_id = g.id AND g.name = ?1') - public function innerJoin($join, $alias = null, $conditionType = null, $condition = null); - - // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55)) - // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55') - public function leftJoin($join, $alias = null, $conditionType = null, $condition = null); - - // NOTE: ->where() overrides all previously set conditions - // - // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2')) - // Example - $qb->where($qb->expr()->andx($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2'))) - // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2') - public function where($where); - - // Example - $qb->andWhere($qb->expr()->orx($qb->expr()->lte('u.age', 40), 'u.numChild = 0')) - public function andWhere($where); - - // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10)); - public function orWhere($where); - - // NOTE: -> groupBy() overrides all previously set grouping conditions - // - // Example - $qb->groupBy('u.id') - public function groupBy($groupBy); - - // Example - $qb->addGroupBy('g.name') - public function addGroupBy($groupBy); - - // NOTE: -> having() overrides all previously set having conditions - // - // Example - $qb->having('u.salary >= ?1') - // Example - $qb->having($qb->expr()->gte('u.salary', '?1')) - public function having($having); - - // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0)) - public function andHaving($having); - - // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100')) - public function orHaving($having); - - // NOTE: -> orderBy() overrides all previously set ordering conditions - // - // Example - $qb->orderBy('u.surname', 'DESC') - public function orderBy($sort, $order = null); - - // Example - $qb->addOrderBy('u.firstName') - public function addOrderBy($sort, $order = null); // Default $order = 'ASC' - } diff --git a/manual/en/tools.rst b/manual/en/tools.rst index 79941ea1d..2274b38d1 100644 --- a/manual/en/tools.rst +++ b/manual/en/tools.rst @@ -1,5 +1,8 @@ -The Doctrine Console --------------------- +Tools +===== + +Doctrine Console +---------------- The Doctrine Console is a Command Line Interface tool for simplifying common tasks during the development of a project that @@ -170,7 +173,7 @@ The following Commands are currently available: Database Schema Generation -------------------------- - **Note** +.. note:: SchemaTool can do harm to your database. It will drop or alter tables, indexes, sequences and such. Please use this tool with @@ -274,7 +277,7 @@ will output the SQL for the ran operation. Before using the orm:schema-tool commands, remember to configure your cli-config.php properly. - **NOTE** +.. note:: When using the Annotation Mapping Driver you have to either setup your autoloader in the cli-config.php correctly to find all the @@ -364,7 +367,9 @@ You can also reverse engineer a database using the $ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml - **CAUTION** Reverse Engineering is not always working perfectly +.. warning:: + + Reverse Engineering is not always working perfectly depending on special cases. It will only detect Many-To-One relations (even if they are One-To-One) and will try to create entities from Many-To-Many tables. It also has problems with naming diff --git a/manual/en/tools.txt b/manual/en/tools.txt deleted file mode 100644 index 221ce89ce..000000000 --- a/manual/en/tools.txt +++ /dev/null @@ -1,254 +0,0 @@ -## The Doctrine Console - -The Doctrine Console is a Command Line Interface tool for simplifying common tasks during the development of a project that uses Doctrine 2. - -### Installation - -If you installed Doctrine 2 through PEAR, the `doctrine` command line tool should already be available to you. - -If you use Doctrine through SVN or a release package you need to copy the `doctrine` and `doctrine.php` files from the `tools/sandbox` or `bin` folder, respectively, to a location of your choice, for example a `tools` folder of your project. -You probably need to edit `doctrine.php` to adjust some paths to the new environment, most importantly the first line that includes the `Doctrine\Common\ClassLoader`. - -### Getting Help - -Type `doctrine` on the command line and you should see an overview of the available commands or use the --help flag to get information on the available commands. If you want to know more about the use of generate entities for example, you can call: - - doctrine orm:generate-entities --help - -### Configuration - -Whenever the `doctrine` command line tool is invoked, it can access alls Commands that were registered by developer. -There is no auto-detection mechanism at work. The `bin\doctrine.php` file already registers all the commands that -currently ship with Doctrine DBAL and ORM. If you want to use additional commands you have to register them yourself. - -All the commands of the Doctrine Console require either the `db` or the `em` helpers to be defined in order to work correctly. Doctrine Console requires the definition of a HelperSet that is the DI tool to be injected in the Console. -In case of a project that is dealing exclusively with DBAL, the ConnectionHelper is required: - - new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn) - )); - $cli->setHelperSet($helperSet); - -When dealing with the ORM package, the EntityManagerHelper is required: - - new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) - )); - $cli->setHelperSet($helperSet); - -The HelperSet instance has to be generated in a separate file (i.e. `cli-config.php`) that contains typical Doctrine -bootstrap code and predefines the needed HelperSet attributes mentioned above. A typical `cli-config.php` file looks as follows: - - register(); - - $classLoader = new \Doctrine\Common\ClassLoader('Proxies', __DIR__); - $classLoader->register(); - - $config = new \Doctrine\ORM\Configuration(); - $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); - $config->setProxyDir(__DIR__ . '/Proxies'); - $config->setProxyNamespace('Proxies'); - - $connectionOptions = array( - 'driver' => 'pdo_sqlite', - 'path' => 'database.sqlite' - ); - - $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); - - $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( - 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), - 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) - )); - -It is important to define a correct HelperSet that doctrine.php script will ultimately use. The Doctrine Binary -will automatically find the first instance of HelperSet in the global variable namespace and use this. - -You can also add your own commands on-top of the Doctrine supported tools. -To include a new command on Doctrine Console, you need to do: - - addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand()); - -Additionally, include multiple commands (and overriding previously defined ones) is possible through the command: - - addCommands(array( - new \MyProject\Tools\Console\Commands\MyCustomCommand(), - new \MyProject\Tools\Console\Commands\SomethingCommand(), - new \MyProject\Tools\Console\Commands\AnotherCommand(), - new \MyProject\Tools\Console\Commands\OneMoreCommand(), - )); - -### Command Overview - -The following Commands are currently available: - - * `help` Displays help for a command (?) - * `list` Lists commands - * `dbal:import` Import SQL file(s) directly to Database. - * `dbal:run-sql` Executes arbitrary SQL directly from the command line. - * `orm:clear-cache:metadata` Clear all metadata cache of the various cache drivers. - * `orm:clear-cache:query` Clear all query cache of the various cache drivers. - * `orm:clear-cache:result` Clear result cache of the various cache drivers. - * `orm:convert-d1-schema` Converts Doctrine 1.X schema into a Doctrine 2.X schema. - * `orm:convert-mapping` Convert mapping information between supported formats. - * `orm:ensure-production-settings` Verify that Doctrine is properly configured for a production environment. - * `orm:generate-entities` Generate entity classes and method stubs from your mapping information. - * `orm:generate-proxies` Generates proxy classes for entity classes. - * `orm:generate-repositories` Generate repository classes from your mapping information. - * `orm:run-dql` Executes arbitrary DQL directly from the command line. - * `orm:schema-tool:create` Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output. - * `orm:schema-tool:drop` Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output. - * `orm:schema-tool:update` Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output. - -## Database Schema Generation - -> **Note** -> -> SchemaTool can do harm to your database. It will drop or alter tables, indexes, sequences and such. Please use -> this tool with caution in development and not on a production server. It is meant for helping you develop your -> Database Schema, but NOT with migrating schema from A to B in production. A safe approach would be generating -> the SQL on development server and saving it into SQL Migration files that are executed manually on the production -> server. -> -> SchemaTool assumes your Doctrine Project uses the given database on its own. Update and Drop commands will -> mess with other tables if they are not related to the current project that is using Doctrine. Please be careful! - -To generate your database schema from your Doctrine mapping files you can use the -`SchemaTool` class or the `schema-tool` Console Command. - -When using the SchemaTool class directly, create your schema using the `createSchema()` method. First create an instance of the `SchemaTool` and pass it an instance of the `EntityManager` that you want to use to create the schema. This method receives an array of `ClassMetadataInfo` instances. - - getClassMetadata('Entities\User'), - $em->getClassMetadata('Entities\Profile') - ); - $tool->createSchema($classes); - -To drop the schema you can use the `dropSchema()` method. - - dropSchema($classes); - -This drops all the tables that are currently used by your metadata model. -When you are changing your metadata a lot during development you might want -to drop the complete database instead of only the tables of the current model -to clean up with orphaned tables. - - dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE); - -You can also use database introspection to update your schema easily with the -`updateSchema()` method. It will compare your existing database schema to the -passed array of `ClassMetdataInfo` instances. - - updateSchema($classes); - -If you want to use this functionality from the command line you can use the -`schema-tool` command. - -To create the schema use the `create` command: - - $ php doctrine orm:schema-tool:create - -To drop the schema use the `drop` command: - - $ php doctrine orm:schema-tool:drop - -If you want to drop and then recreate the schema then use both options: - - $ php doctrine orm:schema-tool:drop - $ php doctrine orm:schema-tool:create - -As you would think, if you want to update your schema use the `update` command: - - $ php doctrine orm:schema-tool:update - -All of the above commands also accept a `--dump-sql` option that will output the SQL -for the ran operation. - - $ php doctrine orm:schema-tool:create --dump-sql - -Before using the orm:schema-tool commands, remember to configure your cli-config.php properly. - -> **NOTE** -> -> When using the Annotation Mapping Driver you have to either setup your autoloader in the cli-config.php -> correctly to find all the entities, or you can use the second argument of the `EntityManagerHelper` to -> specify all the paths of your entities (or mapping files), i.e. -> `new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);` - -## Convert Mapping Information - -To convert some mapping information between the various supported formats you can -use the `ClassMetadataExporter` to get exporter instances for the different formats: - - getExporter('yml', '/path/to/export/yml'); - -Now you can export some `ClassMetadata` instances: - - getClassMetadata('Entities\User'), - $em->getClassMetadata('Entities\Profile') - ); - $exporter->setMetadata($classes); - $exporter->export(); - -This functionality is also available from the command line to convert your -loaded mapping information to another format. The `orm:convert-mapping` command -accepts two arguments, the type to convert to and the path to generate it: - - $ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml - -## Reverse Engineering - -You can use the `DatabaseDriver` to reverse engineer a database to an array of -`ClassMetadataInfo` instances and generate YAML, XML, etc. from them. - -First you need to retrieve the metadata instances with the `DatabaseDriver`: - - getConfiguration()->setMetadataDriverImpl( - new \Doctrine\ORM\Mapping\Driver\DatabaseDriver( - $em->getConnection()->getSchemaManager() - ) - ); - - $cmf = new DisconnectedClassMetadataFactory($em); - $metadata = $cmf->getAllMetadata(); - -Now you can get an exporter instance and export the loaded metadata to yml: - - getExporter('yml', '/path/to/export/yml'); - $exporter->setMetadata($metadata); - $exporter->export(); - -You can also reverse engineer a database using the `orm:convert-mapping` command: - - $ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml - -> **CAUTION** -> Reverse Engineering is not always working perfectly depending on special cases. -> It will only detect Many-To-One relations (even if they are One-To-One) and -> will try to create entities from Many-To-Many tables. It also has problems -> with naming of foreign keys that have multiple column names. Any Reverse Engineered -> Database-Schema needs considerable manual work to become a useful domain model. \ No newline at end of file diff --git a/manual/en/transactions-and-concurrency.rst b/manual/en/transactions-and-concurrency.rst index 19692f1ac..717783340 100644 --- a/manual/en/transactions-and-concurrency.rst +++ b/manual/en/transactions-and-concurrency.rst @@ -1,3 +1,6 @@ +Transactions and Concurrency +============================ + Transaction Demarcation ----------------------- diff --git a/manual/en/transactions-and-concurrency.txt b/manual/en/transactions-and-concurrency.txt deleted file mode 100644 index 550f8e52e..000000000 --- a/manual/en/transactions-and-concurrency.txt +++ /dev/null @@ -1,249 +0,0 @@ -## Transaction Demarcation - -Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very -important because if not done properly it can negatively affect the performance of your application. -Many databases and database abstraction layers like PDO by default operate in auto-commit mode, -which means that every single SQL statement is wrapped in a small transaction. Without any explicit -transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap. - -For the most part, Doctrine 2 already takes care of proper transaction demarcation for you: All the write -operations (INSERT/UPDATE/DELETE) are queued until `EntityManager#flush()` is invoked which wraps all -of these changes in a single transaction. - -However, Doctrine 2 also allows (and encourages) you to take over and control transaction demarcation yourself. - -These are two ways to deal with transactions when using the Doctrine ORM and are now described in more detail. - -### Approach 1: Implicitly - -The first approach is to use the implicit transaction handling provided by the Doctrine ORM -EntityManager. Given the following code snippet, without any explicit transaction demarcation: - - setName('George'); - $em->persist($user); - $em->flush(); - -Since we do not do any custom transaction demarcation in the above code, `EntityManager#flush()` will begin -and commit/rollback a transaction. This behavior is made possible by the aggregation of the DML operations -by the Doctrine ORM and is sufficient if all the data manipulation that is part of a unit of work happens -through the domain model and thus the ORM. - - -### Approach 2: Explicitly - -The explicit alternative is to use the `Doctrine\DBAL\Connection` API -directly to control the transaction boundaries. The code then looks like this: - - getConnection()->beginTransaction(); // suspend auto-commit - try { - //... do some work - $user = new User; - $user->setName('George'); - $em->persist($user); - $em->flush(); - $em->getConnection()->commit(); - } catch (Exception $e) { - $em->getConnection()->rollback(); - $em->close(); - throw $e; - } - -Explicit transaction demarcation is required when you want to include custom DBAL operations in a unit of work -or when you want to make use of some methods of the `EntityManager` API that require an active transaction. -Such methods will throw a `TransactionRequiredException` to inform you of that requirement. - -A more convenient alternative for explicit transaction demarcation is the use of provided control -abstractions in the form of `Connection#transactional($func)` and `EntityManager#transactional($func)`. -When used, these control abstractions ensure that you never forget to rollback the transaction or -close the `EntityManager`, apart from the obvious code reduction. An example that is functionally -equivalent to the previously shown code looks as follows: - - transactional(function($em) { - //... do some work - $user = new User; - $user->setName('George'); - $em->persist($user); - }); - -The difference between `Connection#transactional($func)` and `EntityManager#transactional($func)` is -that the latter abstraction flushes the `EntityManager` prior to transaction commit and also closes -the `EntityManager` properly when an exception occurs (in addition to rolling back the transaction). - - -### Exception Handling - -When using implicit transaction demarcation and an exception occurs during `EntityManager#flush()`, the transaction -is automatically rolled back and the `EntityManager` closed. - -When using explicit transaction demarcation and an exception occurs, the transaction should be rolled back immediately -and the `EntityManager` closed by invoking `EntityManager#close()` and subsequently discarded, as demonstrated in -the example above. This can be handled elegantly by the control abstractions shown earlier. -Note that when catching `Exception` you should generally re-throw the exception. If you intend to -recover from some exceptions, catch them explicitly in earlier catch blocks (but do not forget to rollback the -transaction and close the `EntityManager` there as well). All other best practices of exception handling apply -similarly (i.e. either log or re-throw, not both, etc.). - -As a result of this procedure, all previously managed or removed instances of the `EntityManager` become detached. -The state of the detached objects will be the state at the point at which the transaction was rolled back. -The state of the objects is in no way rolled back and thus the objects are now out of synch with the database. -The application can continue to use the detached objects, knowing that their state is potentially no longer -accurate. - -If you intend to start another unit of work after an exception has occurred you should do that with a new `EntityManager`. - -## Locking Support - -Doctrine 2 offers support for Pessimistic- and Optimistic-locking strategies natively. This allows to take -very fine-grained control over what kind of locking is required for your Entities in your application. - -### Optimistic Locking - -Database transactions are fine for concurrency control during a single request. However, a database transaction -should not span across requests, the so-called "user think time". Therefore a long-running "business transaction" -that spans multiple requests needs to involve several database transactions. Thus, database transactions alone -can no longer control concurrency during such a long-running business transaction. Concurrency control becomes -the partial responsibility of the application itself. - -Doctrine has integrated support for automatic optimistic locking via a version field. In this approach any entity -that should be protected against concurrent modifications during long-running business transactions gets a version -field that is either a simple number (mapping type: integer) or a timestamp (mapping type: datetime). When changes -to such an entity are persisted at the end of a long-running conversation the version of the entity is compared to -the version in the database and if they don't match, an `OptimisticLockException` is thrown, indicating that the -entity has been modified by someone else already. - -You designate a version field in an entity as follows. In this example we'll use an integer. - - find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion); - - // do the work - - $em->flush(); - } catch(OptimisticLockException $e) { - echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; - } - -Or you can use `EntityManager#lock()` to find out: - - find('User', $theEntityId); - - try { - // assert version - $em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion); - - } catch(OptimisticLockException $e) { - echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; - } - -#### Important Implementation Notes - -You can easily get the optimistic locking workflow wrong if you compare the wrong versions. -Say you have Alice and Bob accessing a hypothetical bank account: - -* Alice reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request) -* Bob reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request) -* Bob updates the headline to "Bar", upgrading the optimistic lock version to 2 (POST Request of a Form) -* Alice updates the headline to "Baz", ... (POST Request of a Form) - -Now at the last stage of this scenario the blog post has to be read again from the database before -Alice's headline can be applied. At this point you will want to check if the blog post is still at version 1 -(which it is not in this scenario). - -Using optimistic locking correctly, you *have* to add the version as an additional hidden field -(or into the SESSION for more safety). Otherwise you cannot verify the version is still the one being originally read from -the database when Alice performed her GET request for the blog post. If this happens you might -see lost updates you wanted to prevent with Optimistic Locking. - -See the example code, The form (GET Request): - - find('BlogPost', 123456); - - echo ''; - echo ''; - -And the change headline action (POST Request): - - find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion); - -### Pessimistic Locking - -Doctrine 2 supports Pessimistic Locking at the database level. No attempt is being made to implement pessimistic locking -inside Doctrine, rather vendor-specific and ANSI-SQL commands are used to acquire row-level locks. Every Entity can -be part of a pessimistic lock, there is no special metadata required to use this feature. - -However for Pessimistic Locking to work you have to disable the Auto-Commit Mode of your Database and start a -transaction around your pessimistic lock use-case using the "Approach 2: Explicit Transaction Demarcation" described -above. Doctrine 2 will throw an Exception if you attempt to acquire an pessimistic lock and no transaction is running. - -Doctrine 2 currently supports two pessimistic lock modes: - -* Pessimistic Write (`Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE`), locks the underlying database rows for concurrent Read and Write Operations. -* Pessimistic Read (`Doctrine\DBAL\LockMode::PESSIMISTIC_READ`), locks other concurrent requests that attempt to update or lock rows in write mode. - -You can use pessimistic locks in three different scenarios: - -1. Using `EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)` or `EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)` -2. Using `EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)` or `EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)` -3. Using `Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)` or `Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)` - diff --git a/manual/en/working-with-associations.rst b/manual/en/working-with-associations.rst index 12d2f10a6..784b0d836 100644 --- a/manual/en/working-with-associations.rst +++ b/manual/en/working-with-associations.rst @@ -1,3 +1,6 @@ +Working with Associations +========================= + Associations between entities are represented just like in regular object-oriented PHP, with references to other objects or collections of objects. When it comes to persistence, it is @@ -101,9 +104,8 @@ information about its type and if its the owning or inverse side. This two entities generate the following MySQL Schema (Foreign Key definitions omitted): -:: +.. code-block:: sql - [sql] CREATE TABLE User ( id VARCHAR(255) NOT NULL, firstComment_id VARCHAR(255) DEFAULT NULL, @@ -262,7 +264,7 @@ essentially an ordered map (just like a PHP array). That is why the separate method that has O(n) complexity using ``array_search``, where n is the size of the map. - **NOTE** +.. note:: Since Doctrine always only looks at the owning side of a bidirectional association for updates, it is not necessary for @@ -350,7 +352,7 @@ is needed. As you can see, proper bidirectional association management in plain OOP is a non-trivial task and encapsulating all the details inside the classes can be challenging. - **NOTE** +.. note:: If you want to make sure that your collections are perfectly encapsulated you should not return them from a diff --git a/manual/en/working-with-associations.txt b/manual/en/working-with-associations.txt deleted file mode 100644 index 8da311188..000000000 --- a/manual/en/working-with-associations.txt +++ /dev/null @@ -1,417 +0,0 @@ -Associations between entities are represented just like in regular object-oriented PHP, with references to other objects -or collections of objects. When it comes to persistence, it is important to understand three main things: - - * The concept of owning and inverse sides in bidirectional associations as described [here](http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#owning-side-and-inverse-side). - * If an entity is removed from a collection, the association is removed, not the entity itself. A collection of entities always only represents the association to the containing entities, not the entity itself. - * Collection-valued persistent fields have to be instances of the `Doctrine\Common\Collections\Collection` interface. [See here](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities:persistent-fields) for more details. - -Changes to associations in your code are not synchronized to the database directly, but upon calling `EntityManager#flush()`. - -To describe all the concepts of working with associations we introduce a specific set of example entities that show -all the different flavors of association management in Doctrine. - -## Association Example Entities - -We will use a simple comment system with Users and Comments as entities to show examples of association management. -See the PHP docblocks of each association in the following example for information about its type and if its the owning or inverse side. - - commentsRead; - } - - public function setFirstComment(Comment $c) { - $this->firstComment = $c; - } - } - -The interaction code would then look like in the following snippet (`$em` here is an instance of the EntityManager): - - find('User', $userId); - - // unidirectional many to many - $comment = $em->find('Comment', $readCommentId); - $user->getReadComments()->add($comment); - - $em->flush(); - - // unidirectional many to one - $myFirstComment = new Comment(); - $user->setFirstComment($myFirstComment); - - $em->persist($myFirstComment); - $em->flush(); - -In the case of bi-directional associations you have to update the fields on both sides: - - commentsAuthored; - } - - public function getFavoriteComments() { - return $this->favorites; - } - } - - class Comment - { - // ... - - public function getUserFavorites() { - return $this->userFavorites; - } - - public function setAuthor(User $author = null) { - $this->author = $author; - } - } - - // Many-to-Many - $user->getFavorites()->add($favoriteComment); - $favoriteComment->getUserFavorites()->add($user); - - $em->flush(); - - // Many-To-One / One-To-Many Bidirectional - $newComment = new Comment(); - $user->getAuthoredComments()->add($newComment); - $newComment->setAuthor($user); - - $em->persist($newComment); - $em->flush(); - - -Notice how always both sides of the bidirectional association are updated. The previous unidirectional associations were simpler to handle. - -## Removing Associations - -Removing an association between two entities is similarly straight-forward. There are two strategies -to do so, by key and by element. Here are some examples: - - getComments()->removeElement($comment); - $comment->setAuthor(null); - - $user->getFavorites()->removeElement($comment); - $comment->getUserFavorites()->removeElement($user); - - // Remove by Key - $user->getComments()->removeElement($ithComment); - $comment->setAuthor(null); - -You need to call `$em->flush()` to make persist these changes in the database permanently. - -Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently -simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does only -allows null values if `null` is set as default value. Otherwise setAddress(null) will fail for removing the association. -If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`. -This can also provide better encapsulation as it hides the internal meaning of not having an address. - -When working with collections, keep in mind that a Collection is essentially an ordered map (just like a PHP array). -That is why the `remove` operation accepts an index/key. `removeElement` is a separate method -that has O(n) complexity using `array_search`, where n is the size of the map. - -> **NOTE** -> -> Since Doctrine always only looks at the owning side of a bidirectional association for updates, it is not necessary -> for write operations that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. -> This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. - -You can also clear the contents of a whole collection using the `Collections::clear()` method. You -should be aware that using this method can lead to a straight and optimized database delete or update call -during the flush operation that is not aware of entities that have been re-added to the collection. - -Say you clear a collection of tags by calling `$post->getTags()->clear();` and then call -`$post->getTags()->add($tag)`. This will not recognize tag being already added before and issue -two database calls. - -## Association Management Methods - -It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained. - -The following code shows updates to the previous User and Comment example that encapsulate much of -the association management code: - - commentsRead[] = $comment; - } - - public function addComment(Comment $comment) { - if (count($this->commentsAuthored) == 0) { - $this->setFirstComment($comment); - } - $this->comments[] = $comment; - $comment->setAuthor($this); - } - - private function setFirstComment(Comment $c) { - $this->firstComment = $c; - } - - public function addFavorite(Comment $comment) { - $this->favorites->add($comment); - $comment->addUserFavorite($this); - } - - public function removeFavorite(Comment $comment) { - $this->favorites->removeElement($comment); - $comment->removeUserFavorite($this); - } - } - - class Comment - { - // .. - - public function addUserFavorite(User $user) { - $this->userFavorites[] = $user; - } - - public function removeUserFavorite(User $user) { - $this->userFavorites->removeElement($user); - } - } - -You will notice that `addUserFavorite` and `removeUserFavorite` do not call `addFavorite` and `removeFavorite`, -thus the bidirectional association is strictly-speaking still incomplete. However if you would naively add the -`addFavorite` in `addUserFavorite`, you end up with an infinite loop, so more work is needed. -As you can see, proper bidirectional association management in plain OOP is a non-trivial task -and encapsulating all the details inside the classes can be challenging. - -> **NOTE** -> -> If you want to make sure that your collections are perfectly encapsulated you should not return -> them from a `getCollectionName()` method directly, but call `$collection->toArray()`. This way a client programmer -> for the entity cannot circumvent the logic you implement on your entity for association management. For example: - - commentsRead->toArray(); - } - } - -This will however always initialize the collection, with all the performance penalties given the size. In -some scenarios of large collections it might even be a good idea to completely hide the read access behind -methods on the EntityRepository. - -There is no single, best way for association management. It greatly depends on the requirements of your concrete -domain model as well as your preferences. - -## Synchronizing Bidirectional Collections - -In the case of Many-To-Many associations you as the developer are responsible to keep the collections on the -owning and inverse side up in sync, when you apply changes to them. Doctrine can only guarantee a consistent -state for the hydration, not for your client code. - -Using the User-Comment entities from above, a very simple example can show the possible caveats you can encounter: - - getFavorites()->add($favoriteComment); - // not calling $favoriteComment->getUserFavorites()->add($user); - - $user->getFavorites()->contains($favoriteComment); // TRUE - $favoriteComment->getUserFavorites()->contains($user); // FALSE - -There are to approaches to handle this problem in your code: - -1. Ignore updating the inverse side of bidirectional collections, BUT never read from them in requests that changed - their state. In the next Request Doctrine hydrates the consistent collection state again. -2. Always keep the bidirectional collections in sync through association management methods. Reads of - the Collections directly after changes are consistent then. - -## Transitive persistence / Cascade Operations - -Persisting, removing, detaching and merging individual entities can become pretty -cumbersome, especially when a larger object graph with collections is involved. -Therefore Doctrine 2 provides a mechanism for transitive persistence through -cascading of these operations. Each association to another entity or a collection -of entities can be configured to automatically cascade certain operations. By -default, no operations are cascaded. - -The following cascade options exist: - - * persist : Cascades persist operations to the associated entities. - * remove : Cascades remove operations to the associated entities. - * merge : Cascades merge operations to the associated entities. - * detach : Cascades detach operations to the associated entities. - * all : Cascades persist, remove, merge and detach operations to associated entities. - -The following example is an extension to the User-Comment example of this chapter. -Suppose in our application a user is created whenever he writes his first comment. -In this case we would use the following code: - - addComment($myFirstComment); - - $em->persist($user); - $em->persist($myFirstComment); - $em->flush(); - -Even if you *persist* a new User that contains our new Comment this code would fail -if you removed the call to `EntityManager#persist($myFirstComment)`. Doctrine 2 does -not cascade the persist operation to all nested entities that are new as well. - -More complicated is the deletion of all a users comments when he is removed from the system: - - $user = $em->find('User', $deleteUserId); - - foreach ($user->getAuthoredComments() AS $comment) { - $em->remove($comment); - } - $em->remove($user); - $em->flush(); - -Without the loop over all the authored comments Doctrine would use an UPDATE statement only -to set the foreign key to NULL and only the User would be deleted from the database -during the flush()-Operation. - -To have Doctrine handle both cases automatically we can change the `User#commentsAuthored` -property to cascade both the "persist" and the "remove" operation. - - persist($user); $em->flush(); - **CAUTION** Generated entity identifiers / primary keys are +.. warning:: + + Generated entity identifiers / primary keys are guaranteed to be available after the next successful flush operation that involves the entity in question. You can not rely on a generated identifier to be available directly after invoking @@ -244,7 +251,9 @@ the ``EntityManager#remove($entity)`` method. By applying the which means that its persistent state will be deleted once ``EntityManager#flush()`` is invoked. - **CAUTION** Just like ``persist``, invoking ``remove`` on an entity +.. warning:: + + Just like ``persist``, invoking ``remove`` on an entity does NOT cause an immediate SQL DELETE to be issued on the database. The entity will be deleted on the next invocation of ``EntityManager#flush()`` that involves that entity. @@ -423,7 +432,9 @@ the ``merge`` operation is to reattach entities to an EntityManager that come from some cache (and are therefore detached) and you want to modify and persist such an entity. - **NOTE** If you load some detached entities from a cache and you do +.. note:: + + If you load some detached entities from a cache and you do not need to persist or delete them or otherwise make use of them without the need for persistence services there is no need to use ``merge``. I.e. you can simply pass detached objects from a cache @@ -516,7 +527,9 @@ to change tracking (see "Change Tracking Policies") and, of course, memory consumption, so you may want to check it from time to time during development. - **CAUTION** Do not invoke ``flush`` after every change to an entity +.. warning:: + + Do not invoke ``flush`` after every change to an entity or every single invocation of persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the performance of your application. Instead, form units of work that operate on your diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt deleted file mode 100644 index ad0a943ec..000000000 --- a/manual/en/working-with-objects.txt +++ /dev/null @@ -1,549 +0,0 @@ - -In this chapter we will help you understand the `EntityManager` and the `UnitOfWork`. -A Unit of Work is similar to an object-level transaction. A new Unit of Work is -implicitly started when an EntityManager is initially created or after -`EntityManager#flush()` has been invoked. A Unit of Work is committed -(and a new one started) by invoking `EntityManager#flush()`. - -A Unit of Work can be manually closed by calling EntityManager#close(). Any -changes to objects within this Unit of Work that have not yet been persisted -are lost. - -> **NOTE** -> -> It is very important to understand that only `EntityManager#flush()` ever causes -> write operations against the database to be executed. Any other methods such -> as `EntityManager#persist($entity)` or `EntityManager#remove($entity)` only -> notify the UnitOfWork to perform these operations during flush. -> -> Not calling `EntityManager#flush()` will lead to all changes during that request being lost. - -## Entities and the Identity Map - -Entities are objects with identity. Their identity has a conceptual meaning inside your domain. -In a CMS application each article has a unique id. You can uniquely identify each article -by that id. - -Take the following example, where you find an article with the headline "Hello World" -with the ID 1234: - - find('CMS\Article', 1234); - $article->setHeadline('Hello World dude!'); - - $article2 = $entityManager->find('CMS\Article', 1234); - echo $article2->getHeadline(); - -In this case the Article is accessed from the entity manager twice, but modified in between. -Doctrine 2 realizes this and will only ever give you access to one instance of the Article -with ID 1234, no matter how often do you retrieve it from the EntityManager and even no -matter what kind of Query method you are using (find, Repository Finder or DQL). -This is called "Identity Map" pattern, which means Doctrine keeps a map of each entity -and ids that have been retrieved per PHP request and keeps returning you the same instances. - -In the previous example the echo prints "Hello World dude!" to the screen. You can -even verify that `$article` and `$article2` are indeed pointing to the same instance -by running the following code: - - comments = new ArrayCollection(); - } - - public function getAuthor() { return $this->author; } - public function getComments() { return $this->comments; } - } - - $article = $em->find('Article', 1); - -This code only retrieves the `User` instance with id 1 executing a single SELECT statement -against the user table in the database. You can still access the associated properties author -and comments and the associated objects they contain. - -This works by utilizing the lazy loading pattern. Instead of passing you back a real -Author instance and a collection of comments Doctrine will create proxy instances for you. -Only if you access these proxies for the first time they will go through the EntityManager -and load their state from the database. - -This lazy-loading process happens behind the scenes, hidden from your code. See the following code: - - find('Article', 1); - - // accessing a method of the user instance triggers the lazy-load - echo "Author: " . $article->getAuthor()->getName() . "\n"; - - // Lazy Loading Proxies pass instanceof tests: - if ($article->getAuthor() instanceof User) { - // a User Proxy is a generated "UserProxy" class - } - - // accessing the comments as an iterator triggers the lazy-load - // retrieving ALL the comments of this article from the database - // using a single SELECT statement - foreach ($article->getComments() AS $comment) { - echo $comment->getText() . "\n\n"; - } - - // Article::$comments passes instanceof tests for the Collection interface - // But it will NOT pass for the ArrayCollection interface - if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) { - echo "This will always be true!"; - } - -A slice of the generated proxy classes code looks like the following piece of code. A real proxy -class override ALL public methods along the lines of the `getName()` method shown below: - - _load(); - return parent::getName(); - } - // .. other public methods of User - } - -> **Warning** -> -> Traversing the object graph for parts that are lazy-loaded will easily trigger lots -> of SQL queries and will perform badly if used to heavily. Make sure to use DQL -> to fetch-join all the parts of the object-graph that you need as efficiently as possible. - -## Persisting entities - -An entity can be made persistent by passing it to the `EntityManager#persist($entity)` -method. By applying the persist operation on some entity, that entity becomes MANAGED, -which means that its persistence is from now on managed by an EntityManager. As a -result the persistent state of such an entity will subsequently be properly -synchronized with the database when `EntityManager#flush()` is invoked. - -> **CAUTION** -> Invoking the `persist` method on an entity does NOT cause an immediate SQL INSERT to be -> issued on the database. Doctrine applies a strategy called "transactional write-behind", -> which means that it will delay most SQL commands until `EntityManager#flush()` is -> invoked which will then issue all necessary SQL statements to synchronize your objects -> with the database in the most efficient way and a single, short transaction, -> taking care of maintaining referential integrity. - -Example: - - setName('Mr.Right'); - $em->persist($user); - $em->flush(); - -> **CAUTION** -> Generated entity identifiers / primary keys are guaranteed to be available after the -> next successful flush operation that involves the entity in question. -> You can not rely on a generated identifier to be available directly after invoking `persist`. -> The inverse is also true. You can not rely on a generated identifier being not available -> after a failed flush operation. - -The semantics of the persist operation, applied on an entity X, are as follows: - -* If X is a new entity, it becomes managed. The entity X will be entered into the database as a result of the flush operation. -* If X is a preexisting managed entity, it is ignored by the persist operation. However, the persist operation is cascaded to entities referenced by X, if the relationships from X to these other entities are mapped with cascade=PERSIST or cascade=ALL (see "Transitive Persistence"). -* If X is a removed entity, it becomes managed. -* If X is a detached entity, an exception will be thrown on flush. - - -## Removing entities - -An entity can be removed from persistent storage by passing it to the `EntityManager#remove($entity)` method. By applying the `remove` operation on some entity, that entity becomes REMOVED, which means that its persistent state will be deleted once `EntityManager#flush()` is invoked. - -> **CAUTION** -> Just like `persist`, invoking `remove` on an entity does NOT cause an immediate SQL -> DELETE to be issued on the database. The entity will be deleted on the next invocation -> of `EntityManager#flush()` that involves that entity. - -Example: - - remove($user); - $em->flush(); - -The semantics of the remove operation, applied to an entity X are as follows: - -* If X is a new entity, it is ignored by the remove operation. However, the remove operation is cascaded to entities referenced by X, if the relationship from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). -* If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). -* If X is a detached entity, an InvalidArgumentException will be thrown. -* If X is a removed entity, it is ignored by the remove operation. -* A removed entity X will be removed from the database as a result of the flush operation. - -After an entity has been removed its in-memory state is the same as before the removal, except for generated identifiers. - -Removing an entity will also automatically delete any existing records in many-to-many -join tables that link this entity. The action taken depends on the value of the `@joinColumn` -mapping attribute "onDelete". Either Doctrine issues a dedicated `DELETE` statement -for records of each join table or it depends on the foreign key semantics of -onDelete="CASCADE". - -Deleting an object with all its associated objects can be achieved in multiple -ways with very different performance impacts. - -1. If an association is marked as `CASCADE=REMOVE` Doctrine 2 will fetch this - association. If its a Single association it will pass this entity to - ´EntityManager#remove()`. If the association is a collection, Doctrine will loop over all - its elements and pass them to `EntityManager#remove()`. In both cases the - cascade remove semantics are applied recursively. For large object graphs - this removal strategy can be very costly. -2. Using a DQL `DELETE` statement allows you to delete multiple entities of a - type with a single command and without hydrating these entities. This - can be very efficient to delete large object graphs from the database. -3. Using foreign key semantics `onDelete="CASCADE"` can force the database - to remove all associated objects internally. This strategy is a bit - tricky to get right but can be very powerful and fast. You should be aware - however that using strategy 1 (`CASCADE=REMOVE`) completely by-passes - any foreign key `onDelete=CASCADE` option, because Doctrine will fetch and remove - all associated entities explicitly nevertheless. - -## Detaching entities - -An entity is detached from an EntityManager and thus no longer managed by -invoking the `EntityManager#detach($entity)` method on it or by cascading -the detach operation to it. Changes made to the detached entity, if any -(including removal of the entity), will not be synchronized to the database -after the entity has been detached. - -Doctrine will not hold on to any references to a detached entity. - -Example: - - detach($entity); - -The semantics of the detach operation, applied to an entity X are as follows: - -* If X is a managed entity, the detach operation causes it to become detached. The detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X. -* If X is a new or detached entity, it is ignored by the detach operation. -* If X is a removed entity, the detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X. - -There are several situations in which an entity is detached automatically without invoking the `detach` method: - -* When `EntityManager#clear()` is invoked, all entities that are currently managed by the EntityManager instance become detached. -* When serializing an entity. The entity retrieved upon subsequent unserialization will be detached (This is the case for all entities that are serialized and stored in some cache, i.e. when using the Query Result Cache). - -The `detach` operation is usually not as frequently needed and used as `persist` and `remove`. - - -## Merging entities - -Merging entities refers to the merging of (usually detached) entities into the -context of an EntityManager so that they become managed again. To merge the -state of an entity into an EntityManager use the `EntityManager#merge($entity)` -method. The state of the passed entity will be merged into a managed copy of -this entity and this copy will subsequently be returned. - -Example: - - merge($detachedEntity); - // $entity now refers to the fully managed copy returned by the merge operation. - // The EntityManager $em now manages the persistence of $entity as usual. - -> **CAUTION** -> When you want to serialize/unserialize entities you have to make all entity properties -> protected, never private. The reason for this is, if you serialize a class that was a proxy -> instance before, the private variables won't be serialized and a PHP Notice is thrown. - -The semantics of the merge operation, applied to an entity X, are as follows: - -* If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity. -* If X is a new entity instance, a new managed copy X' will be created and the state of X is copied onto this managed instance. -* If X is a removed entity instance, an InvalidArgumentException will be thrown. -* If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been mapped with the cascade element value MERGE or ALL (see "Transitive Persistence"). -* For all entities Y referenced by relationships from X having the cascade element value -MERGE or ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.) -* If X is an entity merged to X', with a reference to another entity Y, where cascade=MERGE or cascade=ALL is not specified, then navigation of the same association from X' yields a reference to a managed object Y' with the same persistent identity as Y. - -The `merge` operation will throw an `OptimisticLockException` if the entity -being merged uses optimistic locking through a version field and the versions -of the entity being merged and the managed copy don't match. This usually means -that the entity has been modified while being detached. - -The `merge` operation is usually not as frequently needed and used as `persist` -and `remove`. The most common scenario for the `merge` operation is to reattach -entities to an EntityManager that come from some cache (and are therefore detached) -and you want to modify and persist such an entity. - -> **NOTE** -> If you load some detached entities from a cache and you do not need to persist or -> delete them or otherwise make use of them without the need for persistence services -> there is no need to use `merge`. I.e. you can simply pass detached objects from a cache -> directly to the view. - -## Synchronization with the Database - -The state of persistent entities is synchronized with the database on flush of an `EntityManager` -which commits the underlying `UnitOfWork`. The synchronization involves writing any updates to -persistent entities and their relationships to the database. Thereby bidirectional relationships -are persisted based on the references held by the owning side of the relationship as explained -in the Association Mapping chapter. - -When `EntityManager#flush()` is called, Doctrine inspects all managed, new and removed entities -and will perform the following operations. - -### Synchronizing New and Managed Entities - -The flush operation applies to a managed entity with the following semantics: - -* The entity itself is synchronized to the database using a SQL UPDATE statement, only if at least one persistent field has changed. -* No SQL updates are executed if the entity did not change. - -The flush operation applies to a new entity with the following semantics: - -* The entity itself is synchronized to the database using a SQL INSERT statement. - -For all (initialized) relationships of the new or managed entity the following semantics apply to each -associated entity X: - -* If X is new and persist operations are configured to cascade on the relationship, - X will be persisted. -* If X is new and no persist operations are configured to cascade on the relationship, - an exception will be thrown as this indicates a programming error. -* If X is removed and persist operations are configured to cascade on the relationship, - an exception will be thrown as this indicates a programming error (X would be re-persisted by the cascade). -* If X is detached and persist operations are configured to cascade on the relationship, - an exception will be thrown (This is semantically the same as passing X to persist()). - -### Synchronizing Removed Entities - -The flush operation applies to a removed entity by deleting its persistent state from the database. -No cascade options are relevant for removed entities on flush, the cascade remove option is already -executed during `EntityManager#remove($entity)`. - -### The size of a Unit of Work - -The size of a Unit of Work mainly refers to the number of managed entities at -a particular point in time. - -### The cost of flushing - -How costly a flush operation is, mainly depends on two factors: - -* The size of the EntityManager's current UnitOfWork. -* The configured change tracking policies - -You can get the size of a UnitOfWork as follows: - - getUnitOfWork()->size(); - -The size represents the number of managed entities in the Unit of Work. This -size affects the performance of flush() operations due to change tracking -(see "Change Tracking Policies") and, of course, memory consumption, so you -may want to check it from time to time during development. - -> **CAUTION** -> Do not invoke `flush` after every change to an entity or every single invocation of -> persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the -> performance of your application. Instead, form units of work that operate on your objects -> and call `flush` when you are done. While serving a single HTTP request there should -> be usually no need for invoking `flush` more than 0-2 times. - -### Direct access to a Unit of Work - -You can get direct access to the Unit of Work by calling `EntityManager#getUnitOfWork()`. -This will return the UnitOfWork instance the EntityManager is currently using. - - getUnitOfWork(); - -> **NOTE** -> Directly manipulating a UnitOfWork is not recommended. When working directly with the -> UnitOfWork API, respect methods marked as INTERNAL by not using them and carefully read -> the API documentation. - -### Entity State - -As outlined in the architecture overview an entity can be in one of four possible states: -NEW, MANAGED, REMOVED, DETACHED. If you explicitly need to find out what the current state -of an entity is in the context of a certain `EntityManager` you can ask the underlying -`UnitOfWork`: - - getUnitOfWork()->getEntityState($entity)) { - case UnitOfWork::MANAGED: - ... - case UnitOfWork::REMOVED: - ... - case UnitOfWork::DETACHED: - ... - case UnitOfWork::NEW: - ... - } - -An entity is in MANAGED state if it is associated with an `EntityManager` and it is not REMOVED. - -An entity is in REMOVED state after it has been passed to `EntityManager#remove()` until the -next flush operation of the same EntityManager. A REMOVED entity is still associated with an -`EntityManager` until the next flush operation. - -An entity is in DETACHED state if it has persistent state and identity but is currently not -associated with an `EntityManager`. - -An entity is in NEW state if has no persistent state and identity and is not associated with an -`EntityManager` (for example those just created via the "new" operator). - - -## Querying - -Doctrine 2 provides the following ways, in increasing level of power and flexibility, to query for persistent objects. You should always start with the simplest one that suits your needs. - -### By Primary Key - -The most basic way to query for a persistent object is by its identifier / primary key using the `EntityManager#find($entityName, $id)` method. Here is an example: - - find('MyProject\Domain\User', $id); - -The return value is either the found entity instance or null if no instance could be found with the given identifier. - -Essentially, `EntityManager#find()` is just a shortcut for the following: - - getRepository('MyProject\Domain\User')->find($id); - -`EntityManager#getRepository($entityName)` returns a repository object which provides many ways to retrieve entities of the specified type. By default, the repository instance is of type `Doctrine\ORM\EntityRepository`. You can also use custom repository classes as shown later. - -### By Simple Conditions - -To query for one or more entities based on several conditions that form a logical conjunction, use the `findBy` and `findOneBy` methods on a repository as follows: - - getRepository('MyProject\Domain\User')->findBy(array('age' => 20)); - - // All users that are 20 years old and have a surname of 'Miller' - $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller')); - - // A single user by its nickname - $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); - -An EntityRepository also provides a mechanism for more concise calls through its use of `__call`. Thus, the following two examples are equivalent: - - getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); - - // A single user by its nickname (__call magic) - $user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb'); - - -### By Eager Loading - -Whenever you query for an entity that has persistent associations and these associations are mapped as EAGER, they will automatically be loaded together with the entity being queried and is thus immediately available to your application. - - -### By Lazy Loading - -Whenever you have a managed entity instance at hand, you can traverse and use any associations of that entity that are configured LAZY as if they were in-memory already. Doctrine will automatically load the associated objects on demand through the concept of lazy-loading. - - -### By DQL - -The most powerful and flexible method to query for persistent objects is the Doctrine Query Language, an object query language. DQL enables you to query for persistent objects in the language of objects. DQL understands classes, fields, inheritance and associations. -DQL is syntactically very similar to the familiar SQL but *it is not SQL*. - -A DQL query is represented by an instance of the `Doctrine\ORM\Query` class. You create a query using `EntityManager#createQuery($dql)`. Here is a simple example: - - createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30"); - $users = $q->getResult(); - -Note that this query contains no knowledge about the relational schema, only about the object model. DQL supports positional as well as named parameters, many functions, (fetch) joins, aggregates, subqueries and much more. Detailed information about DQL and its syntax as well as the Doctrine\ORM\Query class can be found in [the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language). For programmatically building up queries based on conditions that are only known at runtime, Doctrine provides the special `Doctrine\ORM\QueryBuilder` class. More information on constructing queries with a QueryBuilder can be found [in the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/query-builder). - - -### By Native Queries - -As an alternative to DQL or as a fallback for special SQL statements native queries can be used. -Native queries are built by using a hand-crafted SQL query and a ResultSetMapping that describes -how the SQL result set should be transformed by Doctrine. More information about native queries -can be found in [the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/native-sql). - -### Custom Repositories - -By default the EntityManager returns a default implementation of `Doctrine\ORM\EntityRepository` when -you call `EntityManager#getRepository($entityClass)`. You can overwrite this behaviour by specifying -the class name of your own Entity Repository in the Annotation, XML or YAML metadata. -In large applications that require lots of specialized DQL queries using a custom repository is -one recommended way of grouping these queries in a central location. - - _em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"') - ->getResult(); - } - } - -You can access your repository now by calling: - - getRepository('MyDomain\Model\User')->getAllAdminUsers(); - diff --git a/manual/en/xml-mapping.rst b/manual/en/xml-mapping.rst index b384dd638..e93ded5a1 100644 --- a/manual/en/xml-mapping.rst +++ b/manual/en/xml-mapping.rst @@ -1,3 +1,6 @@ +XML Mapping +=========== + The XML mapping driver enables you to provide the ORM metadata in form of XML documents. @@ -13,9 +16,8 @@ code-completion based on such an XML Schema document. The following is an outline of a XML mapping document with the proper xmlns/xsi setup for the latest code in trunk. -:: +.. code-block:: xml - [xml] `` element as a direct child of the ```` element: -:: +.. code-block:: xml - [xml] @@ -188,9 +188,8 @@ element as a children to the ```` element. The field element is only used for primitive types that are not the ID of the entity. For the ID mapping you have to use the ```` element. -:: +.. code-block:: xml - [xml] @@ -238,9 +237,8 @@ surrogate keys are recommended for use with Doctrine 2. The Id field allows to define properties of the identifier and allows a subset of the ```` element attributes: -:: +.. code-block:: xml - [xml] @@ -270,9 +268,8 @@ to nest a ```` element inside the id-element. This of course only works for surrogate keys. For composite keys you always have to use the ``ASSIGNED`` strategy. -:: +.. code-block:: xml - [xml] @@ -333,9 +330,8 @@ from, which itself is not an entity however. The chapter on *Inheritance Mapping* describes a Mapped Superclass in detail. You can define it in XML using the ```` tag. -:: +.. code-block:: xml - [xml] @@ -366,9 +362,8 @@ You can specify the inheritance type in the ```` element and then use the ```` and ```` attributes. -:: +.. code-block:: xml - [xml] @@ -381,7 +376,7 @@ and then use the ```` and The allowed values for inheritance-type attribute are ``JOINED`` or ``SINGLE_TABLE``. - **NOTE** +.. note:: All inheritance related definitions have to be defined on the root entity of the hierarchy. @@ -393,9 +388,8 @@ Defining Lifecycle Callbacks You can define the lifecycle callback methods on your entities using the ```` element: -:: +.. code-block:: xml - [xml] @@ -412,9 +406,8 @@ depend on the associations being on the inverse or owning side. For the inverse side the mapping is as simple as: -:: +.. code-block:: xml - [xml] @@ -431,9 +424,8 @@ Required attributes for inverse One-To-One: For the owning side this mapping would look like: -:: +.. code-block:: xml - [xml] @@ -467,9 +459,8 @@ association, which means it contains the foreign key. The completed explicitly defined mapping is: -:: +.. code-block:: xml - [xml] @@ -484,9 +475,8 @@ bidirectional association. This simplifies the mapping compared to the one-to-one case. The minimal mapping for this association looks like: -:: +.. code-block:: xml - [xml] @@ -516,9 +506,8 @@ to the naming of the join-column/foreign key. The explicitly defined mapping includes a ```` tag nested inside the many-to-one association tag: -:: +.. code-block:: xml - [xml] @@ -537,9 +526,8 @@ association. There exists no such thing as a uni-directional one-to-many association, which means this association only ever exists for bi-directional associations. -:: +.. code-block:: xml - [xml] @@ -566,9 +554,8 @@ From all the associations the many-to-many has the most complex definition. When you rely on the mapping defaults you can omit many definitions and rely on their implicit values. -:: +.. code-block:: xml - [xml] @@ -596,9 +583,8 @@ The mapping defaults would lead to a join-table with the name "User\_Group" being created that contains two columns "user\_id" and "group\_id". The explicit definition of this mapping would be: -:: +.. code-block:: xml - [xml] @@ -626,9 +612,8 @@ related entities. You can specify the cascade operations in the ```` element inside any of the association mapping tags. -:: +.. code-block:: xml - [xml] @@ -677,9 +662,8 @@ Defining Order of To-Many Associations You can require one-to-many or many-to-many associations to be retrieved using an additional ``ORDER BY``. -:: +.. code-block:: xml - [xml] @@ -695,9 +679,8 @@ To define additional indexes or unique constraints on the entities table you can use the ```` and ```` elements: -:: +.. code-block:: xml - [xml] diff --git a/manual/en/xml-mapping.txt b/manual/en/xml-mapping.txt deleted file mode 100644 index 2dd1806b7..000000000 --- a/manual/en/xml-mapping.txt +++ /dev/null @@ -1,509 +0,0 @@ -The XML mapping driver enables you to provide the ORM metadata in form of XML documents. - -The XML driver is backed by an XML Schema document that describes the structure of a mapping document. The most recent version of the XML Schema document is available online at [http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd](http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd). In order to point to the latest version of the document of a particular stable release branch, just append the release number, i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with XML mapping files is to use an IDE/editor that can provide code-completion based on such an XML Schema document. The following is an outline of a XML mapping document with the proper xmlns/xsi setup for the latest code in trunk. - - [xml] - - - ... - - - -The XML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: - - * Each entity/mapped superclass must get its own dedicated XML mapping document. - * The name of the mapping document must consist of the fully qualified name of the class, where namespace - separators are replaced by dots (.). For example an Entity with the fully qualified class-name "MyProject\Entities\User" - would require a mapping file "MyProject.Entities.User.dcm.xml" unless the extension is changed. - * All mapping documents should get the extension ".dcm.xml" to identify it as a Doctrine mapping file. This is more of - a convention and you are not forced to do this. You can change the file extension easily enough. - -- - - setFileExtension('.xml'); - -It is recommended to put all XML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the XmlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: - - setMetadataDriverImpl($driver); - - -## Example - -As a quick start, here is a small example document that makes use of several common elements: - - [xml] - // Doctrine.Tests.ORM.Mapping.User.dcm.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Be aware that class-names specified in the XML files should be fully qualified. - -## XML-Element Reference - -The XML-Element reference explains all the tags and attributes that the Doctrine Mapping XSD Schema defines. -You should read the Basic-, Association- and Inheritance Mapping chapters to understand what each of this -definitions means in detail. - -### Defining an Entity - -Each XML Mapping File contains the definition of one entity, specified as the `` element -as a direct child of the `` element: - - [xml] - - - - - - -Required attributes: - -* name - The fully qualified class-name of the entity. - -Optional attributes: - -* table - The Table-Name to be used for this entity. Otherwise the Unqualified Class-Name is used by default. -* repository-class - The fully qualified class-name of an alternative `Doctrine\ORM\EntityRepository` implementation to be used with this entity. -* inheritance-type - The type of inheritance, defaults to none. A more detailed description follows in the *Defining Inheritance Mappings* section. - -### Defining Fields - -Each entity class can contain zero to infinite fields that are managed by Doctrine. You can define -them using the `` element as a children to the `` element. The field element is only -used for primitive types that are not the ID of the entity. For the ID mapping you have to use the `` element. - - [xml] - - - - - - - - - -Required attributes: - -* name - The name of the Property/Field on the given Entity PHP class. - -Optional attributes: - -* type - The `Doctrine\DBAL\Types\Type` name, defaults to "string" -* column - Name of the column in the database, defaults to the field name. -* length - The length of the given type, for use with strings only. -* unique - Should this field contain a unique value across the table? Defaults to false. -* nullable - Should this field allow NULL as a value? Defaults to false. -* version - Should this field be used for optimistic locking? Only works on fields with type integer or datetime. -* scale - Scale of a decimal type. -* precision - Precision of a decimal type. -* column-definition - Optional alternative SQL representation for this column. This definition begin after the - field-name and has to specify the complete column definition. Using this feature will turn this field dirty - for Schema-Tool update commands at all times. - -### Defining Identity and Generator Strategies - -An entity has to have at least one `` element. For composite keys you can specify more than one id-element, -however surrogate keys are recommended for use with Doctrine 2. The Id field allows to define properties of -the identifier and allows a subset of the `` element attributes: - - [xml] - - - - -Required attributes: - -* name - The name of the Property/Field on the given Entity PHP class. -* type - The `Doctrine\DBAL\Types\Type` name, preferably "string" or "integer". - -Optional attributes: - -* column - Name of the column in the database, defaults to the field name. - -Using the simplified definition above Doctrine will use no identifier strategy for this entity. That means -you have to manually set the identifier before calling `EntityManager#persist($entity)`. This is the -so called `ASSIGNED` strategy. - -If you want to switch the identifier generation strategy you have to nest a `` element inside -the id-element. This of course only works for surrogate keys. For composite keys you always have to use -the `ASSIGNED` strategy. - - [xml] - - - - - - -The following values are allowed for the `` strategy attribute: - -* AUTO - Automatic detection of the identifier strategy based on the preferred solution of the database vendor. -* IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs available to Doctrine AFTER the INSERT statement has been executed. -* SEQUENCE - Use of a database sequence to retrieve the entity-ids. This is possible before the INSERT statement is executed. - -If you are using the SEQUENCE strategy you can define an additional element to describe the sequence: - - [xml] - - - - - - - -Required attributes for ``: - -* sequence-name - The name of the sequence - -Optional attributes for ``: - -* allocation-size - By how much steps should the sequence be incremented when a value is retrieved. Defaults to 1 -* initial-value - What should the initial value of the sequence be. - -> **NOTE** -> -> If you want to implement a cross-vendor compatible application you have to specify and -> additionally define the element, if Doctrine chooses the sequence strategy for a platform. - -### Defining a Mapped Superclass - -Sometimes you want to define a class that multiple entities inherit from, which itself is not an entity however. -The chapter on *Inheritance Mapping* describes a Mapped Superclass in detail. You can define it in XML using -the `` tag. - - [xml] - - - - - - - -Required attributes: - -* name - Class name of the mapped superclass. - -You can nest any number of `` and unidirectional `` or `` associations inside -a mapped superclass. - -### Defining Inheritance Mappings - -There are currently two inheritance persistence strategies that you can choose from when defining entities that -inherit from each other. Single Table inheritance saves the fields of the complete inheritance hierarchy in a single table, -joined table inheritance creates a table for each entity combining the fields using join conditions. - -You can specify the inheritance type in the `` element and then use the `` and -`` attributes. - - [xml] - - - - - - - - - -The allowed values for inheritance-type attribute are `JOINED` or `SINGLE_TABLE`. - -> **NOTE** -> -> All inheritance related definitions have to be defined on the root entity of the hierarchy. - -### Defining Lifecycle Callbacks - -You can define the lifecycle callback methods on your entities using the `` element: - - [xml] - - - - - - - -### Defining One-To-One Relations - -You can define One-To-One Relations/Associations using the `` element. The required -and optional attributes depend on the associations being on the inverse or owning side. - -For the inverse side the mapping is as simple as: - - [xml] - - - - -Required attributes for inverse One-To-One: - -* field - Name of the property/field on the entity's PHP class. -* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! -* mapped-by - Name of the field on the owning side (here Address entity) that contains the owning side association. - -For the owning side this mapping would look like: - - [xml] - - - - -Required attributes for owning One-to-One: - -* field - Name of the property/field on the entity's PHP class. -* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! - -Optional attributes for owning One-to-One: - -* inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference. -* orphan-removal - If true, the inverse side entity is always deleted when the owning side entity is. Defaults to false. -* fetch - Either LAZY or FETCH, defaults to LAZY. This attribute makes only sense on the owning side, the inverse side *ALWAYS* has to use the `FETCH` strategy. - -The definition for the owning side relies on a bunch of mapping defaults for the join column names. -Without the nested `` element Doctrine assumes to foreign key to be called `user_id` on the Address -Entities table. This is because the `MyProject\Address` entity is the owning side of this association, which means -it contains the foreign key. - -The completed explicitly defined mapping is: - - [xml] - - - - - - -### Defining Many-To-One Associations - -The many-to-one association is *ALWAYS* the owning side of any bidirectional association. This simplifies the mapping -compared to the one-to-one case. The minimal mapping for this association looks like: - - [xml] - - - - -Required attributes: - -* field - Name of the property/field on the entity's PHP class. -* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! - -Optional attributes: - -* inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference. -* orphan-removal - If true the entity on the inverse side is always deleted when the owning side entity is and it is not connected to any other owning side entity anymore. Defaults to false. -* fetch - Either LAZY or FETCH, defaults to LAZY. - -This definition relies on a bunch of mapping defaults with regards to the naming of the join-column/foreign key. The -explicitly defined mapping includes a `` tag nested inside the many-to-one association tag: - - [xml] - - - - - - -The join-column attribute `name` specifies the column name of the foreign key and -the `referenced-column-name` attribute specifies the name of the primary key column -on the User entity. - -### Defining One-To-Many Associations - -The one-to-many association is *ALWAYS* the inverse side of any association. There exists no such thing as a -uni-directional one-to-many association, which means this association only ever exists for bi-directional associations. - - [xml] - - - - -Required attributes: - -* field - Name of the property/field on the entity's PHP class. -* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! -* mapped-by - Name of the field on the owning side (here Phonenumber entity) that contains the owning side association. - -Optional attributes: - -* fetch - Either LAZY or FETCH, defaults to LAZY. - -### Defining Many-To-Many Associations - -From all the associations the many-to-many has the most complex definition. When you rely on the mapping defaults -you can omit many definitions and rely on their implicit values. - - [xml] - - - - -Required attributes: - -* field - Name of the property/field on the entity's PHP class. -* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! - -Optional attributes: - -* mapped-by - Name of the field on the owning side that contains the owning side association if the defined many-to-many association is on the inverse side. -* inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference. -* fetch - Either LAZY or FETCH, defaults to LAZY. - -The mapping defaults would lead to a join-table with the name "User_Group" being created that contains two columns -"user_id" and "group_id". The explicit definition of this mapping would be: - - [xml] - - - - - - - - - - - - - -Here both the `` and `` tags are necessary to tell Doctrine for which side the -specified join-columns apply. These are nested inside a `` attribute which allows to specify -the table name of the many-to-many join-table. - -### Cascade Element - -Doctrine allows cascading of several UnitOfWork operations to related entities. You can specify the cascade -operations in the `` element inside any of the association mapping tags. - - [xml] - - - - - - - - -Besides `` the following operations can be specified by their respective tags: - -* `` -* `` -* `` -* `` - -### Join Column Element - -In any explicitly defined association mapping you will need the `` tag. It defines how the -foreign key and primary key names are called that are used for joining two entities. - -Required attributes: - -* name - The column name of the foreign key. -* referenced-column-name - The column name of the associated entities primary key - -Optional attributes: - -* unique - If the join column should contain a UNIQUE constraint. This makes sense for Many-To-Many join-columns only to simulate a one-to-many unidirectional using a join-table. -* nullable - should the join column be nullable, defaults to true. -* on-delete - Foreign Key Cascade action to perform when entity is deleted, defaults to NO ACTION/RESTRICT but can be set to "CASCADE". - -### Defining Order of To-Many Associations - -You can require one-to-many or many-to-many associations to be retrieved using an additional `ORDER BY`. - - [xml] - - - - - - - - -### Defining Indexes or Unique Constraints - -To define additional indexes or unique constraints on the entities table you can use the -`` and `` elements: - - [xml] - - - - - - - - - - - - -You have to specify the column and not the entity-class field names in the index and unique-constraint -definitions. \ No newline at end of file diff --git a/manual/en/yaml-mapping.rst b/manual/en/yaml-mapping.rst index 54245c5c2..9f62eea8d 100644 --- a/manual/en/yaml-mapping.rst +++ b/manual/en/yaml-mapping.rst @@ -1,3 +1,6 @@ +YAML Mapping +============ + The YAML mapping driver enables you to provide the ORM metadata in form of YAML documents. @@ -42,9 +45,8 @@ Example As a quick start, here is a small example document that makes use of several common elements: -:: +.. code-block:: yaml - [yml] # Doctrine.Tests.ORM.Mapping.User.dcm.yml Doctrine\Tests\ORM\Mapping\User: type: entity diff --git a/manual/en/yaml-mapping.txt b/manual/en/yaml-mapping.txt deleted file mode 100644 index e043eb121..000000000 --- a/manual/en/yaml-mapping.txt +++ /dev/null @@ -1,66 +0,0 @@ -The YAML mapping driver enables you to provide the ORM metadata in form of YAML documents. - -The YAML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: - - * Each entity/mapped superclass must get its own dedicated YAML mapping document. - * The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.). - * All mapping documents should get the extension ".dcm.yml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough. - -- - - setFileExtension('.yml'); - -It is recommended to put all YAML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the YamlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: - - setMetadataDriverImpl($driver); - - -## Example - -As a quick start, here is a small example document that makes use of several common elements: - - [yml] - # Doctrine.Tests.ORM.Mapping.User.dcm.yml - Doctrine\Tests\ORM\Mapping\User: - type: entity - table: cms_users - id: - id: - type: integer - generator: - strategy: AUTO - fields: - name: - type: string - length: 50 - oneToOne: - address: - targetEntity: Address - joinColumn: - name: address_id - referencedColumnName: id - oneToMany: - phonenumbers: - targetEntity: Phonenumber - mappedBy: user - cascade: cascadePersist - manyToMany: - groups: - targetEntity: Group - joinTable: - name: cms_users_groups - joinColumns: - user_id: - referencedColumnName: id - inverseJoinColumns: - group_id: - referencedColumnName: id - lifecycleCallbacks: - prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] - postPersist: [ doStuffOnPostPersist ] - - Be aware that class-names specified in the YAML files should be fully qualified. \ No newline at end of file From c22bddc9a75da8192d07cbd1f23c78690cc16a07 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 9 Nov 2010 22:17:31 +0100 Subject: [PATCH 132/430] DDC-866 Fix deprecated EBNF rule --- manual/en/dql-doctrine-query-language.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/dql-doctrine-query-language.rst b/manual/en/dql-doctrine-query-language.rst index e99916d2c..0c824cd57 100644 --- a/manual/en/dql-doctrine-query-language.rst +++ b/manual/en/dql-doctrine-query-language.rst @@ -1297,7 +1297,7 @@ Path Expressions StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField /* "u.Group" */ - SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField + SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField /* "u.Group.Permissions" */ CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField From ee042bc642629f88110343ecf3a78c980db9f05b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 9 Nov 2010 22:18:25 +0100 Subject: [PATCH 133/430] Some changes left --- manual/en/index.rst | 2 +- manual/en/introduction.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/manual/en/index.rst b/manual/en/index.rst index 91eb8f6b6..cb638ddbb 100644 --- a/manual/en/index.rst +++ b/manual/en/index.rst @@ -36,7 +36,7 @@ Contents: tools metadata-drivers best-practices - limitations-and-Known-issues + limitations-and-known-issues Indices and tables ================== diff --git a/manual/en/introduction.rst b/manual/en/introduction.rst index f4e540574..66e9f3a41 100644 --- a/manual/en/introduction.rst +++ b/manual/en/introduction.rst @@ -1,3 +1,6 @@ +Introduction +============ + Welcome ------- From de1d72f34879edd89c37c5887132b057dfa8e261 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 9 Nov 2010 22:19:07 +0100 Subject: [PATCH 134/430] DDC-866 - Fix EBNF grammer rule --- manual/en/dql-doctrine-query-language.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index c8dcefbc9..5c6e696e5 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -938,7 +938,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField /* "u.Group" */ - SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField + SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField /* "u.Group.Permissions" */ CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField From 87c1c50bfa0ca3fad9dd496ad3cff1631df8c41d Mon Sep 17 00:00:00 2001 From: "Timo A. Hummel" Date: Thu, 11 Nov 2010 01:22:30 +0800 Subject: [PATCH 135/430] Fixed documentation issue for cascade="ALL" (DDC-850) --- manual/en/annotations-reference.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/en/annotations-reference.txt b/manual/en/annotations-reference.txt index 7b162a77c..9a54949a6 100644 --- a/manual/en/annotations-reference.txt +++ b/manual/en/annotations-reference.txt @@ -351,7 +351,7 @@ Example: [php] /** - * @ManyToOne(targetEntity="Cart", cascade="ALL", fetch="EAGER") + * @ManyToOne(targetEntity="Cart", cascade={"ALL"}, fetch="EAGER") */ private $cart; From 69d4e185d8d5b0699474fca5d7741363b963b355 Mon Sep 17 00:00:00 2001 From: "Timo A. Hummel" Date: Thu, 11 Nov 2010 02:43:57 +0800 Subject: [PATCH 136/430] Added documentation notice regarding @version in combination with @id (DDC-873) --- manual/en/annotations-reference.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manual/en/annotations-reference.txt b/manual/en/annotations-reference.txt index 9a54949a6..8df9ab2c2 100644 --- a/manual/en/annotations-reference.txt +++ b/manual/en/annotations-reference.txt @@ -599,7 +599,8 @@ Example: +++ @Version Marker annotation that defines a specified column as version attribute used in an optimistic locking scenario. -It only works on [@Column](#ann_column) annotations that have the type integer or datetime. +It only works on [@Column](#ann_column) annotations that have the type integer or datetime. Combining @Version +with [@Id](#ann_id) is not supported. Example: From 54a61bbbc216ccec509d7c053b11fe36e5a39c52 Mon Sep 17 00:00:00 2001 From: goran Date: Sat, 23 Oct 2010 20:45:45 +0800 Subject: [PATCH 137/430] Changed method name in an example for fetching class metadata --- manual/en/php-mapping.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/manual/en/php-mapping.txt b/manual/en/php-mapping.txt index 19a4f66a5..2167055a1 100644 --- a/manual/en/php-mapping.txt +++ b/manual/en/php-mapping.txt @@ -45,7 +45,10 @@ Now we can easily retrieve the populated `ClassMetadata` instance where the `PHP includes the file and the `ClassMetadataFactory` caches it for later retrieval: [php] - $class = $em->getMetadataFor('Entities\User'); + $class = $em->getClassMetadata('Entities\User'); + // or + $class = $em->getMetadataFactory()->getMetadataFor('Entities\User'); + ++ Static Function From f2b20e594922d219f2eb51853e3d0e7b0a712caa Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 10 Nov 2010 23:43:24 +0100 Subject: [PATCH 138/430] DDC-736 - Add note about order of the identfication variables during fetch joins requirements. --- manual/en/dql-doctrine-query-language.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manual/en/dql-doctrine-query-language.txt b/manual/en/dql-doctrine-query-language.txt index 5c6e696e5..1af13b469 100644 --- a/manual/en/dql-doctrine-query-language.txt +++ b/manual/en/dql-doctrine-query-language.txt @@ -84,6 +84,9 @@ root level of the result array. In the previous example an array of User instanc and the address of each user is fetched and hydrated into the `User#address` variable. If you access the address Doctrine does not need to lazy load the association with another query. +You have to make sure that the order of the fetch join aliases matches the order in which +the entities are defined in the FROM and JOIN clauses. Otherwise the hydration algorithm will go bust. + > **NOTE** > Doctrine allows you to walk all the associations between all the objects in your domain model. > Objects that were not already loaded from the database are replaced with lazy load proxy instances. From 49ecdff0165dbdfaadf4b62cd2ff3911cca997fd Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 10 Nov 2010 23:49:07 +0100 Subject: [PATCH 139/430] DDC-763 - Add note about handling merges of multiple entities which share subgraphs of objects --- manual/en/working-with-objects.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/manual/en/working-with-objects.txt b/manual/en/working-with-objects.txt index 0decce091..a24949d62 100644 --- a/manual/en/working-with-objects.txt +++ b/manual/en/working-with-objects.txt @@ -308,6 +308,12 @@ and `remove`. The most common scenario for the `merge` operation is to reattach entities to an EntityManager that come from some cache (and are therefore detached) and you want to modify and persist such an entity. +> **CAUTION** +> If you need to perform multiple merges of entities that share certain subparts +> of their object-graphs and cascade merge, then you have to call `EntityManager#clear()` between the +> successive calls to `EntityManager#merge()`. Otherwise you might end up with +> multiple copies of the "same" object in the database, however with different ids. + > **NOTE** > If you load some detached entities from a cache and you do not need to persist or > delete them or otherwise make use of them without the need for persistence services From 1b9e9c019d29a1ba5fee13cb3554da1c6b3be829 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 16 Nov 2010 22:03:27 +0100 Subject: [PATCH 140/430] DDC-862 - Fix Codeigniter Cookbook entry --- cookbook/en/integrating-with-codeigniter.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cookbook/en/integrating-with-codeigniter.txt b/cookbook/en/integrating-with-codeigniter.txt index e058ec6b9..fa80f1f1f 100644 --- a/cookbook/en/integrating-with-codeigniter.txt +++ b/cookbook/en/integrating-with-codeigniter.txt @@ -61,6 +61,9 @@ Now, here is what your Doctrine.php file should look like. Customize it to your $config = new Configuration; $cache = new ArrayCache; $config->setMetadataCacheImpl($cache); + $driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities')); + $config->setMetadataDriverImpl($driverImpl); + $config->setQueryCacheImpl($cache); // Proxy configuration From 6789f2c8d131fd4796fa7a1ef504e6410fd1a98f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 3 Dec 2010 17:58:01 +0100 Subject: [PATCH 141/430] Several changes and fixes to the docs. --- cookbook/en/getting-started-xml-edition.txt | 5 +- manual/en/basic-mapping.txt | 20 ++++---- manual/en/inheritance-mapping.txt | 53 +++++++++++++-------- manual/en/tools.txt | 6 +-- 4 files changed, 48 insertions(+), 36 deletions(-) diff --git a/cookbook/en/getting-started-xml-edition.txt b/cookbook/en/getting-started-xml-edition.txt index 74e637b0c..c4b8e55e8 100644 --- a/cookbook/en/getting-started-xml-edition.txt +++ b/cookbook/en/getting-started-xml-edition.txt @@ -374,9 +374,8 @@ steps and then discuss them step by step: [php] // Setup Autoloader (1) - require '/path/to/lib/Doctrine/Common/ClassLoader.php'; - $loader = new Doctrine\Common\ClassLoader("Doctrine", '/path/to/Doctrine/trunk/lib/'); - $loader->register(); + // See the ORM Documentation Chapter "Configuration" for the up to day information + // on autoloading Doctrine 2. $config = new Doctrine\ORM\Configuration(); // (2) diff --git a/manual/en/basic-mapping.txt b/manual/en/basic-mapping.txt index 09dd391b6..e86c9adff 100644 --- a/manual/en/basic-mapping.txt +++ b/manual/en/basic-mapping.txt @@ -186,16 +186,6 @@ Here is an example: // Register my type Type::addType('mytype', 'My\Project\Types\MyType'); -As can be seen above, when registering the custom types in the configuration you specify a unique name -for the mapping type and map that to the corresponding fully qualified class name. Now you can use your new type in your mapping like this: - - [php] - class MyPersistentClass - { - /** @Column(type="mytype") */ - private $field; - } - To have Schema-Tool convert the underlying database type of your new "mytype" directly into an instance of `MyType` you have to additionally register this mapping with your database platform: @@ -207,6 +197,16 @@ Now using Schema-Tool, whenever it detects a column having the `db_mytype` it wi Doctrine Type instance for Schema representation. Keep in mind that you can easily produce clashes this way, each database type can only map to exactly one Doctrine mapping type. +As can be seen above, when registering the custom types in the configuration you specify a unique name +for the mapping type and map that to the corresponding fully qualified class name. Now you can use your new type in your mapping like this: + + [php] + class MyPersistentClass + { + /** @Column(type="mytype") */ + private $field; + } + ++ Identifiers / Primary Keys Every entity class needs an identifier/primary key. You designate the field that serves as the identifier with the `@Id` marker annotation. Here is an example: diff --git a/manual/en/inheritance-mapping.txt b/manual/en/inheritance-mapping.txt index 48c9d91a2..ca325be56 100644 --- a/manual/en/inheritance-mapping.txt +++ b/manual/en/inheritance-mapping.txt @@ -50,8 +50,6 @@ The DDL for the corresponding database schema would look something like this (th As you can see from this DDL snippet, there is only a single table for the entity subclass. All the mappings from the mapped superclass were inherited to the subclass as if they had been defined on that class directly. - - ++ Single Table Inheritance [Single Table Inheritance](http://martinfowler.com/eaaCatalog/singleTableInheritance.html) is an inheritance mapping strategy where all classes of a hierarchy are mapped to a single database table. In order to distinguish which row represents which type in the hierarchy a so-called discriminator column is used. @@ -59,15 +57,15 @@ As you can see from this DDL snippet, there is only a single table for the entit Example: [php] - namespace MyProject\Model; + namespace Zoo\Entities; /** * @Entity * @InheritanceType("SINGLE_TABLE") * @DiscriminatorColumn(name="discr", type="string") - * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + * @DiscriminatorMap({"animal" = "Animal", "cat" = "Cat", "dog" = "Dog"}) */ - class Person + class Animal { // ... } @@ -75,15 +73,23 @@ Example: /** * @Entity */ - class Employee extends Person + class Cat extends Animal + { + // ... + } + + /** + * @Entity + */ + class Dog extends Animal { // ... } Things to note: -* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified on the topmost class that is part of the mapped entity hierarchy. -* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of a certain type. In the case above a value of "person" identifies a row as being of type `Person` and "employee" identifies a row as being of type `Employee`. +* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified *ONLY* on the topmost class that is part of the mapped entity hierarchy. +* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of a certain type. In the case above a value of "animal" identifies a row as being of type `Animal`, "cat" identifies a row as being of type `Cat` and "dog" is an instance of `Dog`. * The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied. +++ Design-time considerations @@ -111,29 +117,23 @@ Doctrine 2 implements this strategy through the use of a discriminator column in Example: [php] - namespace MyProject\Model; - + namespace Zoo\Entities; + /** * @Entity * @InheritanceType("JOINED") * @DiscriminatorColumn(name="discr", type="string") - * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) + * @DiscriminatorMap({"animal" = "Animal", "cat" = "Cat", "dog" = "Dog"}) */ - class Person - { - // ... - } - - /** @Entity */ - class Employee extends Person + class Animal { // ... } Things to note: -* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified on the topmost class that is part of the mapped entity hierarchy. -* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of which type. In the case above a value of "person" identifies a row as being of type `Person` and "employee" identifies a row as being of type `Employee`. +* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified *ONLY* on the topmost class that is part of the mapped entity hierarchy. +* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of a certain type. In the case above a value of "animal" identifies a row as being of type `Animal`, "cat" identifies a row as being of type `Cat` and "dog" is an instance of `Dog`. * The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied. > **NOTE** @@ -157,3 +157,16 @@ For each entity in the Class-Table Inheritance hierarchy all the mapped fields h table of this entity. Additionally each child table has to have an id column that matches the id column definition on the root table (except for any sequence or auto-increment details). Furthermore each child table has to have a foreign key pointing from the id column to the root table id column and cascading on delete. + +++ Querying Inheritance Hierachies + +By definition you cannot query for the type of a MappedSuperclass. However in Single- or Class-Table Inheritance scenarios +you can query for any instance specified in the DiscriminatorMap or the root entity name. For both inheritance scenarios +the following queries would work: + + [php] + $animals = $em->getRepository('Zoo\Entities\Animal')->findAll(); + $cats = $em->getRepository('Zoo\Entities\Cat')->findAll(); + $dogs = $em->getRepository('Zoo\Entities\Dog')->findAll(); + +This works for all finder methods, the Query Builder and DQL. \ No newline at end of file diff --git a/manual/en/tools.txt b/manual/en/tools.txt index d461bafcf..1ab251dfc 100644 --- a/manual/en/tools.txt +++ b/manual/en/tools.txt @@ -25,7 +25,7 @@ All the commands of the Doctrine Console require either the `db` or the `em` hel In case of a project that is dealing exclusively with DBAL, the ConnectionHelper is required: [php] - $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( + $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array( 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn) )); $cli->setHelperSet($helperSet); @@ -33,7 +33,7 @@ In case of a project that is dealing exclusively with DBAL, the ConnectionHelper When dealing with the ORM package, the EntityManagerHelper is required: [php] - $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( + $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array( 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); $cli->setHelperSet($helperSet); @@ -62,7 +62,7 @@ bootstrap code and predefines the needed HelperSet attributes mentioned above. A $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); - $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( + $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array( 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); From 46983465fd52c5c5bb1285a496d6cbc1cbef94a3 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 3 Dec 2010 20:13:10 +0100 Subject: [PATCH 142/430] Finialized ReST doc changes, merged changes from latest Markdown docs. --- cookbook/en.rst | 34 --- cookbook/en.txt | 11 - cookbook/en/Makefile | 89 -------- cookbook/en/conf.py | 196 ----------------- cookbook/en/index.rst | 32 --- cookbook/en/make.bat | 113 ---------- {manual/en => en}/Makefile | 0 en/_templates/layout.html | 198 +++++++++++++++++ {manual/en => en}/conf.py | 14 +- .../en => en/cookbook}/aggregate-fields.rst | 24 ++- .../en => en/cookbook}/dql-custom-walkers.rst | 16 +- .../cookbook}/dql-user-defined-functions.rst | 27 +-- ...menting-arrayaccess-for-domain-objects.rst | 8 +- ...nting-the-notify-changetracking-policy.rst | 6 +- .../implementing-wakeup-or-clone.rst | 6 +- .../integrating-with-codeigniter.rst | 9 +- .../en => en/cookbook}/sql-table-prefixes.rst | 4 +- .../strategy-cookbook-introduction.rst | 6 +- .../cookbook}/validation-of-entities.rst | 18 +- en/index.rst | 70 ++++++ {manual/en => en}/make.bat | 0 .../reference}/annotations-reference.rst | 201 +++++++++++------- {manual/en => en/reference}/architecture.rst | 2 + .../reference}/association-mapping.rst | 49 ++--- {manual/en => en/reference}/basic-mapping.rst | 41 ++-- .../en => en/reference}/batch-processing.rst | 24 ++- .../en => en/reference}/best-practices.rst | 2 +- {manual/en => en/reference}/caching.rst | 82 +++---- .../reference}/change-tracking-policies.rst | 6 +- {manual/en => en/reference}/configuration.rst | 40 ++-- .../dql-doctrine-query-language.rst | 161 +++++++------- {manual/en => en/reference}/events.rst | 38 ++-- .../reference}/improving-performance.rst | 7 +- .../reference}/inheritance-mapping.rst | 6 +- {manual/en => en/reference}/introduction.rst | 31 ++- .../limitations-and-known-issues.rst | 4 +- .../en => en/reference}/metadata-drivers.rst | 22 +- {manual/en => en/reference}/native-sql.rst | 22 +- .../en => en/reference}/partial-objects.rst | 2 +- {manual/en => en/reference}/php-mapping.rst | 16 +- {manual/en => en/reference}/query-builder.rst | 28 +-- {manual/en => en/reference}/tools.rst | 46 ++-- .../transactions-and-concurrency.rst | 18 +- .../reference}/working-with-associations.rst | 31 ++- .../reference}/working-with-objects.rst | 65 +++--- {manual/en => en/reference}/xml-mapping.rst | 7 +- {manual/en => en/reference}/yaml-mapping.rst | 4 +- .../getting-started-xml-edition.rst | 77 +++---- generate-docs.sh | 2 +- manual/en.rst | 79 ------- manual/en.txt | 26 --- manual/en/index.rst | 47 ---- 52 files changed, 897 insertions(+), 1170 deletions(-) delete mode 100644 cookbook/en.rst delete mode 100644 cookbook/en.txt delete mode 100644 cookbook/en/Makefile delete mode 100644 cookbook/en/conf.py delete mode 100644 cookbook/en/index.rst delete mode 100644 cookbook/en/make.bat rename {manual/en => en}/Makefile (100%) create mode 100644 en/_templates/layout.html rename {manual/en => en}/conf.py (95%) rename {cookbook/en => en/cookbook}/aggregate-fields.rst (97%) rename {cookbook/en => en/cookbook}/dql-custom-walkers.rst (97%) rename {cookbook/en => en/cookbook}/dql-user-defined-functions.rst (93%) rename {cookbook/en => en/cookbook}/implementing-arrayaccess-for-domain-objects.rst (96%) rename {cookbook/en => en/cookbook}/implementing-the-notify-changetracking-policy.rst (95%) rename {cookbook/en => en/cookbook}/implementing-wakeup-or-clone.rst (95%) rename {cookbook/en => en/cookbook}/integrating-with-codeigniter.rst (95%) rename {cookbook/en => en/cookbook}/sql-table-prefixes.rst (98%) rename {cookbook/en => en/cookbook}/strategy-cookbook-introduction.rst (99%) rename {cookbook/en => en/cookbook}/validation-of-entities.rst (95%) create mode 100644 en/index.rst rename {manual/en => en}/make.bat (100%) rename {manual/en => en/reference}/annotations-reference.rst (82%) rename {manual/en => en/reference}/architecture.rst (99%) rename {manual/en => en/reference}/association-mapping.rst (98%) rename {manual/en => en/reference}/basic-mapping.rst (96%) rename {manual/en => en/reference}/batch-processing.rst (93%) rename {manual/en => en/reference}/best-practices.rst (99%) rename {manual/en => en/reference}/caching.rst (91%) rename {manual/en => en/reference}/change-tracking-policies.rst (98%) rename {manual/en => en/reference}/configuration.rst (97%) rename {manual/en => en/reference}/dql-doctrine-query-language.rst (96%) rename {manual/en => en/reference}/events.rst (98%) rename {manual/en => en/reference}/improving-performance.rst (92%) rename {manual/en => en/reference}/inheritance-mapping.rst (99%) rename {manual/en => en/reference}/introduction.rst (96%) rename {manual/en => en/reference}/limitations-and-known-issues.rst (99%) rename {manual/en => en/reference}/metadata-drivers.rst (95%) rename {manual/en => en/reference}/native-sql.rst (98%) rename {manual/en => en/reference}/partial-objects.rst (99%) rename {manual/en => en/reference}/php-mapping.rst (95%) rename {manual/en => en/reference}/query-builder.rst (98%) rename {manual/en => en/reference}/tools.rst (96%) rename {manual/en => en/reference}/transactions-and-concurrency.rst (98%) rename {manual/en => en/reference}/working-with-associations.rst (97%) rename {manual/en => en/reference}/working-with-objects.rst (96%) rename {manual/en => en/reference}/xml-mapping.rst (99%) rename {manual/en => en/reference}/yaml-mapping.rst (98%) rename {cookbook/en => en/tutorials}/getting-started-xml-edition.rst (97%) delete mode 100644 manual/en.rst delete mode 100644 manual/en.txt delete mode 100644 manual/en/index.rst diff --git a/cookbook/en.rst b/cookbook/en.rst deleted file mode 100644 index 2c5786b1d..000000000 --- a/cookbook/en.rst +++ /dev/null @@ -1,34 +0,0 @@ -Getting Started XML-Edition -=========================== - -Implementing ArrayAccess for domain objects -=========================================== - -Implementing the NOTIFY change-tracking policy -============================================== - -Validation of Entities -====================== - -Implementing wakeup or clone -============================ - -Integrating with CodeIgniter -============================ - -DQL Custom Walkers -================== - -DQL User Defined Functions -========================== - -SQL Table Prefixes -================== - -Strategy Cookbook Introduction -============================== - -Aggregate Fields -================ - - diff --git a/cookbook/en.txt b/cookbook/en.txt deleted file mode 100644 index 6a77ba99d..000000000 --- a/cookbook/en.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Getting Started XML-Edition -# Implementing ArrayAccess for domain objects -# Implementing the NOTIFY change-tracking policy -# Validation of Entities -# Implementing wakeup or clone -# Integrating with CodeIgniter -# DQL Custom Walkers -# DQL User Defined Functions -# SQL Table Prefixes -# Strategy Cookbook Introduction -# Aggregate Fields \ No newline at end of file diff --git a/cookbook/en/Makefile b/cookbook/en/Makefile deleted file mode 100644 index 987598317..000000000 --- a/cookbook/en/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORMCookbook.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORMCookbook.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/cookbook/en/conf.py b/cookbook/en/conf.py deleted file mode 100644 index 18997a35d..000000000 --- a/cookbook/en/conf.py +++ /dev/null @@ -1,196 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Doctrine 2 ORM Cookbook documentation build configuration file, created by -# sphinx-quickstart on Mon Nov 1 21:13:13 2010. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Doctrine 2 ORM Cookbook' -copyright = u'2010, Doctrine Project Team' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '2.0' -# The full version, including alpha/beta/rc tags. -release = '2.0.0-BETA4' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -language = 'en' - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -highlight_language = 'php' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Doctrine2ORMCookbookdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'Doctrine2ORMCookbook.tex', u'Doctrine 2 ORM Cookbook Documentation', - u'Doctrine Project Team', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True diff --git a/cookbook/en/index.rst b/cookbook/en/index.rst deleted file mode 100644 index 4c4be6adc..000000000 --- a/cookbook/en/index.rst +++ /dev/null @@ -1,32 +0,0 @@ -.. Doctrine 2 ORM Cookbook documentation master file, created by - sphinx-quickstart on Mon Nov 1 21:13:13 2010. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to Doctrine 2 ORM Cookbook's documentation! -=================================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - getting-started-xml-edition - implementing-arrayaccess-for-domain-objects - implementing-the-notify-changetracking-policy - validations-of-entities - implementing-wakeup-or-clone - integrating-with-codeigniter - dql-custom-walkers - dql-user-defined-functions - sql-table-prefixes - strategy-cookbook-introdution - aggregates-fields - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/cookbook/en/make.bat b/cookbook/en/make.bat deleted file mode 100644 index 9f1a938d9..000000000 --- a/cookbook/en/make.bat +++ /dev/null @@ -1,113 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -set SPHINXBUILD=sphinx-build -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORMCookbook.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORMCookbook.ghc - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/manual/en/Makefile b/en/Makefile similarity index 100% rename from manual/en/Makefile rename to en/Makefile diff --git a/en/_templates/layout.html b/en/_templates/layout.html new file mode 100644 index 000000000..3380dbe13 --- /dev/null +++ b/en/_templates/layout.html @@ -0,0 +1,198 @@ +{%- block doctype -%} + +{%- endblock %} +{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} +{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} +{%- set url_root = pathto('', 1) %} +{%- if url_root == '#' %}{% set url_root = '' %}{% endif %} + +{%- macro relbar() %} + +{%- endmacro %} + +{%- macro sidebar() %} + {%- if not embedded %}{% if not theme_nosidebar|tobool %} +
+
+ {%- block sidebarlogo %} + {%- if logo %} + + {%- endif %} + {%- endblock %} + {%- block sidebartoc %} + {%- if display_toc %} +

{{ _('Table Of Contents') }}

+ {{ toc }} + {%- endif %} + {%- endblock %} + {%- block sidebarrel %} + {%- if prev %} +

{{ _('Previous topic') }}

+

{{ prev.title }}

+ {%- endif %} + {%- if next %} +

{{ _('Next topic') }}

+

{{ next.title }}

+ {%- endif %} + {%- endblock %} + {%- block sidebarsourcelink %} + {%- if show_source and has_source and sourcename %} +

{{ _('This Page') }}

+ + {%- endif %} + {%- endblock %} + {%- if customsidebar %} + {% include customsidebar %} + {%- endif %} + {%- block sidebarsearch %} + {%- if pagename != "search" %} + + + {%- endif %} + {%- endblock %} +
+
+ {%- endif %}{% endif %} +{%- endmacro %} + + + + + {{ metatags }} + {%- if not embedded and docstitle %} + {%- set titlesuffix = " — "|safe + docstitle|e %} + {%- else %} + {%- set titlesuffix = "" %} + {%- endif %} + {{ title|striptags }}{{ titlesuffix }} + + + {%- if not embedded %} + + {%- for scriptfile in script_files %} + + {%- endfor %} + {%- if use_opensearch %} + + {%- endif %} + {%- if favicon %} + + {%- endif %} + {%- endif %} +{%- block linktags %} + {%- if hasdoc('about') %} + + {%- endif %} + {%- if hasdoc('genindex') %} + + {%- endif %} + {%- if hasdoc('search') %} + + {%- endif %} + {%- if hasdoc('copyright') %} + + {%- endif %} + + {%- if parents %} + + {%- endif %} + {%- if next %} + + {%- endif %} + {%- if prev %} + + {%- endif %} +{%- endblock %} +{%- block extrahead %} {% endblock %} + + +{%- block header %}{% endblock %} + +{%- block relbar1 %}{{ relbar() }}{% endblock %} + +{%- block sidebar1 %} {# possible location for sidebar #} {% endblock %} + +
+{%- block document %} +
+ {%- if not embedded %}{% if not theme_nosidebar|tobool %} +
+ {%- endif %}{% endif %} +
+ {% block body %} {% endblock %} +
+ {%- if not embedded %}{% if not theme_nosidebar|tobool %} +
+ {%- endif %}{% endif %} +
+{%- endblock %} + +{%- block sidebar2 %}{{ sidebar() }}{% endblock %} +
+
+ +{%- block relbar2 %}{{ relbar() }}{% endblock %} + +{%- block footer %} + +{%- endblock %} + + diff --git a/manual/en/conf.py b/en/conf.py similarity index 95% rename from manual/en/conf.py rename to en/conf.py index b972b640f..7069824f1 100644 --- a/manual/en/conf.py +++ b/en/conf.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Doctrine 2 ORM documentation build configuration file, created by -# sphinx-quickstart on Mon Nov 1 21:19:39 2010. +# sphinx-quickstart on Fri Dec 3 18:10:24 2010. # # This file is execfile()d with the current directory set to its containing dir. # @@ -38,7 +38,7 @@ master_doc = 'index' # General information about the project. project = u'Doctrine 2 ORM' -copyright = u'2010, Roman Borschel, Guilherme Blanco, Benjamin Eberlei, Jonathan Wage' +copyright = u'2010, Doctrine Project Team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -47,11 +47,11 @@ copyright = u'2010, Roman Borschel, Guilherme Blanco, Benjamin Eberlei, Jonathan # The short X.Y version. version = '2.0' # The full version, including alpha/beta/rc tags. -release = '2.0.0-BETA4' +release = '2.0.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -language = 'en' +language = 'php' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -78,13 +78,11 @@ exclude_trees = ['_build'] # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +show_authors = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' -highlight_language = 'php' - # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] @@ -175,7 +173,7 @@ htmlhelp_basename = 'Doctrine2ORMdoc' # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Doctrine2ORM.tex', u'Doctrine 2 ORM Documentation', - u'Roman Borschel, Guilherme Blanco, Benjamin Eberlei, Jonathan Wage', 'manual'), + u'Doctrine Project Team', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of diff --git a/cookbook/en/aggregate-fields.rst b/en/cookbook/aggregate-fields.rst similarity index 97% rename from cookbook/en/aggregate-fields.rst rename to en/cookbook/aggregate-fields.rst index ef91a4d62..521f5d025 100644 --- a/cookbook/en/aggregate-fields.rst +++ b/en/cookbook/aggregate-fields.rst @@ -1,6 +1,8 @@ Aggregate Fields ================ +.. sectionauthor:: Benjamin Eberlei + You will often come across the requirement to display aggregate values of data that can be computed by using the MIN, MAX, COUNT or SUM SQL functions. For any ORM this is a tricky issue @@ -27,7 +29,7 @@ added on the ``Entry`` object. Our entities look like: -:: +.. code-block:: php ``Entry`` relation with this method: -:: +.. code-block:: php + The Doctrine Query Language (DQL) is a proprietary sql-dialect that substitutes tables and columns for Entity names and their fields. Using DQL you write a query against the database using your @@ -58,7 +60,7 @@ Say you have a blog and posts all with one category and one author. A query for the front-page or any archive page might look something like: -.. code-clock:: sql +.. code-block:: sql SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... @@ -72,7 +74,7 @@ posts that match the WHERE clause of this query to be able to predict the number of pages to show to the user. A draft of the DQL query for pagination would look like: -.. code-clock:: sql +.. code-block:: sql SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... @@ -80,7 +82,7 @@ Now you could go and write each of these queries by hand, or you can use a tree walker to modify the AST for you. Lets see how the API would look for this use-case: -:: +.. code-block:: php + By default DQL supports a limited subset of all the vendor-specific SQL functions common between all the vendors. However in many cases once you have decided on a specific database vendor, you will never @@ -8,13 +10,12 @@ change it during the life of your project. This decision for a specific vendor potentially allows you to make use of powerful SQL features that are unique to the vendor. - **Note** - - It is worth to mention that Doctrine 2 also allows you to handwrite - your SQL instead of extending the DQL parser, which is sort of an - advanced extension point. You can map arbitrary SQL to your objects - and gain access to vendor specific functionalities using the - ``EntityManager#createNativeQuery()`` API. +It is worth to mention that Doctrine 2 also allows you to handwrite +your SQL instead of extending the DQL parser. Extending DQL is sort of an +advanced extension point. You can map arbitrary SQL to your objects +and gain access to vendor specific functionalities using the +``EntityManager#createNativeQuery()`` API as described in +the :doc:`Native Query <../reference/native-sql>` chapter. The DQL Parser has hooks to register functions that can then be @@ -37,7 +38,7 @@ Registering your own DQL functions You can register your functions adding them to the ORM configuration: -:: +.. code-block:: php p.created diff --git a/cookbook/en/implementing-arrayaccess-for-domain-objects.rst b/en/cookbook/implementing-arrayaccess-for-domain-objects.rst similarity index 96% rename from cookbook/en/implementing-arrayaccess-for-domain-objects.rst rename to en/cookbook/implementing-arrayaccess-for-domain-objects.rst index aeb9a7ada..4eb2b717b 100644 --- a/cookbook/en/implementing-arrayaccess-for-domain-objects.rst +++ b/en/cookbook/implementing-arrayaccess-for-domain-objects.rst @@ -1,6 +1,8 @@ Implementing ArrayAccess for Domain Objects =========================================== +.. sectionauthor:: Roman Borschel (roman@code-factory.org) + This recipe will show you how to implement ArrayAccess for your domain objects in order to allow more uniform access, for example in templates. In these examples we will implement ArrayAccess on a @@ -18,7 +20,7 @@ at runtime. Note that this implementation has 2 main caveats: - It will not work with private fields - It will not go through any getters/setters -:: +.. code-block:: php `_, it is usually not allowed for an entity to implement ``__wakeup`` @@ -15,7 +17,7 @@ Safely implementing \_\_wakeup To safely implement ``__wakeup``, simply enclose your implementation code in an identity check as follows: -:: +.. code-block:: php setMetadataCacheImpl($cache); + $driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities')); + $config->setMetadataDriverImpl($driverImpl); + $config->setQueryCacheImpl($cache); // Proxy configuration @@ -115,7 +118,7 @@ Now to use it Whenever you need a reference to the entity manager inside one of your controllers, views, or models you can do this: -:: +.. code-block:: php doctrine->em; @@ -126,7 +129,7 @@ EntityManager do your Doctrine 2.0 voodoo as normal. Note: If you do not choose to autoload the Doctrine library, you will need to put this line before you get a reference to it: -:: +.. code-block:: php load->library('doctrine'); diff --git a/cookbook/en/sql-table-prefixes.rst b/en/cookbook/sql-table-prefixes.rst similarity index 98% rename from cookbook/en/sql-table-prefixes.rst rename to en/cookbook/sql-table-prefixes.rst index 8f3ee3809..7e1579286 100644 --- a/cookbook/en/sql-table-prefixes.rst +++ b/en/cookbook/sql-table-prefixes.rst @@ -20,7 +20,7 @@ DoctrineExtensions namespace. You create this file in your library/DoctrineExtensions directory, but will need to set up appropriate autoloaders. -:: +.. code-block:: php + Doctrine 2 does not ship with any internal validators, the reason being that we think all the frameworks out there already ship with quite decent ones that can be integrated into your Domain easily. @@ -26,7 +28,7 @@ Say we have an ``Order`` with several ``OrderLine`` instances. We never want to allow any customer to order for a larger sum than he is allowed to: -:: +.. code-block:: php @@ -95,7 +96,7 @@ Of course you can do any type of primitive checks, not null, email-validation, string size, integer and date ranges in your validation callbacks. -:: +.. code-block:: php `_ - - +Further readings: :doc:`Lifecycle Events <../reference/events>` diff --git a/en/index.rst b/en/index.rst new file mode 100644 index 000000000..877bdc7c3 --- /dev/null +++ b/en/index.rst @@ -0,0 +1,70 @@ +Welcome to Doctrine 2 ORM's documentation! +========================================== + +Reference Guide +--------------- + +.. toctree:: + :maxdepth: 1 + :numbered: + + reference/introduction + reference/architecture + reference/configuration + reference/basic-mapping + reference/association-mapping + reference/inheritance-mapping + reference/working-with-objects + reference/working-with-associations + reference/transactions-and-concurrency + reference/events + reference/batch-processing + reference/dql-doctrine-query-language + reference/query-builder + reference/native-sql + reference/change-tracking-policies + reference/partial-objects + reference/xml-mapping + reference/yaml-mapping + reference/annotations-reference + reference/php-mapping + reference/caching + reference/improving-performance + reference/best-practices + reference/tools + reference/metadata-drivers + reference/best-practices + reference/limitations-and-known-issues + +Tutorials +--------- + +.. toctree:: + :maxdepth: 1 + + tutorials/getting-started-xml-edition + +Cookbook +-------- + +.. toctree:: + :maxdepth: 1 + + cookbook/aggregate-fields + cookbook/dql-custom-walkers + cookbook/dql-user-defined-functions + cookbook/implementing-arrayaccess-for-domain-objects + cookbook/implementing-the-notify-changetracking-policy + cookbook/implementing-wakeup-or-clone + cookbook/integrating-with-codeigniter + cookbook/sql-table-prefixes + cookbook/strategy-cookbook-introduction + cookbook/validation-of-entities + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/manual/en/make.bat b/en/make.bat similarity index 100% rename from manual/en/make.bat rename to en/make.bat diff --git a/manual/en/annotations-reference.rst b/en/reference/annotations-reference.rst similarity index 82% rename from manual/en/annotations-reference.rst rename to en/reference/annotations-reference.rst index 3b5cefddd..8795ed1a6 100644 --- a/manual/en/annotations-reference.rst +++ b/en/reference/annotations-reference.rst @@ -8,39 +8,41 @@ Index ----- -- `@Column <#ann_column>`_ -- `@ChangeTrackingPolicy <#ann_changetrackingpolicy>`_ -- `@DiscriminatorColumn <#ann_discriminatorcolumn>`_ -- `@DiscriminatorMap <#ann_discriminatormap>`_ -- `@Entity <#ann_entity>`_ -- `@GeneratedValue <#ann_generatedvalue>`_ -- `@HasLifecycleCallbacks <#ann_haslifecyclecallbacks>`_ -- `@Index <#ann_indexes>`_ -- `@Id <#ann_id>`_ -- `@InheritanceType <#ann_inheritancetype>`_ -- `@JoinColumn <#ann_joincolumn>`_ -- `@JoinTable <#ann_jointable>`_ -- `@ManyToOne <#ann_manytoone>`_ -- `@ManyToMany <#ann_manytomany>`_ -- `@MappedSuperclass <#ann_mappedsuperclass>`_ -- `@OneToOne <#ann_onetoone>`_ -- `@OneToMany <#ann_onetomany>`_ -- `@OrderBy <#ann_orderby>`_ -- `@PostLoad <#ann_postload>`_ -- `@PostPersist <#ann_postpersist>`_ -- `@PostRemove <#ann_postremove>`_ -- `@PostUpdate <#ann_postupdate>`_ -- `@PrePersist <#ann_prepersist>`_ -- `@PreRemove <#ann_preremove>`_ -- `@PreUpdate <#ann_preupdate>`_ -- `@SequenceGenerator <#ann_sequencegenerator>`_ -- `@Table <#ann_table>`_ -- `@UniqueConstraint <#ann_uniqueconstraint>`_ -- `@Version <#ann_version>`_ +- :ref:`@Column ` +- :ref:`@ChangeTrackingPolicy ` +- :ref:`@DiscriminatorColumn ` +- :ref:`@DiscriminatorMap ` +- :ref:`@Entity ` +- :ref:`@GeneratedValue ` +- :ref:`@HasLifecycleCallbacks ` +- :ref:`@Index ` +- :ref:`@Id ` +- :ref:`@InheritanceType ` +- :ref:`@JoinColumn ` +- :ref:`@JoinTable ` +- :ref:`@ManyToOne ` +- :ref:`@ManyToMany ` +- :ref:`@MappedSuperclass ` +- :ref:`@OneToOne ` +- :ref:`@OneToMany ` +- :ref:`@OrderBy ` +- :ref:`@PostLoad ` +- :ref:`@PostPersist ` +- :ref:`@PostRemove ` +- :ref:`@PostUpdate ` +- :ref:`@PrePersist ` +- :ref:`@PreRemove ` +- :ref:`@PreUpdate ` +- :ref:`@SequenceGenerator ` +- :ref:`@Table ` +- :ref:`@UniqueConstraint ` +- :ref:`@Version ` Reference --------- +.. _annref_column: + @Column ~~~~~~~ @@ -79,11 +81,11 @@ Optional attributes: attribute still handles the conversion between PHP and Database values. If you use this attribute on a column that is used for joins between tables you should also take a look at - `@JoinColumn <#ann_joincolumn>`_. + :ref:`@JoinColumn `. Examples: -:: +.. code-block:: php `_ +The :doc:`details on all the available change tracking policies ` can be found in the configuration section. Example: -:: +.. code-block:: php `_. This +instance variable which is annotated by :ref:`@Id `. This annotation is optional and only has meaning when used in conjunction with @Id. @@ -220,7 +231,7 @@ Required attributes: Example: -:: +.. code-block:: php `_ annotation on +Annotation is used inside the :ref:`@Table ` annotation on the entity-class level. It allows to hint the SchemaTool to generate a database index on the specified table columns. It only has meaning in the SchemaTool schema generation context. @@ -273,7 +288,7 @@ Required attributes: Example: -:: +.. code-block:: php `_ and -`@DiscriminatorColumn <#ann_discriminatorcolumn>`_ annotations. +:ref:`@DiscriminatorMap ` and +:ref:`@DiscriminatorColumn ` annotations. Examples: -:: +.. code-block:: php `_, `@OneToOne <#ann_onetoone>`_ fields -and in the Context of `@JoinTable <#ann_jointable>`_ nested inside +:ref:`@ManyToOne `, :ref:`@OneToOne ` fields +and in the Context of :ref:`@JoinTable ` nested inside a @ManyToMany. This annotation is not required. If its not specified the attributes *name* and *referencedColumnName* are inferred from the table and primary key names. @@ -378,13 +399,13 @@ Optional attributes: this attribute on @JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a - "columnDefinition" attribute on `@Column <#ann_column>`_ also sets + "columnDefinition" attribute on :ref:`@Column ` also sets the related @JoinColumn's columnDefinition. This is necessary to make foreign keys work. Example: -:: +.. code-block:: php `_ or `@OneToOne <#ann_onetoone>`_ +:ref:`@ManyToOne ` or :ref:`@OneToOne ` relation with an entity that has multiple identifiers. +.. _annref_jointable: + @JoinTable ~~~~~~~~~~~~~~ -Using `@OneToMany <#ann_onetomany>`_ or -`@ManyToMany <#ann_manytomany>`_ on the owning side of the relation +Using :ref:`@OneToMany ` or +:ref:`@ManyToMany ` on the owning side of the relation requires to specify the @JoinTable annotation which describes the details of the database join table. If you do not specify @JoinTable on these relations reasonable mapping defaults apply @@ -427,7 +452,7 @@ Optional attributes: Example: -:: +.. code-block:: php `_ is an +between two entities. :ref:`@JoinTable ` is an additional, optional annotation that has reasonable default configuration values using the table and names of the two related entities. @@ -505,7 +534,7 @@ Optional attributes: Example: -:: +.. code-block:: php `_. +:doc:`more details on the restrictions of mapped superclasses `. + +.. _annref_onetoone: @OneToOne ~~~~~~~~~~~~~~ The @OneToOne annotation works almost exactly as the -`@ManyToOne <#ann_manytoone>`_ with one additional option that can +:ref:`@ManyToOne ` with one additional option that can be specified. The configuration defaults for -`@JoinColumn <#ann_joincolumn>`_ using the target entity table and +:ref:`@JoinColumn ` using the target entity table and primary key column names apply here too. Required attributes: @@ -567,7 +600,7 @@ Optional attributes: Example: -:: +.. code-block:: php `_ or `@OneToMany <#ann_onetomany>`_ +:ref:`@ManyToMany ` or :ref:`@OneToMany ` annotation to specify by which criteria the collection should be retrieved from the database by using an ORDER BY clause. @@ -620,7 +657,7 @@ snippet: Example: -:: +.. code-block:: php `_ annotation on +Annotation is used inside the :ref:`@Table ` annotation on the entity-class level. It allows to hint the SchemaTool to generate a database unique constraint on the specified table columns. It only has meaning in the SchemaTool schema generation @@ -769,7 +826,7 @@ Required attributes: Example: -:: +.. code-block:: php `_ annotations that have the type integer or -datetime. +:ref:`@Column ` annotations that have the type integer or +datetime. Combining @Version with :ref:`@Id ` is not supported. Example: -:: +.. code-block:: php find('Group', $groupId); @@ -266,13 +268,13 @@ associations are correctly defined. You can either use the Doctrine Command Line Tool: -:: +.. code-block:: php doctrine orm:validate-schema Or you can trigger the validation manually: -:: +.. code-block:: php use Doctrine\ORM\Tools\SchemaValidator; @@ -304,7 +306,7 @@ example of a ``Product`` that has one ``Shipping`` object associated to it. The ``Shipping`` side does not reference back to the ``Product`` so it is unidirectional. -:: +.. code-block:: php 10 However the following: diff --git a/manual/en/basic-mapping.rst b/en/reference/basic-mapping.rst similarity index 96% rename from manual/en/basic-mapping.rst rename to en/reference/basic-mapping.rst index 506337092..95a1d8c4d 100644 --- a/manual/en/basic-mapping.rst +++ b/en/reference/basic-mapping.rst @@ -23,7 +23,8 @@ chapters for XML and YAML mapping, respectively. .. note:: If you're wondering which mapping driver gives the best - performance, the answer is: None. Once the metadata of a class has + performance, the answer is: They all give exactly the same performance. + Once the metadata of a class has been read from the source (annotations, xml or yaml) it is stored in an instance of the ``Doctrine\ORM\Mapping\ClassMetadata`` class and these instances are stored in the metadata cache. Therefore at @@ -68,7 +69,7 @@ In order to mark a class for object-relational persistence it needs to be designated as an entity. This can be done through the ``@Entity`` marker annotation. -:: +.. code-block:: php getConnection(); @@ -327,7 +324,7 @@ Every entity class needs an identifier/primary key. You designate the field that serves as the identifier with the ``@Id`` marker annotation. Here is an example: -:: +.. code-block:: php createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9'); @@ -69,7 +69,7 @@ by step instead of loading the whole result into memory at once. The following example shows how to do this, combining the iteration with the batching strategy that was already used for bulk inserts: -:: +.. code-block:: php createQuery('delete from MyProject\Model\Manager m where m.salary > 100000'); @@ -121,7 +123,7 @@ An alternative solution for bulk deletes is to use the by step instead of loading the whole result into memory at once. The following example shows how to do this: -:: +.. code-block:: php iterate()`` implements the Iterator interface so you can process a large result without memory problems using the following approach: -:: +.. code-block:: php _em->createQuery('select u from MyProject\Model\User u'); @@ -163,7 +167,9 @@ problems using the following approach: $this->_em->detach($row[0]); } - **NOTE** Iterating results is not possible with queries that +.. note:: + + Iterating results is not possible with queries that fetch-join a collection-valued association. The nature of such SQL result sets is not suitable for incremental hydration. diff --git a/manual/en/best-practices.rst b/en/reference/best-practices.rst similarity index 99% rename from manual/en/best-practices.rst rename to en/reference/best-practices.rst index 28709666f..f58291d18 100644 --- a/manual/en/best-practices.rst +++ b/en/reference/best-practices.rst @@ -85,7 +85,7 @@ Initialize collections in the constructor It is recommended best practice to initialize any business collections in entities in the constructor. Example: -:: +.. code-block:: php `_ on the PHP website. It will give +`in the PHP Documentation `_. It will give you a little background information about what it is and how you can use it as well as how to install it. Below is a simple example of how you could use the APC cache driver by itself. -:: +.. code-block:: php `_ on the PHP website. It will +` on the PHP website `_. It will give you a little background information about what it is and how you can use it as well as how to install it. Below is a simple example of how you could use the Memcache cache driver by itself. -:: +.. code-block:: php save('cache_id', 'my_data'); @@ -136,7 +136,7 @@ below. You can save any type of data whether it be a string, array, object, etc. -:: +.. code-block:: php contains('cache_id')) { @@ -168,7 +168,7 @@ Now if you want to retrieve some cache entry you can use the ``fetch()`` method. It also accepts a single argument just like ``contains()`` which is the ID of the cache entry. -:: +.. code-block:: php fetch('my_array'); @@ -184,7 +184,7 @@ you can delete all entries. By Cache ID ^^^^^^^^^^^ -:: +.. code-block:: php delete('my_array'); @@ -192,7 +192,7 @@ By Cache ID You can also pass wild cards to the ``delete()`` method and it will return an array of IDs that were matched and deleted. -:: +.. code-block:: php delete('users_*'); @@ -203,7 +203,7 @@ By Regular Expression If you need a little more control than wild cards you can use a PHP regular expression to delete cache entries. -:: +.. code-block:: php deleteByRegex('/users_.*/'); @@ -216,7 +216,7 @@ a prefix or suffix is sufficient, it is recommended that you do that instead of using a regular expression because it will be much faster if you have many cache entries. -:: +.. code-block:: php deleteByPrefix('users_'); @@ -227,7 +227,7 @@ By Suffix Just like we did above with the prefix you can do the same with a suffix. -:: +.. code-block:: php deleteBySuffix('_my_account'); @@ -238,7 +238,7 @@ All If you simply want to delete all cache entries you can do so with the ``deleteAll()`` method. -:: +.. code-block:: php deleteAll(); @@ -249,12 +249,14 @@ Counting If you want to count how many entries are stored in the cache driver instance you can use the ``count()`` method. -:: +.. code-block:: php count(); - **NOTE** In order to use ``deleteByRegex()``, ``deleteByPrefix()``, +.. note:: + + In order to use ``deleteByRegex()``, ``deleteByPrefix()``, ``deleteBySuffix()``, ``deleteAll()``, ``count()`` or ``getIds()`` you must enable an option for the cache driver to manage your cache IDs internally. This is necessary because APC, Memcache, etc. don't @@ -279,7 +281,7 @@ naming collisions. This can be worked around by using namespaces. You can set the namespace a cache driver should use by using the ``setNamespace()`` method. -:: +.. code-block:: php setNamespace('my_namespace_'); @@ -303,7 +305,7 @@ change unless you alter the DQL query. This can be done by configuring the query cache implementation to use on your ORM configuration. -:: +.. code-block:: php setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache()); @@ -325,7 +327,7 @@ cache implementation. Now when you're executing DQL queries you can configure them to use the result cache. -:: +.. code-block:: php createQuery('select u from \Entities\User u'); @@ -334,12 +336,14 @@ the result cache. You can also configure an individual query to use a different result cache driver. -:: +.. code-block:: php setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache()); - **NOTE** Setting the result cache driver on the query will +.. note:: + + Setting the result cache driver on the query will automatically enable the result cache for the query. If you want to disable it pass false to ``useResultCache()``. @@ -352,7 +356,7 @@ result cache driver. If you want to set the time the cache has to live you can use the ``setResultCacheLifetime()`` method. -:: +.. code-block:: php setResultCacheLifetime(3600); @@ -361,7 +365,7 @@ The ID used to store the result set cache is a hash which is automatically generated for you if you don't set a custom ID yourself with the ``setResultCacheId()`` method. -:: +.. code-block:: php setResultCacheId('my_custom_id'); @@ -369,7 +373,7 @@ yourself with the ``setResultCacheId()`` method. You can also set the lifetime and cache ID by passing the values as the second and third argument to ``useResultCache()``. -:: +.. code-block:: php useResultCache(true, 3600, 'my_custom_id'); @@ -384,7 +388,7 @@ each request we should cache it using one of the cache drivers. Just like the query and result cache we need to configure it first. -:: +.. code-block:: php setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); @@ -402,7 +406,7 @@ clearing the query, result and metadata cache. From the Doctrine command line you can run the following command. -:: +.. code-block:: php $ ./doctrine clear-cache @@ -412,19 +416,19 @@ what you clear you can use the following options. To clear the query cache use the ``--query`` option. -:: +.. code-block:: php $ ./doctrine clear-cache --query To clear the metadata cache use the ``--metadata`` option. -:: +.. code-block:: php $ ./doctrine clear-cache --metadata To clear the result cache use the ``--result`` option. -:: +.. code-block:: php $ ./doctrine clear-cache --result @@ -435,29 +439,31 @@ clear. Just like the API of the cache drivers you can clear based on an ID, regular expression, prefix or suffix. -:: +.. code-block:: php $ ./doctrine clear-cache --result --id=cache_id Or if you want to clear based on a regular expressions. -:: +.. code-block:: php $ ./doctrine clear-cache --result --regex=users_.* Or with a prefix. -:: +.. code-block:: php $ ./doctrine clear-cache --result --prefix=users_ And finally with a suffix. -:: +.. code-block:: php $ ./doctrine clear-cache --result --suffix=_my_account - **NOTE** Using the ``--id``, ``--regex``, etc. options with the +.. note:: + + Using the ``--id``, ``--regex``, etc. options with the ``--query`` and ``--metadata`` are not allowed as it is not necessary to be specific about what you clear. You only ever need to completely clear the cache to remove stale entries. @@ -477,6 +483,6 @@ Ways exist to work around this, like pre-populating your cache and not letting your users requests populate the cache. You can read more about cache slams -`here `_. +`in this blog post `_. diff --git a/manual/en/change-tracking-policies.rst b/en/reference/change-tracking-policies.rst similarity index 98% rename from manual/en/change-tracking-policies.rst rename to en/reference/change-tracking-policies.rst index 25f32d051..7bd884910 100644 --- a/manual/en/change-tracking-policies.rst +++ b/en/reference/change-tracking-policies.rst @@ -46,7 +46,7 @@ them to EntityManager#persist(). This policy can be configured as follows: -:: +.. code-block:: php setProxyDir($dir); @@ -181,7 +185,7 @@ down. Proxy Namespace (***REQUIRED***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php setProxyNamespace($namespace); @@ -194,7 +198,7 @@ Doctrine, refer to the "Proxy Objects" section further down. Metadata Driver (***REQUIRED***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php setMetadataDriverImpl($driver); @@ -220,7 +224,7 @@ or YamlDriver please refer to the dedicated chapters The annotation driver can be configured with a factory method on the ``Doctrine\ORM\Configuration``: -:: +.. code-block:: php newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); @@ -236,7 +240,7 @@ directories of Entities. Metadata Cache (***RECOMMENDED***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php setMetadataCacheImpl($cache); @@ -265,7 +269,7 @@ per-request basis. Query Cache (***RECOMMENDED***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php setQueryCacheImpl($cache); @@ -295,7 +299,7 @@ per-request basis. SQL Logger (***Optional***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php setSQLLogger($logger); @@ -311,7 +315,7 @@ implementation that logs to the standard output using ``echo`` and Auto-generating Proxy Classes (***OPTIONAL***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php setAutoGenerateProxyClasses($bool); @@ -385,7 +389,7 @@ useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier. You could simply do this: -:: +.. code-block:: php setAutoGenerateProxyClasses($bool); @@ -444,7 +448,7 @@ added to the entity class that are not yet in the proxy class. In such a case, simply use the Doctrine Console to (re)generate the proxy classes like so: -:: +.. code-block:: php $ ./doctrine orm:generate-proxies @@ -456,7 +460,7 @@ with them using two different metadata drivers, for example XML and YAML. You can use the DriverChain Metadata implementations to aggregate these drivers based on namespaces: -:: +.. code-block:: php 20: -:: +.. code-block:: php createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20'); @@ -115,7 +116,7 @@ Example: Regular join of the address: -:: +.. code-block:: php createQuery("SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); @@ -123,7 +124,7 @@ Regular join of the address: Fetch join of the address: -:: +.. code-block:: php createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); @@ -165,7 +166,7 @@ on the hydration mode. Hydrate all User entities: -:: +.. code-block:: php createQuery('SELECT u FROM MyProject\Model\User u'); @@ -173,7 +174,7 @@ Hydrate all User entities: Retrieve the IDs of all CmsUsers: -:: +.. code-block:: php createQuery('SELECT u.id FROM CmsUser u'); @@ -181,7 +182,7 @@ Retrieve the IDs of all CmsUsers: Retrieve the IDs of all users that have written an article: -:: +.. code-block:: php createQuery('SELECT DISTINCT u.id FROM CmsArticle a JOIN a.user u'); @@ -190,7 +191,7 @@ Retrieve the IDs of all users that have written an article: Retrieve all articles and sort them by the name of the articles users instance: -:: +.. code-block:: php createQuery('SELECT a FROM CmsArticle a JOIN a.user u ORDER BY u.name ASC'); @@ -198,7 +199,7 @@ users instance: Retrieve the Username and Name of a CmsUser: -:: +.. code-block:: php createQuery('SELECT u.username, u.name FROM CmsUser u'); @@ -207,7 +208,7 @@ Retrieve the Username and Name of a CmsUser: Retrieve a ForumUser and his single associated entity: -:: +.. code-block:: php createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a'); @@ -216,7 +217,7 @@ Retrieve a ForumUser and his single associated entity: Retrieve a CmsUser and fetch join all the phonenumbers he has: -:: +.. code-block:: php createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p'); @@ -225,7 +226,7 @@ Retrieve a CmsUser and fetch join all the phonenumbers he has: Hydrate a result in Ascending: -:: +.. code-block:: php createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC'); @@ -233,7 +234,7 @@ Hydrate a result in Ascending: Or in Descending Order: -:: +.. code-block:: php createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC'); @@ -241,7 +242,7 @@ Or in Descending Order: Using Aggregate Functions: -:: +.. code-block:: php createQuery('SELECT COUNT(u.id) FROM Entities\User u'); @@ -249,7 +250,7 @@ Using Aggregate Functions: With WHERE Clause and Positional Parameter: -:: +.. code-block:: php createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1'); @@ -257,7 +258,7 @@ With WHERE Clause and Positional Parameter: With WHERE Clause and Named Parameter: -:: +.. code-block:: php createQuery('SELECT u FROM ForumUser u WHERE u.username = :name'); @@ -265,7 +266,7 @@ With WHERE Clause and Named Parameter: With Nested Conditions in WHERE Clause: -:: +.. code-block:: php createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id'); @@ -273,7 +274,7 @@ With Nested Conditions in WHERE Clause: With COUNT DISTINCT: -:: +.. code-block:: php createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser'); @@ -281,7 +282,7 @@ With COUNT DISTINCT: With Arithmetic Expression in WHERE clause: -:: +.. code-block:: php createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); @@ -290,7 +291,7 @@ With Arithmetic Expression in WHERE clause: Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids: -:: +.. code-block:: php createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a'); @@ -298,7 +299,7 @@ article-ids: Restricting a JOIN clause by additional conditions: -:: +.. code-block:: php createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%'"); @@ -306,7 +307,7 @@ Restricting a JOIN clause by additional conditions: Using several Fetch JOINs: -:: +.. code-block:: php createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c'); @@ -314,7 +315,7 @@ Using several Fetch JOINs: BETWEEN in WHERE clause: -:: +.. code-block:: php createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2'); @@ -322,7 +323,7 @@ BETWEEN in WHERE clause: DQL Functions in WHERE clause: -:: +.. code-block:: php createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'"); @@ -330,7 +331,7 @@ DQL Functions in WHERE clause: IN() Expression: -:: +.. code-block:: php createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)'); @@ -344,7 +345,7 @@ IN() Expression: CONCAT() DQL Function: -:: +.. code-block:: php createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); @@ -355,7 +356,7 @@ CONCAT() DQL Function: EXISTS in WHERE clause with correlated Subquery -:: +.. code-block:: php createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.user = u.id)'); @@ -363,7 +364,7 @@ EXISTS in WHERE clause with correlated Subquery Get all users who are members of $group. -:: +.. code-block:: php createQuery('SELECT u.id FROM CmsUser u WHERE :groupId MEMBER OF u.groups'); @@ -372,7 +373,7 @@ Get all users who are members of $group. Get all users that have more than 1 phonenumber -:: +.. code-block:: php createQuery('SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1'); @@ -380,7 +381,7 @@ Get all users that have more than 1 phonenumber Get all users that have no phonenumber -:: +.. code-block:: php createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); @@ -389,7 +390,7 @@ Get all users that have no phonenumber Get all instances of a specific type, for use with inheritance hierarchies: -:: +.. code-block:: php createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee'); @@ -408,7 +409,7 @@ and joining some data. If you want to select partial objects you can use the ``partial`` DQL keyword: -:: +.. code-block:: php createQuery('SELECT partial u.{id, username} FROM CmsUser u'); @@ -416,7 +417,7 @@ DQL keyword: You use the partial syntax when joining as well: -:: +.. code-block:: php createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a'); @@ -441,7 +442,7 @@ with primary or unique fields though: Returns an array of the following kind, indexed by both user-id then phonenumber-id: -:: +.. code-block:: php array 0 => @@ -543,7 +544,7 @@ Arithmetic operators You can do math in DQL using numeric values, for example: -.. warning:: +.. code-block:: sql SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 @@ -582,7 +583,7 @@ parser with own specialized platform functions. You can register custom DQL functions in your ORM Configuration: -:: +.. code-block:: php Object @@ -873,7 +876,7 @@ A pure result usually looks like this: A mixed result on the other hand has the following general structure: -:: +.. code-block:: php array array @@ -900,7 +903,7 @@ clause, we get a mixed result. Here is how the result could look like: -:: +.. code-block:: php array array @@ -913,7 +916,7 @@ Here is how the result could look like: And here is how you would access it in PHP code: -:: +.. code-block:: php createQuery('SELECT u FROM CmsUser u'); @@ -956,7 +959,7 @@ Array Hydration You can run the same query with array hydration and the result set is hydrated into an array that represents the object graph: -:: +.. code-block:: php createQuery('SELECT u FROM CmsUser u'); @@ -964,7 +967,7 @@ is hydrated into an array that represents the object graph: You can use the ``getArrayResult()`` shortcut as well: -:: +.. code-block:: php getArrayResult(); @@ -975,7 +978,7 @@ Scalar Hydration If you want to return a flat rectangular result set instead of an object graph you can use scalar hydration: -:: +.. code-block:: php createQuery('SELECT u FROM CmsUser u'); @@ -996,7 +999,7 @@ Single Scalar Hydration If you a query which returns just a single scalar value you can use single scalar hydration: -:: +.. code-block:: php createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); @@ -1005,7 +1008,7 @@ single scalar hydration: You can use the ``getSingleScalarResult()`` shortcut as well: -:: +.. code-block:: php getSingleScalarResult(); @@ -1016,7 +1019,7 @@ Custom Hydration Modes You can easily add your own custom hydration modes by first creating a class which extends ``AbstractHydrator``: -:: +.. code-block:: php getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); Now the hydrator is ready to be used in your queries: -:: +.. code-block:: php createQuery('SELECT u FROM CmsUser u'); @@ -1087,7 +1090,7 @@ cached at all. You have to enable the result cache on a per query basis. The following example shows a complete workflow using the Result Cache API: -:: +.. code-block:: php createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); @@ -1229,14 +1232,14 @@ Terminals Query Language ~~~~~~~~~~~~~~ -:: +.. code-block:: php QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement Statements ~~~~~~~~~~ -:: +.. code-block:: php SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] UpdateStatement ::= UpdateClause [WhereClause] @@ -1245,7 +1248,7 @@ Statements Identifiers ~~~~~~~~~~~ -:: +.. code-block:: php /* Alias Identification usage (the "u" of "u.name") */ IdentificationVariable ::= identifier @@ -1282,7 +1285,7 @@ Identifiers Path Expressions ~~~~~~~~~~~~~~~~ -:: +.. code-block:: php /* "u.Group" or "u.Phonenumbers" declarations */ JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) @@ -1311,7 +1314,7 @@ Path Expressions Clauses ~~~~~~~ -:: +.. code-block:: php SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression @@ -1328,7 +1331,7 @@ Clauses Items ~~~~~ -:: +.. code-block:: php UpdateItem ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] @@ -1338,7 +1341,7 @@ Items From, Join and Index by ~~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) @@ -1351,7 +1354,7 @@ From, Join and Index by Select Expressions ~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php SelectExpression ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")" | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable] SimpleSelectExpression ::= ScalarExpression | IdentificationVariable | @@ -1362,7 +1365,7 @@ Select Expressions Conditional Expressions ~~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* @@ -1375,7 +1378,7 @@ Conditional Expressions Collection Expressions ~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression @@ -1383,7 +1386,7 @@ Collection Expressions Literal Values ~~~~~~~~~~~~~~ -:: +.. code-block:: php Literal ::= string | char | integer | float | boolean InParameter ::= Literal | InputParameter @@ -1391,7 +1394,7 @@ Literal Values Input Parameter ~~~~~~~~~~~~~~~ -:: +.. code-block:: php InputParameter ::= PositionalParameter | NamedParameter PositionalParameter ::= "?" integer @@ -1400,7 +1403,7 @@ Input Parameter Arithmetic Expressions ~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* @@ -1413,7 +1416,7 @@ Arithmetic Expressions Scalar and Type Expressions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression BooleanPrimary | CaseExpression | EntityTypeExpression @@ -1440,7 +1443,7 @@ Scalar and Type Expressions Aggregate Expressions ~~~~~~~~~~~~~~~~~~~~~ -:: +.. code-block:: php AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" @@ -1450,7 +1453,7 @@ Other Expressions QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS -:: +.. code-block:: php QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression @@ -1464,7 +1467,7 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS Functions ~~~~~~~~~ -:: +.. code-block:: php FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime diff --git a/manual/en/events.rst b/en/reference/events.rst similarity index 98% rename from manual/en/events.rst rename to en/reference/events.rst index ebdeee81b..836980d41 100644 --- a/manual/en/events.rst +++ b/en/reference/events.rst @@ -12,7 +12,7 @@ central point of Doctrine's event listener system. Listeners are registered on the manager and events are dispatched through the manager. -:: +.. code-block:: php dispatchEvent(EventTest::preFoo); @@ -63,7 +63,7 @@ Events can be dispatched by using the ``dispatchEvent()`` method. You can easily remove a listener with the ``removeEventListener()`` method. -:: +.. code-block:: php removeEventListener(array(self::preFoo, self::postFoo), $this); @@ -74,7 +74,7 @@ which implements the ``\Doctrine\Common\EventSubscriber`` interface and implements a ``getSubscribedEvents()`` method which returns an array of events it should be subscribed to. -:: +.. code-block:: php dispatchEvent(TestEvent::preFoo); @@ -106,7 +106,7 @@ notified for that event. Now you can test the ``$eventSubscriber`` instance to see if the ``preFoo()`` method was invoked. -:: +.. code-block:: php preFooInvoked) { @@ -178,7 +178,7 @@ the life-time of their registered entities. You can access the Event constants from the ``Events`` class in the ORM package. -:: +.. code-block:: php getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener()); @@ -436,7 +434,7 @@ To make use of the onFlush event you have to be familiar with the internal UnitOfWork API, which grants you access to the previously mentioned sets. See this example: -:: +.. code-block:: php If you want to use the Doctrine Database Abstraction Layer you can install it with the following command. -:: +.. code-block:: bash $ sudo pear install pear.doctrine-project.org/DoctrineDBAL- Or, if you want to get the works and go for the ORM you can install it with the following command. -:: +.. code-block:: bash $ sudo pear install pear.doctrine-project.org/DoctrineORM- @@ -122,7 +122,7 @@ it with the following command. writing this is ``2.0.0BETA3`` for the ORM, so you could install it like the following: - :: + .. code-block:: bash $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.0BETA3 @@ -130,7 +130,7 @@ it with the following command. When you have a package installed via PEAR you can require and load the ``ClassLoader`` with the following code. -:: +.. code-block:: php @@ -394,11 +394,8 @@ Can you **find** the easier way?). 6) Explore Doctrine 2! -See the following links if you want to start with more complex -tutorials rather than reading the manual: - - -- Doctrine2 Cookbook: - `Getting Started XML Edition `_ +Instead of reading through the reference manual we also recommend to look at the tutorials: + +:doc:`Getting Started XML Edition <../tutorials/getting-started-xml-edition>` diff --git a/manual/en/limitations-and-known-issues.rst b/en/reference/limitations-and-known-issues.rst similarity index 99% rename from manual/en/limitations-and-known-issues.rst rename to en/reference/limitations-and-known-issues.rst index 8631c2d7d..dbf5cd7e5 100644 --- a/manual/en/limitations-and-known-issues.rst +++ b/en/reference/limitations-and-known-issues.rst @@ -60,7 +60,7 @@ the fundamental difference between the two different ``product_attributes`` tables you should see how they translate into a Doctrine Mapping (Using Annotations): -:: +.. code-block:: php getConfiguration()->setMetadataCacheImpl(new ApcCache()); @@ -40,7 +42,7 @@ If you want to use one of the included core metadata drivers you just need to configure it. All the drivers are in the ``Doctrine\ORM\Mapping\Driver`` namespace: -:: +.. code-block:: php getMetadataFactory(); @@ -180,7 +184,7 @@ Now you can learn about the entity and use the data stored in the ``ClassMetadata`` instance to get all mapped fields for example and iterate over them: -:: +.. code-block:: php fieldMappings as $fieldMapping) { diff --git a/manual/en/native-sql.rst b/en/reference/native-sql.rst similarity index 98% rename from manual/en/native-sql.rst rename to en/reference/native-sql.rst index 992eefc06..77bd0ad78 100644 --- a/manual/en/native-sql.rst +++ b/en/reference/native-sql.rst @@ -67,7 +67,7 @@ element in the transformed result. You add an entity result through ``ResultSetMapping#addEntityResult()``. Let's take a look at the method signature in detail: -:: +.. code-block:: php User (Object) @@ -269,7 +269,7 @@ assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress, where the User is the owning side and thus owns the foreign key. -:: +.. code-block:: php createQuery("select partial u.{id,name} from MyApp\Domain\User u"); diff --git a/manual/en/php-mapping.rst b/en/reference/php-mapping.rst similarity index 95% rename from manual/en/php-mapping.rst rename to en/reference/php-mapping.rst index 21fb33fec..6ba0755c3 100644 --- a/manual/en/php-mapping.rst +++ b/en/reference/php-mapping.rst @@ -13,7 +13,7 @@ If you wish to write your mapping information inside PHP files that are named after the entity and included to populate the metadata for an entity you can do so by using the ``PHPDriver``: -:: +.. code-block:: php getMetadataFor('Entities\User'); + $class = $em->getClassMetadata('Entities\User'); + // or + $class = $em->getMetadataFactory()->getMetadataFor('Entities\User'); Static Function --------------- @@ -72,7 +74,7 @@ itself. This is useful for cases where you want to keep your entity and mapping information together but don't want to use annotations. For this you just need to use the ``StaticPHPDriver``: -:: +.. code-block:: php expr()->*`` methods. Here is a converted example 8 to suggested standard way to build queries: -:: +.. code-block:: php addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand()); @@ -119,7 +119,7 @@ do: Additionally, include multiple commands (and overriding previously defined ones) is possible through the command: -:: +.. code-block:: php addCommands(array( @@ -200,7 +200,7 @@ the ``createSchema()`` method. First create an instance of the that you want to use to create the schema. This method receives an array of ``ClassMetadataInfo`` instances. -:: +.. code-block:: php dropSchema($classes); @@ -222,7 +222,7 @@ model. When you are changing your metadata a lot during development you might want to drop the complete database instead of only the tables of the current model to clean up with orphaned tables. -:: +.. code-block:: php dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE); @@ -232,7 +232,7 @@ easily with the ``updateSchema()`` method. It will compare your existing database schema to the passed array of ``ClassMetdataInfo`` instances. -:: +.. code-block:: php updateSchema($classes); @@ -242,20 +242,20 @@ use the ``schema-tool`` command. To create the schema use the ``create`` command: -:: +.. code-block:: php $ php doctrine orm:schema-tool:create To drop the schema use the ``drop`` command: -:: +.. code-block:: php $ php doctrine orm:schema-tool:drop If you want to drop and then recreate the schema then use both options: -:: +.. code-block:: php $ php doctrine orm:schema-tool:drop $ php doctrine orm:schema-tool:create @@ -263,14 +263,14 @@ options: As you would think, if you want to update your schema use the ``update`` command: -:: +.. code-block:: php $ php doctrine orm:schema-tool:update All of the above commands also accept a ``--dump-sql`` option that will output the SQL for the ran operation. -:: +.. code-block:: php $ php doctrine orm:schema-tool:create --dump-sql @@ -294,7 +294,7 @@ To convert some mapping information between the various supported formats you can use the ``ClassMetadataExporter`` to get exporter instances for the different formats: -:: +.. code-block:: php getExporter('yml', '/path/to/export/yml'); Now you can export some ``ClassMetadata`` instances: -:: +.. code-block:: php getConfiguration()->setMetadataDriverImpl( @@ -353,7 +353,7 @@ First you need to retrieve the metadata instances with the Now you can get an exporter instance and export the loaded metadata to yml: -:: +.. code-block:: php getExporter('yml', '/path/to/export/yml'); @@ -363,11 +363,11 @@ to yml: You can also reverse engineer a database using the ``orm:convert-mapping`` command: -:: +.. code-block:: php $ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml -.. warning:: +.. note:: Reverse Engineering is not always working perfectly depending on special cases. It will only detect Many-To-One diff --git a/manual/en/transactions-and-concurrency.rst b/en/reference/transactions-and-concurrency.rst similarity index 98% rename from manual/en/transactions-and-concurrency.rst rename to en/reference/transactions-and-concurrency.rst index 717783340..56a9c7db7 100644 --- a/manual/en/transactions-and-concurrency.rst +++ b/en/reference/transactions-and-concurrency.rst @@ -33,7 +33,7 @@ The first approach is to use the implicit transaction handling provided by the Doctrine ORM EntityManager. Given the following code snippet, without any explicit transaction demarcation: -:: +.. code-block:: php find('BlogPost', 123456); @@ -301,7 +301,7 @@ See the example code, The form (GET Request): And the change headline action (POST Request): -:: +.. code-block:: php `_. +- The :ref:`concept of owning and inverse sides ` + in bidirectional associations. - If an entity is removed from a collection, the association is removed, not the entity itself. A collection of entities always only represents the association to the containing entities, not the entity itself. -- Collection-valued persistent fields have to be instances of the +- Collection-valued :ref:`persistent fields ` have to be instances of the ``Doctrine\Common\Collections\Collection`` interface. - `See here `_ - for more details. Changes to associations in your code are not synchronized to the database directly, but upon calling ``EntityManager#flush()``. @@ -34,7 +31,7 @@ entities to show examples of association management. See the PHP docblocks of each association in the following example for information about its type and if its the owning or inverse side. -:: +.. code-block:: php find('User', $userId); @@ -176,7 +173,7 @@ The interaction code would then look like in the following snippet In the case of bi-directional associations you have to update the fields on both sides: -:: +.. code-block:: php getFavorites()->add($favoriteComment); @@ -439,7 +436,7 @@ of this chapter. Suppose in our application a user is created whenever he writes his first comment. In this case we would use the following code: -:: +.. code-block:: php find('User', $deleteUserId); @@ -478,7 +475,7 @@ To have Doctrine handle both cases automatically we can change the ``User#commentsAuthored`` property to cascade both the "persist" and the "remove" operation. -:: +.. code-block:: php find('CMS\Article', 1234); @@ -60,7 +60,7 @@ screen. You can even verify that ``$article`` and ``$article2`` are indeed pointing to the same instance by running the following code: -:: +.. code-block:: php find('Article', 1); @@ -157,7 +157,7 @@ A slice of the generated proxy classes code looks like the following piece of code. A real proxy class override ALL public methods along the lines of the ``getName()`` method shown below: -:: +.. code-block:: php persist($user); $em->flush(); -.. warning:: +.. note:: Generated entity identifiers / primary keys are guaranteed to be available after the next successful flush @@ -251,7 +251,7 @@ the ``EntityManager#remove($entity)`` method. By applying the which means that its persistent state will be deleted once ``EntityManager#flush()`` is invoked. -.. warning:: +.. note:: Just like ``persist``, invoking ``remove`` on an entity does NOT cause an immediate SQL DELETE to be issued on the @@ -261,7 +261,7 @@ which means that its persistent state will be deleted once Example: -:: +.. code-block:: php remove($user); @@ -332,7 +332,7 @@ Doctrine will not hold on to any references to a detached entity. Example: -:: +.. code-block:: php detach($entity); @@ -381,7 +381,7 @@ this entity and this copy will subsequently be returned. Example: -:: +.. code-block:: php getUnitOfWork()->size(); @@ -527,7 +536,7 @@ to change tracking (see "Change Tracking Policies") and, of course, memory consumption, so you may want to check it from time to time during development. -.. warning:: +.. note:: Do not invoke ``flush`` after every change to an entity or every single invocation of persist/remove/merge/... This is an @@ -545,12 +554,14 @@ You can get direct access to the Unit of Work by calling ``EntityManager#getUnitOfWork()``. This will return the UnitOfWork instance the EntityManager is currently using. -:: +.. code-block:: php getUnitOfWork(); - **NOTE** Directly manipulating a UnitOfWork is not recommended. +.. note:: + + Directly manipulating a UnitOfWork is not recommended. When working directly with the UnitOfWork API, respect methods marked as INTERNAL by not using them and carefully read the API documentation. @@ -565,7 +576,7 @@ explicitly need to find out what the current state of an entity is in the context of a certain ``EntityManager`` you can ask the underlying ``UnitOfWork``: -:: +.. code-block:: php getUnitOfWork()->getEntityState($entity)) { @@ -610,7 +621,7 @@ identifier / primary key using the ``EntityManager#find($entityName, $id)`` method. Here is an example: -:: +.. code-block:: php `_. +:doc:`the dedicated chapter `. For programmatically building up queries based on conditions that are only known at runtime, Doctrine provides the special ``Doctrine\ORM\QueryBuilder`` class. More information on constructing queries with a QueryBuilder can be found -`in the dedicated chapter `_. +:doc:`in Query Builder chapter `. By Native Queries ~~~~~~~~~~~~~~~~~ @@ -742,7 +753,7 @@ applications that require lots of specialized DQL queries using a custom repository is one recommended way of grouping these queries in a central location. -:: +.. code-block:: php setFileExtension('.xml'); @@ -57,7 +57,7 @@ want to. In order to tell the XmlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: -:: +.. code-block:: php diff --git a/manual/en/yaml-mapping.rst b/en/reference/yaml-mapping.rst similarity index 98% rename from manual/en/yaml-mapping.rst rename to en/reference/yaml-mapping.rst index 9f62eea8d..941ade9ef 100644 --- a/manual/en/yaml-mapping.rst +++ b/en/reference/yaml-mapping.rst @@ -21,7 +21,7 @@ In order to work, this requires certain conventions: - -:: +.. code-block:: php setFileExtension('.yml'); @@ -32,7 +32,7 @@ want to. In order to tell the YamlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: -:: +.. code-block:: php `_. +**clone** nor **wakeup** or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`. An entity contains persistable properties. A persistable property is an instance variable of the entity that contains the data which @@ -62,7 +61,7 @@ A first prototype A first simplified design for this domain model might look like the following set of classes: -:: +.. code-block:: php register(); + // See :doc:`Configuration <../reference/configuration>` for up to date autoloading details. $config = new Doctrine\ORM\Configuration(); // (2) @@ -549,7 +543,7 @@ can use whatever suits you best. The second block contains of the instantiation of the ORM Configuration object. Besides the configuration shown in the next blocks there are several others with are all explained in the -`Configuration section of the manual `_. +:doc:`Configuration section of the manual <../reference/configuration>`. The Proxy Configuration is a required block for your application, you have to specify where Doctrine writes the PHP code for Proxy @@ -598,7 +592,7 @@ For the command-line tool to work a cli-config.php file has to be present in the project root directory, where you will execute the doctrine command. Its a fairly simple file: -:: +.. code-block:: php cd myproject/ doctrine@my-desktop> doctrine orm:schema-tool:create @@ -623,7 +616,7 @@ Doctrine command-line tool: your doctrine command-line client. See the - `Tools section of the manual `_ + :doc:`Tools section of the manual <../reference/tools>` on how to setup the Doctrine console correctly. @@ -631,17 +624,15 @@ During the development you probably need to re-create the database several times when changing the Entity metadata. You can then either re-create the database: -:: +.. code-block:: bash - [console] doctrine@my-desktop> doctrine orm:schema-tool:drop doctrine@my-desktop> doctrine orm:schema-tool:create Or use the update functionality: -:: +.. code-block:: bash - [console] doctrine@my-desktop> doctrine orm:schema-tool:update The updating of databases uses a Diff Algorithm for a given @@ -655,7 +646,7 @@ Writing Entities into the Database Having created the schema we can now start and save entities in the database. For starters we need a create user use-case: -:: +.. code-block:: php find("User", $theReporterId); @@ -758,7 +749,7 @@ mapper for the required view representations. When opening the application, bugs can be paginated through a list-view, which is the first read-only use-case: -:: +.. code-block:: php find("Bug", (int)$theBugId); @@ -881,7 +872,7 @@ write scenarios: However we will soon see another problem with our entities using this approach. Try displaying the engineer's name: -:: +.. code-block:: php description."\n"; @@ -897,7 +888,7 @@ replaced by LazyLoading proxies. Sample code of this proxy generated code can be found in the specified Proxy Directory, it looks like: -:: +.. code-block:: php getDescription()."\n"; @@ -952,7 +943,7 @@ list of all open bugs the user reported or was assigned to. This will be achieved using DQL again, this time with some WHERE clauses and usage of bound parameters: -:: +.. code-block:: php find("Bug", (int)$theBugId); @@ -1024,12 +1015,10 @@ improvement compared to updating all the properties. This tutorial is over here, I hope you had fun. Additional content will be added to this tutorial incrementally, topics will include: -:: - - * Entity Repositories - * More on Association Mappings - * Lifecycle Events triggered in the UnitOfWork - * Ordering of Collections +* Entity Repositories +* More on Association Mappings +* Lifecycle Events triggered in the UnitOfWork +* Ordering of Collections Additional details on all the topics discussed here can be found in the respective manual chapters. diff --git a/generate-docs.sh b/generate-docs.sh index 07aaf048f..33e811f6c 100755 --- a/generate-docs.sh +++ b/generate-docs.sh @@ -1,2 +1,2 @@ #!/bin/bash -sphinx-build manual/en /var/www/docs +sphinx-build en /var/www/docs diff --git a/manual/en.rst b/manual/en.rst deleted file mode 100644 index f66e1b2fd..000000000 --- a/manual/en.rst +++ /dev/null @@ -1,79 +0,0 @@ -Introduction -============ - -Architecture -============ - -Configuration -============= - -Basic Mapping -============= - -Association Mapping -=================== - -Inheritance Mapping -=================== - -Working with objects -==================== - -Working with associations -========================= - -Transactions and Concurrency -============================ - -Events -====== - -Batch processing -================ - -DQL (Doctrine Query Language) -============================= - -Query Builder -============= - -Native SQL -========== - -Change Tracking Policies -======================== - -Partial Objects -=============== - -XML Mapping -=========== - -YAML Mapping -============ - -Annotations Reference -===================== - -PHP Mapping -=========== - -Caching -======= - -Improving Performance -===================== - -Tools -===== - -Metadata Drivers -================ - -Best Practices -============== - -Limitations and Known Issues -============================ - - diff --git a/manual/en.txt b/manual/en.txt deleted file mode 100644 index 95d8bdbec..000000000 --- a/manual/en.txt +++ /dev/null @@ -1,26 +0,0 @@ -# Introduction -# Architecture -# Configuration -# Basic Mapping -# Association Mapping -# Inheritance Mapping -# Working with objects -# Working with associations -# Transactions and Concurrency -# Events -# Batch processing -# DQL (Doctrine Query Language) -# Query Builder -# Native SQL -# Change Tracking Policies -# Partial Objects -# XML Mapping -# YAML Mapping -# Annotations Reference -# PHP Mapping -# Caching -# Improving Performance -# Tools -# Metadata Drivers -# Best Practices -# Limitations and Known Issues \ No newline at end of file diff --git a/manual/en/index.rst b/manual/en/index.rst deleted file mode 100644 index cb638ddbb..000000000 --- a/manual/en/index.rst +++ /dev/null @@ -1,47 +0,0 @@ -.. Doctrine 2 ORM documentation master file, created by - sphinx-quickstart on Mon Nov 1 21:19:39 2010. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to Doctrine 2 ORM's documentation! -========================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - introduction - architecture - configuration - basic-mapping - association-mapping - inheritance-mapping - working-with-objects - working-with-associations - transactions-and-concurrency - events - batch-processing - dql-doctrine-query-language - query-builder - native-sql - change-tracking-policies - partial-objects - xml-mapping - yaml-mapping - annotations-reference - php-mapping - caching - improving-performance - tools - metadata-drivers - best-practices - limitations-and-known-issues - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - From 01c2a099916a8da191eaf6d1a0f5b5ea07148bbd Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 3 Dec 2010 22:47:33 +0100 Subject: [PATCH 143/430] Add Working with DateTime chapter. --- en/cookbook/working-with-datetime.rst | 168 ++++++++++++++++++++++++++ en/index.rst | 1 + 2 files changed, 169 insertions(+) create mode 100644 en/cookbook/working-with-datetime.rst diff --git a/en/cookbook/working-with-datetime.rst b/en/cookbook/working-with-datetime.rst new file mode 100644 index 000000000..a4d9af898 --- /dev/null +++ b/en/cookbook/working-with-datetime.rst @@ -0,0 +1,168 @@ +Working with DateTime Instances +=============================== + +There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner +workings pretty well not to make mistakes with date handling. This cookbook entry holds several +interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2. + +DateTime changes are detected by Reference +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When calling ``EntityManager#flush()`` Doctrine computes the changesets of all the currently managed entities +and saves the differences to the database. In case of object properties (@Column(type="datetime") or @Column(type="object")) +these comparisons are always made **BY REFERENCE**. That means the following change will **NOT** be saved into the database: + +.. code-block:: php + + updated->modify("now"); + } + } + +The way to go would be: + +.. code-block:: php + + updated = new \DateTime("now"); + } + } + +Default Timezone Gotcha +~~~~~~~~~~~~~~~~~~~~~~~ + +By default Doctrine assumes that you are working with a default timezone. Each DateTime instance that +is created by Doctrine will be assigned the timezone that is currently the default, either through +the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``. + +This is very important to handle correctly if your application runs on different serves or is moved from one to another server +(with different timezone settings). You have to make sure that the timezone is the correct one +on all this systems. + +Handling different Timezones with the DateTime Type +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you first come across the requirement to save different you are still optimistic to manage this mess, +however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2) +that supports timezones correctly. Correctly here means that you can cover all the use-cases that +can come up with timezones. If you don't believe me you should read up on `Storing DateTime +in Databases `_. + +The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC. +However with frequent daylight saving and political timezone changes you can have a UTC offset that moves +in different offset directions depending on the real location. + +The solution for this dialemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround +that even allows correct date-time handling with timezones: + +1. Always convert any DateTime instance to UTC. +2. Only set Timezones for displaying purposes +3. Save the Timezone in the Entity for persistence. + +Say we have an application for an international postal company and employees insert events regarding postal-package +around the world, in their current timezones. To determine the exact time an event occoured means to save both +the UTC time at the time of the booking and the timezone the event happend in. + +.. code-block:: php + + format($platform->getDateTimeFormatString(), + (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone(\DateTimeZone::UTC)) + ); + } + + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + $val = \DateTime::createFromFormat( + $platform->getDateTimeFormatString(), + $value, + (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone(\DateTimeZone::UTC)) + ); + if (!$val) { + throw ConversionException::conversionFailed($value, $this->getName()); + } + return $val; + } + } + +This database type makes sure that every DateTime instance is always saved in UTC, relative +to the current timezone that the passed DateTime instance has. To be able to transform these values +back into their real timezone you have to save the timezone in a seperate field of the entity +requiring timezoned datetimes: + +.. code-block:: php + + localized = true; + $this->created = $createDate; + $this->timezone = $createDate->getTimeZone()->getName(); + } + + public function getCreated() + { + if (!$this->localized) { + $this->created->setTimeZone(new \DateTimeZone($this->timezone)); + } + return $this->created; + } + } + +This snippet makes use of the previously discussed "changeset by reference only" property of +objects. That means a new DateTime will only be used during updating if the reference +changes between retrieval and flush operation. This means we can easily go and modify +the instance by setting the previous local timezone. diff --git a/en/index.rst b/en/index.rst index 877bdc7c3..83803b8ec 100644 --- a/en/index.rst +++ b/en/index.rst @@ -60,6 +60,7 @@ Cookbook cookbook/sql-table-prefixes cookbook/strategy-cookbook-introduction cookbook/validation-of-entities + cookbook/working-with-datetime Indices and tables ================== From eb0fd4d066cc8299029019ec313ac82eb9728d76 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 11 Dec 2010 01:01:44 +0100 Subject: [PATCH 144/430] Add section about database and unit of work being out of sync and how this effects your code. --- en/reference/dql-doctrine-query-language.rst | 1 + en/reference/working-with-objects.rst | 28 ++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index 8716ba587..c71cdbbdb 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -781,6 +781,7 @@ automatically for you: SELECT p0_.id AS id0, p0_.name AS name1, e1_.department AS department2, p0_.discr AS discr3 FROM Employee e1_ INNER JOIN Person p0_ ON e1_.id = p0_.id WHERE p0_.name = ? + The Query class --------------- diff --git a/en/reference/working-with-objects.rst b/en/reference/working-with-objects.rst index 9244ea1ab..08e950843 100644 --- a/en/reference/working-with-objects.rst +++ b/en/reference/working-with-objects.rst @@ -256,8 +256,12 @@ which means that its persistent state will be deleted once Just like ``persist``, invoking ``remove`` on an entity does NOT cause an immediate SQL DELETE to be issued on the database. The entity will be deleted on the next invocation of - ``EntityManager#flush()`` that involves that entity. - + ``EntityManager#flush()`` that involves that entity. This + means that entities scheduled for removal can still be queried + for and appear in query and collection results. See + the section on :ref:`Database and UnitOfWork Out-Of-Sync ` + for more information. + Example: @@ -465,6 +469,26 @@ When ``EntityManager#flush()`` is called, Doctrine inspects all managed, new and removed entities and will perform the following operations. +.. _workingobjects_database_uow_outofsync: + +Effects of Database and UnitOfWork being Out-Of-Sync +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As soon as you begin to change the state ofentities, call persist or remove the +contents of the UnitOfWork and the database will drive out of sync. They can +only be sychronized by calling ``EntityManager#flush()`. This section +describes the effects of database and UnitOfWork being out of sync. + +- Entities that are scheduled for removal can still be queried from the database. + They are returned from DQL and Repository queries and are visible in collections. +- Entities that are passed to ``EntityManager#persist`` do not turn up in query + results. +- Entities that have changed will not be overwritten with the state from the database. + This is because the identity map will detect the construction of an already existing + entity and assumes its the most up to date version. + +``EntityManager#flush()`` is never called implicitly by Doctrine. You always have to trigger it manually. + Synchronizing New and Managed Entities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 9c6d3dbecd6b173523db6b43271deaaec4958b01 Mon Sep 17 00:00:00 2001 From: beberlei Date: Sat, 11 Dec 2010 11:29:37 +0100 Subject: [PATCH 145/430] Add link to Doctrine homepage to docs layout template. --- en/_templates/layout.html | 1 + 1 file changed, 1 insertion(+) diff --git a/en/_templates/layout.html b/en/_templates/layout.html index 3380dbe13..79878cee5 100644 --- a/en/_templates/layout.html +++ b/en/_templates/layout.html @@ -18,6 +18,7 @@ {%- if not loop.first %}{{ reldelim2 }}{% endif %} {%- endfor %} {%- block rootrellink %} +
  • Doctrine Homepage »
  • {{ shorttitle|e }}{{ reldelim1 }}
  • {%- endblock %} {%- for parent in parents %} From d37120bb157c83df9237f27be65761ac24e586e3 Mon Sep 17 00:00:00 2001 From: Albert Jessurum Date: Sun, 12 Dec 2010 03:06:35 +0800 Subject: [PATCH 146/430] Fixed links --- en/reference/architecture.rst | 6 +++--- en/reference/limitations-and-known-issues.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/en/reference/architecture.rst b/en/reference/architecture.rst index 6950fec27..362ff9ea1 100644 --- a/en/reference/architecture.rst +++ b/en/reference/architecture.rst @@ -17,11 +17,11 @@ be any regular PHP class observing the following restrictions: always be private or protected, otherwise lazy-loading might not work as expected. - An entity class must not implement ``__clone`` or - `do so safely `_. + :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`. - An entity class must not implement ``__wakeup`` or - `do so safely `_. + :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`. Also consider implementing - `Serializable `_ + `Serializable `_ instead. - Any two entity classes in a class hierarchy that inherit directly or indirectly from one another must not have a mapped diff --git a/en/reference/limitations-and-known-issues.rst b/en/reference/limitations-and-known-issues.rst index dbf5cd7e5..0a7df425b 100644 --- a/en/reference/limitations-and-known-issues.rst +++ b/en/reference/limitations-and-known-issues.rst @@ -254,7 +254,7 @@ legacy-databases to work with Doctrine 2. - You can quote column-names as described in the - `Basic-Mapping `_ section. + :doc:`Basic-Mapping ` section. - You cannot quote join column names. - You cannot use non [a-zA-Z0-9\_]+ characters, they will break several SQL statements. From 0618944f8a34331aa57c7bc357d69159926f432f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 12 Dec 2010 13:12:44 +0100 Subject: [PATCH 147/430] DDC-901 - Fix documentation bug in Cookbook SQL Walker entry. --- en/cookbook/dql-custom-walkers.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/cookbook/dql-custom-walkers.rst b/en/cookbook/dql-custom-walkers.rst index e1934d64a..19ce8bda6 100644 --- a/en/cookbook/dql-custom-walkers.rst +++ b/en/cookbook/dql-custom-walkers.rst @@ -139,8 +139,8 @@ implementation is: } $pathExpression = new PathExpression( - PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, array( - $parent['metadata']->getSingleIdentifierFieldName()) + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, + $parent['metadata']->getSingleIdentifierFieldName() ); $pathExpression->type = PathExpression::TYPE_STATE_FIELD; From 29b47d3d440419df701ce1b3d7f07113c73360d9 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 12 Dec 2010 13:18:18 +0100 Subject: [PATCH 148/430] Fix @Table not supporting schema as attribute anymore. --- en/reference/annotations-reference.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst index 8795ed1a6..cddfcf736 100644 --- a/en/reference/annotations-reference.rst +++ b/en/reference/annotations-reference.rst @@ -789,7 +789,6 @@ Required attributes: Optional attributes: -- schema - Database schema name of this table. - indexes - Array of @Index annotations - uniqueConstraints - Array of @UniqueConstraint annotations. From 34dc2e6506928f441fa2e601112ee8d999a6659d Mon Sep 17 00:00:00 2001 From: ajessu Date: Tue, 14 Dec 2010 23:31:30 +0800 Subject: [PATCH 149/430] Delete duplicate item on menu --- en/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/en/index.rst b/en/index.rst index 83803b8ec..59c227056 100644 --- a/en/index.rst +++ b/en/index.rst @@ -30,7 +30,6 @@ Reference Guide reference/php-mapping reference/caching reference/improving-performance - reference/best-practices reference/tools reference/metadata-drivers reference/best-practices From 6e7f47bf9e0a1c461c2978e8c4409904250486cf Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 14 Dec 2010 23:29:05 +0100 Subject: [PATCH 150/430] Fix mess in Mini-Tutorial of Introduction chapter. --- en/reference/introduction.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst index ace3d7d9b..9f42e2741 100644 --- a/en/reference/introduction.rst +++ b/en/reference/introduction.rst @@ -318,19 +318,19 @@ Mini-tutorial the following: -.. raw:: html +.. code-block:: php - - - setName('Garfield'); :math:`$em->persist($`user); $em->flush(); + setName('Garfield'); + $em->persist($user); + $em->flush(); + + echo "User saved!"; Open index.php in your browser or execute it on the command line. From 2e00e9d7155feb404f451693c7ec2fa93d7fc02a Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 14 Dec 2010 23:33:31 +0100 Subject: [PATCH 151/430] Fix another mess in Mini Tutorial. --- en/reference/introduction.rst | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst index 9f42e2741..58a4ab77a 100644 --- a/en/reference/introduction.rst +++ b/en/reference/introduction.rst @@ -364,20 +364,18 @@ database. 5) Replace the contents of index.php with the following: -.. raw:: html +.. code-block:: php - - - createQuery('select u from Entities u where u.name = ?1'); - $q->setParameter(1, 'Garfield'); $garfield = - $q->getSingleResult(); + getName() . "!"; + ## PUT YOUR TEST CODE BELOW + + $q = $em->createQuery('select u from Entities u where u.name = ?1'); + $q->setParameter(1, 'Garfield'); $garfield = + $q->getSingleResult(); + + echo "Hello " . $garfield->getName() . "!"; You just created your first DQL query to retrieve the user with the From 76743431a4bb550055f39b08710293a14d869d8d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 14 Dec 2010 23:34:41 +0100 Subject: [PATCH 152/430] Fix wrong code in Mini tutorial --- en/reference/introduction.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst index 58a4ab77a..b7325aadd 100644 --- a/en/reference/introduction.rst +++ b/en/reference/introduction.rst @@ -371,7 +371,7 @@ database. ## PUT YOUR TEST CODE BELOW - $q = $em->createQuery('select u from Entities u where u.name = ?1'); + $q = $em->createQuery('select u from Entities\User u where u.name = ?1'); $q->setParameter(1, 'Garfield'); $garfield = $q->getSingleResult(); From 65fe9b86c5709c8d0be3b928b1c89d99ac370d05 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 14 Dec 2010 23:35:17 +0100 Subject: [PATCH 153/430] Fix another code block --- en/reference/introduction.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst index b7325aadd..73343a7e8 100644 --- a/en/reference/introduction.rst +++ b/en/reference/introduction.rst @@ -372,8 +372,8 @@ database. ## PUT YOUR TEST CODE BELOW $q = $em->createQuery('select u from Entities\User u where u.name = ?1'); - $q->setParameter(1, 'Garfield'); $garfield = - $q->getSingleResult(); + $q->setParameter(1, 'Garfield'); + $garfield = $q->getSingleResult(); echo "Hello " . $garfield->getName() . "!"; From 6a0f3f2d7b0c783bd8ed362d40ae4ad0462836aa Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 16 Dec 2010 21:59:27 +0100 Subject: [PATCH 154/430] Add code-configuration directive, thank you Fabien! --- en/_exts/configurationblock.py | 93 ++++++ en/_exts/configurationblock.pyc | Bin 0 -> 3386 bytes en/_static/configurationblock.css | 93 ++++++ en/_static/configurationblock.js | 34 ++ en/_templates/layout.html | 2 + en/conf.py | 4 +- en/tutorials/getting-started-xml-edition.rst | 328 +++++++++++++------ 7 files changed, 458 insertions(+), 96 deletions(-) create mode 100644 en/_exts/configurationblock.py create mode 100644 en/_exts/configurationblock.pyc create mode 100644 en/_static/configurationblock.css create mode 100644 en/_static/configurationblock.js diff --git a/en/_exts/configurationblock.py b/en/_exts/configurationblock.py new file mode 100644 index 000000000..36ca61f5b --- /dev/null +++ b/en/_exts/configurationblock.py @@ -0,0 +1,93 @@ +#Copyright (c) 2010 Fabien Potencier +# +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is furnished +#to do so, subject to the following conditions: +# +#The above copyright notice and this permission notice shall be included in all +#copies or substantial portions of the Software. +# +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +from docutils.parsers.rst import Directive, directives +from docutils import nodes +from string import upper + +class configurationblock(nodes.General, nodes.Element): + pass + +class ConfigurationBlock(Directive): + has_content = True + required_arguments = 0 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + formats = { + 'html': 'HTML', + 'xml': 'XML', + 'php': 'PHP', + 'yaml': 'YAML', + 'jinja': 'Twig', + 'html+jinja': 'Twig', + 'jinja+html': 'Twig', + 'php+html': 'PHP', + 'html+php': 'PHP', + 'ini': 'INI', + 'php-annotations': 'Annotations', + } + + def run(self): + env = self.state.document.settings.env + + node = nodes.Element() + node.document = self.state.document + self.state.nested_parse(self.content, self.content_offset, node) + + entries = [] + for i, child in enumerate(node): + if isinstance(child, nodes.literal_block): + # add a title (the language name) before each block + #targetid = "configuration-block-%d" % env.new_serialno('configuration-block') + #targetnode = nodes.target('', '', ids=[targetid]) + #targetnode.append(child) + + innernode = nodes.emphasis(self.formats[child['language']], self.formats[child['language']]) + + para = nodes.paragraph() + para += [innernode, child] + + entry = nodes.list_item('') + entry.append(para) + entries.append(entry) + + resultnode = configurationblock() + resultnode.append(nodes.bullet_list('', *entries)) + + return [resultnode] + +def visit_configurationblock_html(self, node): + self.body.append(self.starttag(node, 'div', CLASS='configuration-block')) + +def depart_configurationblock_html(self, node): + self.body.append('\n') + +def visit_configurationblock_latex(self, node): + pass + +def depart_configurationblock_latex(self, node): + pass + +def setup(app): + app.add_node(configurationblock, + html=(visit_configurationblock_html, depart_configurationblock_html), + latex=(visit_configurationblock_latex, depart_configurationblock_latex)) + app.add_directive('configuration-block', ConfigurationBlock) diff --git a/en/_exts/configurationblock.pyc b/en/_exts/configurationblock.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd1959c3a04f9ead6f17f187c2009b906f7dc47a GIT binary patch literal 3386 zcmd5;UvC>l5TCvC|JX^I(n^6sB|yliYE55)K&8+>sIAmS)g=OI6`d}=wY|;x&b?iy zi4?yO9(mvcAifh{g)acVnM-`3YTi;*pKo?%cXoIF&;9jxr=@M{$$*Z3HH_ckwSNG_ z_$%5aN)+`KB_8!XQatO?u1`sY`jvwAX}3yAjrujv719+lb%6~UdK6dbSE3`LXZyd=mn#ce@WDDKc!cNqejfAH&iz+4mHzRC5# znLX`VEd2>(yNWrg0BIKMQ{-$q)wx^4!XQhBW;DwqXR>rB$p*U%lkn!dcx?kfLr6uz zRu?!GH}iBDrqM))plPsPkAPX*GWndow^v2ml_jYue9`ptfr#Br>d#`M&-Hwh@ z+spFF^*9^Yo=$tA-gg#Uugi90dJt4_);b@`>erNLaql$e-T!FegG;pd1xTTVhCh9F zGQ7yJN<34eG4MK#;R`s)qm39$6%1AO-3HtSXKlbw@Q@5k#n8q8mQFD&V}PetFd}0%lZ>0c-%jxpFVslG^{0z&+Z)WIEcW( zXKcSRBXRNzlfH;7hX9{;tt17z#*^Yp990s+%7^o%k2W94Xj0>rK;KEz%t>fiy8;?~ z*Q0W7z#+o-arWH9Yu7PzC<+=y#v|j?T(PRA%6TyN%GE=ULUlsc1gQ&b5SEU7QX_2e zY5z|Sa-LFj|L5Q7xkvK~&8vi%I|RhpOLXK>_L3A6Z8@aFYw+XxI*U_MV4T-v)3OMJ zEAhwmRT_Ep<-9IyaG=~bhb#>;tIUM!)uAs$Lx`8pN#Xs`U67L?W0O}|hBe*~=HFDY ztw|$=)MXQII?kIUN=LJ3q#bbTUIY*iS{Pdk12SPY(Ww){YUfNkvf>+^?gbqBt`6{h zqJ^ssJk{3eIGjc~_<0fe+4TpRhR}G!w1|)t_HH zk|09p$#fi9W98@}vNdv6&cTJzD37LNp%QR}*gSC@6cGwd8cWVfK<&(uM7xlgx*Rw# z7NSNc!{GfBic6K;0nu{!}@*c95S9t59& z4sHUJW)bW>Z4+?tUBPQV1*oa=@m2i|Ps<;0K=ZpOB%gs;6cR>|PpHaLm{FIHJjMAK z6S9gUU{m}Nn>`7GdynsIZCTDLn6zwf*Cm)G3&r?5S$q)iMMaXiD0eOzNplO>D8c6d zrJc^xMD9Zzy@#B0?DbMakuc<%`~(cr;z%v5!xoY<%S~iq9T;U{3+C9gZ2wz5SmSo9 z(B^AQh^GIKav$NkSR*UX)b1%bUe}KET(o0{if4Idvv2?__ST)ka-Zpx1eJ1s3mosB z+*cvh+4dH}e>f#Lo0VS<6EXN#K#Fieu|+sJkC0FN0SlzUtqJ&%@)R@G2>FKsjZS(h z{{rqPl6-RAnaS5+HU?1~hg_beHw3pZ6MVzqHiK^gqzy6G;(J!=O7TtWg8{weEhQ({ zXmrWh^cf>8EvYu1j@s<5f^eLF+@YOG>(qmM;N@B#1RxECGYWRBoIR!e;aJJ*WDYThb7;BQ|7Zn+IzR;%b?SN+R3n||^9 E3vF@wG5`Po literal 0 HcmV?d00001 diff --git a/en/_static/configurationblock.css b/en/_static/configurationblock.css new file mode 100644 index 000000000..447d29631 --- /dev/null +++ b/en/_static/configurationblock.css @@ -0,0 +1,93 @@ +ul.simple +{ + padding: 5px 0 0 0; +} + +ul.simple li +{ + margin: 0; + margin-right: 5px; + display: inline; +} + +div.configuration-block em +{ + margin-bottom: 10px; +} + +div.configuration-block li +{ + padding: 5px; +} + +div.configuration-block em +{ + font-style: normal; + font-size: 90%; +} + +div.jsactive +{ + position: relative; +} + +div.jsactive ul +{ + list-style: none; +} + +div.jsactive li +{ + float: left; + list-style: none; + margin-left: 0; + -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; + background-color: #ddd; + margin-right: 5px; +} + +div.jsactive .selected +{ + background-color: #000; +} + +div.jsactive .selected a +{ + color: #fff; + text-decoration: none; +} + +div.jsactive .selected a:hover +{ + color: #fff; + text-decoration: underline; +} + +div.jsactive a +{ + color: #000; + text-decoration: none; +} + +div.jsactive a:hover +{ + color: #000; + text-decoration: underline; +} + +div.jsactive div +{ + position: absolute; + top: 30px; + left: 0; +} + +div.jsactive div div +{ + position: static; +} + +div.jsactive pre +{ + margin: 0; +} diff --git a/en/_static/configurationblock.js b/en/_static/configurationblock.js new file mode 100644 index 000000000..0f708a1a8 --- /dev/null +++ b/en/_static/configurationblock.js @@ -0,0 +1,34 @@ +$(document).ready(function(){ + $('div.configuration-block [class^=highlight-]').hide(); + $('div.configuration-block [class^=highlight-]').width($('div.configuration-block').width()); + + $('div.configuration-block').addClass('jsactive'); + $('div.configuration-block').addClass('clearfix'); + + $('div.configuration-block').each(function (){ + var el = $('[class^=highlight-]:first', $(this)); + el.show(); + el.parents('ul').height(el.height() + 40); + }); + + // Global + $('div.configuration-block li').each(function(){ + var str = $(':first', $(this)).html(); + $(':first ', $(this)).html(''); + $(':first ', $(this)).append('' + str + '') + $(':first', $(this)).bind('click', function(){ + $('[class^=highlight-]', $(this).parents('ul')).hide(); + $('li', $(this).parents('ul')).removeClass('selected'); + $(this).parent().addClass('selected'); + + var block = $('[class^=highlight-]', $(this).parent('li')); + block.show(); + block.parents('ul').height(block.height() + 40); + return false; + }); + }); + + $('div.configuration-block').each(function (){ + $('li:first', $(this)).addClass('selected'); + }); +}); diff --git a/en/_templates/layout.html b/en/_templates/layout.html index 79878cee5..447c36b64 100644 --- a/en/_templates/layout.html +++ b/en/_templates/layout.html @@ -104,6 +104,7 @@ {{ title|striptags }}{{ titlesuffix }} + {%- if not embedded %} {%- for scriptfile in script_files %} + {%- endfor %} {%- if use_opensearch %} `. An entity contains persistable properties. A persistable property -is an instance variable of the entity that contains the data which -is persisted and retrieved by Doctrine's data mapping -capabilities. +is an instance variable of the entity that is saved into and retrieved from the database +by Doctrine's data mapping capabilities. An Example Model: Bug Tracker ----------------------------- @@ -225,8 +224,8 @@ the bi-directional reference: reportedBugs[] = $bug; @@ -298,7 +300,7 @@ the database that points from from Bugs to Products. - - - - - - - - - -
    + .. code-block:: php + + + + + + + + + + +
    + + .. code-block:: yaml + + Product: + type: entity + table: products + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string The top-level ``entity`` definition tag specifies information about the class and table-name. The primitive type ``Product::$name`` is @@ -387,43 +416,99 @@ case of PostgreSql and Oracle. We then go on specifying the definition of a Bug: -.. code-block:: xml +.. configuration-block:: + .. code-block:: php - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + +
    + + .. code-block:: yaml + + Bug: + type: entity + table: bugs + id: + id: + type: integer + generator: + strategy: AUTO + fields: + description: + type: text + created: + type: datetime + status: + type: string + manyToOne: + reporter: + targetEntity: User + inversedBy: reportedBugs + engineer: + targetEntity: User + inversedBy: assignedBugs + manyToMany: + products: + targetEntity: Product + Here again we have the entity, id and primitive type definitions. The column names are used from the Zend\_Db\_Table examples and @@ -456,26 +541,79 @@ schema details all the time. The last missing definition is that of the User entity: -.. code-block:: xml +.. configuration-block:: - - - - - - - - - - - - - - - + .. code-block:: php + + + + + + + + + + + + + +
    + + .. code-block:: yaml + + User: + type: entity + table: users + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string + oneToMany: + reportedBugs: + targetEntity: Bug + mappedBy: reporter + assignedBugs: + targetEntity: Bug + mappedBy: engineer Here are some new things to mention about the ``one-to-many`` tags. Remember that we discussed about the inverse and owning side. Now @@ -511,7 +649,9 @@ step: $config->setAutoGenerateProxyClasses((APPLICATION_ENV == "development")); // Mapping Configuration (4) - $driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings"); + $driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings/xml"); + //$driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings/yml"); + //$driverImpl = $config->newDefaultAnnotationDriver(__DIR__."/entities"); $config->setMetadataDriverImpl($driverImpl); // Caching Configuration (5) @@ -603,10 +743,10 @@ doctrine command. Its a fairly simple file: You can then change into your project directory and call the Doctrine command-line tool: -.. code-block:: bash +:: doctrine@my-desktop> cd myproject/ - doctrine@my-desktop> doctrine orm:schema-tool:create + doctrine@my-desktop> doctrine orm:schema-tool:create --force .. note:: @@ -624,16 +764,16 @@ During the development you probably need to re-create the database several times when changing the Entity metadata. You can then either re-create the database: -.. code-block:: bash +:: - doctrine@my-desktop> doctrine orm:schema-tool:drop - doctrine@my-desktop> doctrine orm:schema-tool:create + doctrine@my-desktop> doctrine orm:schema-tool:drop --force + doctrine@my-desktop> doctrine orm:schema-tool:create --force Or use the update functionality: -.. code-block:: bash +:: - doctrine@my-desktop> doctrine orm:schema-tool:update + doctrine@my-desktop> doctrine orm:schema-tool:update --force The updating of databases uses a Diff Algorithm for a given Database Schema, a cornerstone of the ``Doctrine\DBAL`` package, From 2eab525077ac52f97169865499dbe9d54a2a7247 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 18 Dec 2010 17:14:20 +0100 Subject: [PATCH 155/430] Fix some small problems in the docs. --- en/reference/introduction.rst | 2 +- en/reference/tools.rst | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst index 73343a7e8..fd19d0407 100644 --- a/en/reference/introduction.rst +++ b/en/reference/introduction.rst @@ -394,6 +394,6 @@ Can you **find** the easier way?). Instead of reading through the reference manual we also recommend to look at the tutorials: -:doc:`Getting Started XML Edition <../tutorials/getting-started-xml-edition>` +:doc:`Getting Started Tutorial <../tutorials/getting-started-xml-edition>` diff --git a/en/reference/tools.rst b/en/reference/tools.rst index 5a8e8719d..5448e4a4c 100644 --- a/en/reference/tools.rst +++ b/en/reference/tools.rst @@ -347,7 +347,8 @@ First you need to retrieve the metadata instances with the ) ); - $cmf = new DisconnectedClassMetadataFactory($em); + $cmf = new DisconnectedClassMetadataFactory(); + $cmf->setEntityManager($em); $metadata = $cmf->getAllMetadata(); Now you can get an exporter instance and export the loaded metadata From 53ebc683c97d9ecf9de416db1949d66217fd7153 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 20 Dec 2010 21:51:05 +0100 Subject: [PATCH 156/430] Update limitations and known issues section in manual. --- en/reference/limitations-and-known-issues.rst | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/en/reference/limitations-and-known-issues.rst b/en/reference/limitations-and-known-issues.rst index 0a7df425b..3e98ef3ff 100644 --- a/en/reference/limitations-and-known-issues.rst +++ b/en/reference/limitations-and-known-issues.rst @@ -173,6 +173,38 @@ DQL and Repository/Persister generated queries, but as this is a pretty important feature we plan to add support for it in the future. +Restricing Associations +~~~~~~~~~~~~~~~~~~~~~~~ + +There is currently no way to restrict associations to a subset of entities matching a given condition. +You should use a DQL query to retrieve a subset of associated entities. For example +if you need all visible articles in a certain category you could have the following code +in an entity repository: + +.. code-block:: php + + getEntityManager() + ->createQuery($dql) + ->setParameter(1, $category) + ->getResult(); + } + } + +Cascade Merge with Bi-directional Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are two bugs now that concern the use of cascade merge in combination with bi-directional associations. +Make sure to study the behavior of cascade merge if you are using it: + +- `DDC-875 Merge can sometimes add the same entity twice into a collection`_ +- `DDC-763 Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"`_ + Custom Persisters ~~~~~~~~~~~~~~~~~ @@ -187,6 +219,17 @@ benefit from custom persister implementations: - `Evaluate possible ways in which stored-procedures can be used `_ - The previous Filter Rules Feature Request +Paginating Associations +~~~~~~~~~~~~~~~~~~~~~~~ + +It is not possible to paginate only parts of an associations at the moment. You can always only +load the whole association/collection into memory. This is rather problematic for large collections, +but we already plan to add facilities to fix this for Doctrine 2.1 + +- `DDC-546 New Fetch Mode EXTRA_LAZY `_ +- `Blog: Working with large collections (Workaround) `_ +- `LargeCollections Helper `_ + Persist Keys of Collections ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -213,10 +256,11 @@ in the core library. We don't think behaviors add more value than they cost pain and debugging hell. Please see the many different blog posts we have written on this topics: - - `Doctrine2 "Behaviors" in a Nutshell `_ - `A re-usable Versionable behavior for Doctrine2 `_ - `Write your own ORM on top of Doctrine2 `_ +- `Doctrine 2 Behavioral Extensions`_ +- `Doctrator_ Doctrine 2 has enough hooks and extension points so that *you* can add whatever you want on top of it. None of this will ever become @@ -264,4 +308,3 @@ support all CRUD operations on views that semantically map to certain tables. You can create views for all your problematic tables and column names to avoid the legacy quoting nightmare. - From 34057c5e4b82d08d6d5b838dd134ede8e40f9045 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 20 Dec 2010 22:12:24 +0100 Subject: [PATCH 157/430] Add note about how Query#setParameter() accepts named or positional parameters. --- en/reference/dql-doctrine-query-language.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index 95f5c28d8..5f9e9cf1b 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -159,6 +159,9 @@ contrast to many SQL dialects positional parameters are specified with numbers, for example "?1", "?2" and so on. Named parameters are specified with ":name1", ":name2" and so on. +When referencing the parameters in ``Query#setParameter($param, $value)`` +both named and positional parameters are used **without** their prefixies. + DQL SELECT Examples ~~~~~~~~~~~~~~~~~~~ From 6bf2f22315bf0161b5ceb5461a939a30c2bcf3f8 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 20 Dec 2010 22:19:02 +0100 Subject: [PATCH 158/430] Add another note about how Query#setParameter() accepts named or positional parameters. --- en/reference/dql-doctrine-query-language.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index 5f9e9cf1b..710fafd2d 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -1086,6 +1086,8 @@ pass parameters to the query the following methods can be used: - ``AbstractQuery::getParameter($param)`` - ``AbstractQuery::getParameters()`` +Both named and positional parameters are passed to these methods without their ? or : prefix. + Cache related API ^^^^^^^^^^^^^^^^^ From ef8689c400da91ec739aeb72a0e268c60dfe73e7 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 28 Dec 2010 00:23:06 +0100 Subject: [PATCH 159/430] Incorporate DDC-879 into Cookbook. --- en/cookbook/sql-table-prefixes.rst | 13 ++++++++++--- en/reference/dql-doctrine-query-language.rst | 4 ---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/en/cookbook/sql-table-prefixes.rst b/en/cookbook/sql-table-prefixes.rst index 7e1579286..6ca73c275 100644 --- a/en/cookbook/sql-table-prefixes.rst +++ b/en/cookbook/sql-table-prefixes.rst @@ -29,18 +29,25 @@ appropriate autoloaders. class TablePrefix { - protected $_prefix = ''; + protected $prefix = ''; public function __construct($prefix) { - $this->_prefix = (string) $prefix; + $this->prefix = (string) $prefix; } public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) { $classMetadata = $eventArgs->getClassMetadata(); - $classMetadata->setTableName($this->_prefix . $classMetadata->getTableName()); + $classMetadata->setTableName($this->prefix . $classMetadata->getTableName()); + foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) { + if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY) { + $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name']; + $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName; + } + } } + } Telling the EntityManager about our listener diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index 710fafd2d..18c9a4568 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -1309,11 +1309,7 @@ Path Expressions /* "u.Group" */ SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField -<<<<<<< HEAD:manual/en/dql-doctrine-query-language.txt -======= - ->>>>>>> ReST:en/reference/dql-doctrine-query-language.rst /* "u.Group.Permissions" */ CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField From 72081ff7ba8f380d42944a2dd39c5b8b811b16b0 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 30 Dec 2010 22:13:57 +0100 Subject: [PATCH 160/430] Note that Native SQL Query is not to be used for DELETE, UPDATE and INSERT statements. --- en/reference/native-sql.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/en/reference/native-sql.rst b/en/reference/native-sql.rst index 77bd0ad78..3d976794a 100644 --- a/en/reference/native-sql.rst +++ b/en/reference/native-sql.rst @@ -1,7 +1,7 @@ Native SQL ========== -A ``NativeQuery`` lets you execute native SQL, mapping the results +A ``NativeQuery`` lets you execute native SELECT SQL statements, mapping the results according to your specifications. Such a specification that describes how an SQL result set is mapped to a Doctrine result is represented by a ``ResultSetMapping``. It describes how each column @@ -9,6 +9,14 @@ of the database result should be mapped by Doctrine in terms of the object graph. This allows you to map arbitrary SQL code to objects, such as highly vendor-optimized SQL or stored-procedures. +.. note:: + + If you want to execute DELETE, UPDATE or INSERT statements + the Native SQL API cannot be used and will probably throw errors. + Use ``EntityManager#getConnection()`` to access the native database + connection and call the ``executeUpdate()`` method for these + queries. + The NativeQuery class --------------------- From 4a231a34f27ebb6cde95457faef33c9c923b2669 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 31 Dec 2010 14:44:14 +0100 Subject: [PATCH 161/430] Clarify MappedSuperclass and Unidirectional associations (with info on one-to-many and many-to-many associations). --- en/reference/inheritance-mapping.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/en/reference/inheritance-mapping.rst b/en/reference/inheritance-mapping.rst index 6128b7a4c..c4e76c531 100644 --- a/en/reference/inheritance-mapping.rst +++ b/en/reference/inheritance-mapping.rst @@ -18,7 +18,11 @@ appear in the middle of an otherwise mapped inheritance hierarchy A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped superclass must be - unidirectional. For further support of inheritance, the single or + unidirectional (with an owning side only). This means that One-To-Many + assocations are not possible on a mapped superclass at all. + Furthermore Many-To-Many associations are only possible if the + mapped superclass is only used in exactly one entity at the moment. + For further support of inheritance, the single or joined table inheritance features have to be used. From f4e93e7550a911ceb987a397387197df832d9cb6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 2 Jan 2011 11:18:22 +0100 Subject: [PATCH 162/430] Add section on orphan removal to Working with Associations chapter. --- en/reference/working-with-associations.rst | 72 ++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst index 9b34062f0..e49194e5b 100644 --- a/en/reference/working-with-associations.rst +++ b/en/reference/working-with-associations.rst @@ -517,4 +517,76 @@ that are found on already managed entities are automatically persisted as long as the association is defined as cascade persist. +Orphan Removal +-------------- +There is another concept of cascading that is relevant only when removing entities +from collections. If an Entity of type ``A`` contains references to privately +owned Entities ``B`` then if the reference from ``A`` to ``B`` is removed the +entity ``B`` should also be removed, because it is not used anymore. + +OrphanRemoval works with both one-to-one and one-to-many associations. + +.. note:: + + When using the ``orphanRemoval=true`` option Doctrine makes the assumption + that the entities are privately owned and will **NOT** be reused by other entities. + If you neglect this assumption your entities will get deleted by Doctrine anyways. + +As a better example consider an Addressbook application where you have Contacts, Addresses +and StandingData: + +.. code-block:: php + + addresses = new ArrayCollection(); + } + + public function newStandingData(StandingData $sd) + { + $this->standingData = $sd; + } + + public function removeAddress($pos) + { + unset($this->addresses[$pos]); + } + } + +Now two examples what happens when you remove the references: + +.. code-block:: php + + find("Addressbook\Contact", $contactId); + $contact->newStandingData(new StandingData("Firstname", "Lastname", "Street")); + $contact->removeAddress(1); + + $em->flush(); + +In this case you have only changed the ``Contact`` entity but you removed +the references for standing data and one address reference. When flush is called +not only are the references removed also both the old standing data and the one address entity +are deleted from the database. From 721915d61b2d54c3fdab59a34fbdc65b23f195bf Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 2 Jan 2011 11:53:12 +0100 Subject: [PATCH 163/430] Fix bug in configurationblock.css destroying simple bullet lists. --- en/_static/configurationblock.css | 4 +- en/reference/annotations-reference.rst | 115 ++++++++++++------------- 2 files changed, 58 insertions(+), 61 deletions(-) diff --git a/en/_static/configurationblock.css b/en/_static/configurationblock.css index 447d29631..4f7043148 100644 --- a/en/_static/configurationblock.css +++ b/en/_static/configurationblock.css @@ -1,9 +1,9 @@ -ul.simple +div.configuration-block ul.simple { padding: 5px 0 0 0; } -ul.simple li +div.configuration-block ul.simple li { margin: 0; margin-right: 5px; diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst index cddfcf736..b918e85d3 100644 --- a/en/reference/annotations-reference.rst +++ b/en/reference/annotations-reference.rst @@ -7,7 +7,6 @@ with short explanations on their context and usage. Index ----- - - :ref:`@Column ` - :ref:`@ChangeTrackingPolicy ` - :ref:`@DiscriminatorColumn ` @@ -53,27 +52,31 @@ as part of the lifecycle of the instance variables entity-class. Required attributes: - -- type - Name of the Doctrine Type which is converted between PHP +- **type**: Name of the Doctrine Type which is converted between PHP and Database representation. Optional attributes: - -- name - By default the property name is used for the database +- **name**: By default the property name is used for the database column name also, however the 'name' attribute allows you to determine the column name. -- length - Used by the "string" type to determine its maximum + +- **length**: Used by the "string" type to determine its maximum length in the database. Doctrine does not validate the length of a string values for you. -- precision - The precision for a decimal (exact numeric) column + +- **precision**: The precision for a decimal (exact numeric) column (Applies only for decimal column) -- scale - The scale for a decimal (exact numeric) column (Applies + +- **scale**: The scale for a decimal (exact numeric) column (Applies only for decimal column) -- unique - Boolean value to determine if the value of the column + +- **unique**: Boolean value to determine if the value of the column should be unique across all rows of the underlying entities table. -- nullable - Determines if NULL values allowed for this column. -- columnDefinition - DDL SQL snippet that starts after the column + +- **nullable**: Determines if NULL values allowed for this column. + +- **columnDefinition**: DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. However you should make careful use of this feature and the @@ -146,14 +149,14 @@ actually instantiated as. Required attributes: -- name - The column name of the discriminator. This name is also +- **name**: The column name of the discriminator. This name is also used during Array hydration as key to specify the class-name. Optional attributes: -- type - By default this is string. -- length - By default this is 255. +- **type**: By default this is string. +- **length**: By default this is 255. .. _annref_discriminatormap: @@ -192,7 +195,7 @@ the persistence of all classes marked as entity. Optional attributes: -- repositoryClass - Specifies the FQCN of a subclass of the +- **repositoryClass**: Specifies the FQCN of a subclass of the Doctrine. Use of repositories for entities is encouraged to keep specialized DQL and SQL operations separated from the Model/Domain Layer. @@ -226,7 +229,7 @@ used as default. Required attributes: -- strategy - Set the name of the identifier generation strategy. +- **strategy**: Set the name of the identifier generation strategy. Valid values are AUTO, SEQUENCE, TABLE, IDENTITY and NONE. Example: @@ -283,8 +286,8 @@ has meaning in the SchemaTool schema generation context. Required attributes: -- name - Name of the Index -- columns - Array of columns. +- **name**: Name of the Index +- **columns**: Array of columns. Example: @@ -377,23 +380,23 @@ inferred from the table and primary key names. Required attributes: -- name - Column name that holds the foreign key identifier for +- **name**: Column name that holds the foreign key identifier for this relation. In the context of @JoinTable it specifies the column name in the join table. -- referencedColumnName - Name of the primary key identifier that +- **referencedColumnName**: Name of the primary key identifier that is used for joining of this relation. Optional attributes: -- unique - Determines if this relation exclusive between the +- **unique**: Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false. -- nullable - Determine if the related entity is required, or if +- **nullable**: Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true. -- onDelete - Cascade Action (Database-level) -- onUpdate - Cascade Action (Database-level) -- columnDefinition - DDL SQL snippet that starts after the column +- **onDelete**: Cascade Action (Database-level) +- **onUpdate**: Cascade Action (Database-level) +- **columnDefinition**: DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on @JoinColumn is necessary if you need slightly @@ -438,18 +441,13 @@ using the affected table and the column names. Required attributes: -- name - Database name of the join-table -- joinColumns - An array of @JoinColumn annotations describing the +- **name**: Database name of the join-table +- **joinColumns**: An array of @JoinColumn annotations describing the join-relation between the owning entities table and the join table. -- inverseJoinColumns - An array of @JoinColumn annotations +- **inverseJoinColumns**: An array of @JoinColumn annotations describing the join-relation between the inverse entities table and the join table. -Optional attributes: - - -- schema - Database schema name of this table. - Example: .. code-block:: php @@ -475,15 +473,15 @@ describes a many-to-one relationship between two entities. Required attributes: -- targetEntity - FQCN of the referenced target entity. Can be the +- **targetEntity**: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! Optional attributes: -- cascade - Cascade Option -- fetch - One of LAZY or EAGER +- **cascade**: Cascade Option +- **fetch**: One of LAZY or EAGER - inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. @@ -511,20 +509,20 @@ entities. Required attributes: -- targetEntity - FQCN of the referenced target entity. Can be the +- **targetEntity**: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! Optional attributes: -- mappedBy - This option specifies the property name on the +- **mappedBy**: This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship. -- inversedBy - The inversedBy attribute designates the field in the +- **inversedBy**: The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. -- cascade - Cascade Option -- fetch - One of LAZY or EAGER +- **cascade**: Cascade Option +- **fetch**: One of LAZY or EAGER **NOTE** For ManyToMany bidirectional relationships either side may be the owning side (the side that defines the @JoinTable and/or @@ -583,19 +581,19 @@ primary key column names apply here too. Required attributes: -- targetEntity - FQCN of the referenced target entity. Can be the +- **targetEntity**: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! Optional attributes: -- cascade - Cascade Option -- fetch - One of LAZY or EAGER -- orphanRemoval - Boolean that specifies if orphans, inverse +- **cascade**: Cascade Option +- **fetch**: One of LAZY or EAGER +- **orphanRemoval**: Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any owning instance, should be removed by Doctrine. Defaults to false. -- inversedBy - The inversedBy attribute designates the field in the +- **inversedBy**: The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. Example: @@ -617,18 +615,18 @@ Example: Required attributes: -- targetEntity - FQCN of the referenced target entity. Can be the +- **targetEntity**: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! Optional attributes: -- cascade - Cascade Option -- orphanRemoval - Boolean that specifies if orphans, inverse +- **cascade**: Cascade Option +- **orphanRemoval**: Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any owning instance, should be removed by Doctrine. Defaults to false. -- mappedBy - This option specifies the property name on the +- **mappedBy**: This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship. @@ -747,16 +745,16 @@ the increment size and initial values of the sequence. Required attributes: -- sequenceName - Name of the sequence +- **sequenceName**: Name of the sequence Optional attributes: -- allocationSize - Increment the sequence by the allocation size +- **allocationSize**: Increment the sequence by the allocation size when its fetched. A value larger than 1 allows to optimize for scenarios where you create more than one new entity per request. Defaults to 10 -- initialValue - Where does the sequence start, defaults to 1. +- **initialValue**: Where does the sequence start, defaults to 1. Example: @@ -784,13 +782,13 @@ unqualified classname. Required attributes: -- name - Name of the table +- **name**: Name of the table Optional attributes: -- indexes - Array of @Index annotations -- uniqueConstraints - Array of @UniqueConstraint annotations. +- **indexes**: Array of @Index annotations +- **uniqueConstraints**: Array of @UniqueConstraint annotations. Example: @@ -820,8 +818,8 @@ context. Required attributes: -- name - Name of the Index -- columns - Array of columns. +- **name**: Name of the Index +- **columns**: Array of columns. Example: @@ -857,4 +855,3 @@ Example: */ protected $version; - From 0e5b30902c87c7be928f0ed9c7135276bfe6e165 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 23 Jan 2011 11:16:25 +0100 Subject: [PATCH 164/430] Enhanced QueryBuilder docs with paragraph on limit and executing queries. --- en/reference/query-builder.rst | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst index e73f9900b..8cf459b15 100644 --- a/en/reference/query-builder.rst +++ b/en/reference/query-builder.rst @@ -170,6 +170,49 @@ mentioned syntax with "getParameter()" or "getParameters()": Note: If you try to get a parameter that was not bound yet, getParameter() simply returns NULL. +Limiting the Result +^^^^^^^^^^^^^^^^^^^ + +To limit a result the query builder has some methods in common with +the Query object which can be retrieved from ``EntityManager#createQuery()``. + +.. code-block:: php + + add('select', 'u') + ->add('from', 'User u') + ->add('orderBy', 'u.name ASC') + ->setFirstResult( $offset ) + ->setMaxResults( $limit ); + +Executing a Query +^^^^^^^^^^^^^^^^^ + +The QueryBuilder is a builder object only, it has no means of actually +executing the Query. Additionally a set of parameters such as query hints +cannot be set on the QueryBuilder itself. This is why you always have to convert +a querybuilder instance into a Query object: + +.. code-block:: + + // $qb instanceof QueryBuilder + $query = $qb->getQuery(); + + // Set additional Query options + $query->setQueryHint('foo', 'bar'); + $query->useResultCache('my_cache_id'); + + // Execute Query + $result = $query->getResult(); + $single = $query->getSingleResult(); + $array = $query->getArrayResult(); + $scalar = $query->getScalarResult(); + $singleScalar = $query->getSingleScalarResult(); + Expr\* classes ^^^^^^^^^^^^^^ From 51bdd6499a3d3a0b146fb91064e71c13ca97e8d0 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Jan 2011 22:46:41 +0100 Subject: [PATCH 165/430] Fix typo in codeigniter coobkok entry --- en/cookbook/integrating-with-codeigniter.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/en/cookbook/integrating-with-codeigniter.rst b/en/cookbook/integrating-with-codeigniter.rst index fcc7ac774..1c06a34e2 100644 --- a/en/cookbook/integrating-with-codeigniter.rst +++ b/en/cookbook/integrating-with-codeigniter.rst @@ -49,7 +49,7 @@ Customize it to your needs. Doctrine\ORM\Configuration, Doctrine\ORM\EntityManager, Doctrine\Common\Cache\ArrayCache, - Doctrine\DBAL\Logging\EchoSqlLogger; + Doctrine\DBAL\Logging\EchoSQLLogger; class Doctrine { @@ -86,8 +86,8 @@ Customize it to your needs. $config->setProxyNamespace('Proxies'); // Set up logger - $logger = new EchoSqlLogger; - $config->setSqlLogger($logger); + $logger = new EchoSQLLogger; + $config->setSQLLogger($logger); $config->setAutoGenerateProxyClasses( TRUE ); From 7d42497e09650fa71da130cec106ed6aa00e8dce Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Jan 2011 22:56:09 +0100 Subject: [PATCH 166/430] Fix typo with SQLLogger in configuration.rst --- en/reference/configuration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index a4b5713b6..27a54c6fe 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -307,10 +307,10 @@ SQL Logger (***Optional***) Gets or sets the logger to use for logging all SQL statements executed by Doctrine. The logger class must implement the -``Doctrine\DBAL\Logging\SqlLogger`` interface. A simple default +``Doctrine\DBAL\Logging\SQLLogger`` interface. A simple default implementation that logs to the standard output using ``echo`` and ``var_dump`` can be found at -``Doctrine\DBAL\Logging\EchoSqlLogger``. +``Doctrine\DBAL\Logging\EchoSQLLogger``. Auto-generating Proxy Classes (***OPTIONAL***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 754ebc052ef73e6484dde68e891bf4fba14fafe7 Mon Sep 17 00:00:00 2001 From: Joel Clermont Date: Thu, 27 Jan 2011 21:02:56 +0800 Subject: [PATCH 167/430] DDC-823 - Fix minor grammar and punctuation mistakes --- en/tutorials/getting-started-xml-edition.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst index 863f5f603..430a6756a 100644 --- a/en/tutorials/getting-started-xml-edition.rst +++ b/en/tutorials/getting-started-xml-edition.rst @@ -6,11 +6,11 @@ domain model in a non-interfering way. The Data Mapper pattern is at the heart of this project, aiming for a complete separation of the domain/business logic from the persistence in a relational database management system. The benefit of Doctrine for the -programmer is the possibility can focus solely on the business and +programmer is the ability to focus solely on the business logic and worry about persistence only as a secondary task. This doesn't mean persistence is not important to Doctrine 2, however it is our belief that there are considerable benefits for object-oriented -programming, if persistence and entities are kept perfectly +programming if persistence and entities are kept perfectly separated. What are Entities? From c89290e5077ade0080032d1cf5bdcf5947f713cb Mon Sep 17 00:00:00 2001 From: Ray Rehbein Date: Thu, 16 Dec 2010 06:12:58 +0800 Subject: [PATCH 168/430] Corrected mismatch between example and comment entities. --- en/reference/working-with-objects.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/working-with-objects.rst b/en/reference/working-with-objects.rst index 08e950843..ccb1fae08 100644 --- a/en/reference/working-with-objects.rst +++ b/en/reference/working-with-objects.rst @@ -113,7 +113,7 @@ from newly opened EntityManager. $article = $em->find('Article', 1); -This code only retrieves the ``User`` instance with id 1 executing +This code only retrieves the ``Article`` instance with id 1 executing a single SELECT statement against the user table in the database. You can still access the associated properties author and comments and the associated objects they contain. From d81dca3e909270cb69d7fe2b42bda043e2d498fa Mon Sep 17 00:00:00 2001 From: Ray Rehbein Date: Thu, 16 Dec 2010 06:13:28 +0800 Subject: [PATCH 169/430] Corrected example calling wrong method name --- en/reference/working-with-associations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst index e49194e5b..8edd54eaf 100644 --- a/en/reference/working-with-associations.rst +++ b/en/reference/working-with-associations.rst @@ -238,7 +238,7 @@ element. Here are some examples: $comment->getUserFavorites()->removeElement($user); // Remove by Key - $user->getComments()->removeElement($ithComment); + $user->getComments()->remove($ithComment); $comment->setAuthor(null); You need to call ``$em->flush()`` to make persist these changes in From 0411b1e75d645377042e1c6a7f28ff3a43f21e8a Mon Sep 17 00:00:00 2001 From: Ray Rehbein Date: Thu, 16 Dec 2010 06:14:08 +0800 Subject: [PATCH 170/430] Attempt at correction for a formatting glitch. It appears Sphinx doesn't use the `` mark in the middle of a word correctly --- en/reference/native-sql.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/native-sql.rst b/en/reference/native-sql.rst index 3d976794a..dfec36ff8 100644 --- a/en/reference/native-sql.rst +++ b/en/reference/native-sql.rst @@ -58,7 +58,7 @@ components: .. note:: It might not surprise you that Doctrine uses - ``ResultSetMapping``s internally when you create DQL queries. As + ``ResultSetMapping`` internally when you create DQL queries. As the query gets parsed and transformed to SQL, Doctrine fills a ``ResultSetMapping`` that describes how the results should be processed by the hydration routines. From fc6cec90745ffd1f6e7220ca6372f3c903c47c0e Mon Sep 17 00:00:00 2001 From: Ray Rehbein Date: Thu, 16 Dec 2010 06:15:14 +0800 Subject: [PATCH 171/430] Added setParamater calls to examples Whitespace changes to break up the long SQL lines into a readable format --- en/reference/dql-doctrine-query-language.rst | 48 ++++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index 18c9a4568..d745d8a98 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -259,6 +259,7 @@ With WHERE Clause and Positional Parameter: createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1'); + $query->setParameter(1, 321); $users = $query->getResult(); // array of ForumUser objects With WHERE Clause and Named Parameter: @@ -267,6 +268,7 @@ With WHERE Clause and Named Parameter: createQuery('SELECT u FROM ForumUser u WHERE u.username = :name'); + $query->setParameter(':name', 'Bob'); $users = $query->getResult(); // array of ForumUser objects With Nested Conditions in WHERE Clause: @@ -275,6 +277,11 @@ With Nested Conditions in WHERE Clause: createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id'); + $query->setParameters(array( + ':name' => 'Bob', + ':name' => 'Alice', + ':id' => 321, + )); $users = $query->getResult(); // array of ForumUser objects With COUNT DISTINCT: @@ -324,6 +331,8 @@ BETWEEN in WHERE clause: createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2'); + $query->setParameter(1, 123); + $query->setParameter(2, 321); $usernames = $query->getResult(); DQL Functions in WHERE clause: @@ -354,9 +363,11 @@ CONCAT() DQL Function: createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); + $query->setParameter(1, 'Jess'); $ids = $query->getResult(); $query = $em->createQuery('SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1'); + $query->setParameter(1, 321); $idUsernames = $query->getResult(); EXISTS in WHERE clause with correlated Subquery @@ -705,7 +716,12 @@ entities looks like the following: .. code-block:: sql - CREATE TABLE Person (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(50) NOT NULL, discr VARCHAR(255) NOT NULL, department VARCHAR(50) NOT NULL) + CREATE TABLE Person ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(50) NOT NULL, + discr VARCHAR(255) NOT NULL, + department VARCHAR(50) NOT NULL + ) Now when persist a new ``Employee`` instance it will set the discriminator value for us automatically: @@ -732,7 +748,9 @@ entities: .. code-block:: sql - SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, p0_.discr AS discr3 FROM Person p0_ WHERE (p0_.name = ?) AND p0_.discr IN ('employee') + SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, + p0_.discr AS discr3 FROM Person p0_ + WHERE (p0_.name = ?) AND p0_.discr IN ('employee') Class Table Inheritance ~~~~~~~~~~~~~~~~~~~~~~~ @@ -769,8 +787,17 @@ you'll notice some differences: .. code-block:: sql - CREATE TABLE Person (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(50) NOT NULL, discr VARCHAR(255) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB; - CREATE TABLE Employee (id INT NOT NULL, department VARCHAR(50) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB; + CREATE TABLE Person ( + id INT AUTO_INCREMENT NOT NULL, + name VARCHAR(50) NOT NULL, + discr VARCHAR(255) NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE Employee ( + id INT NOT NULL, + department VARCHAR(50) NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; ALTER TABLE Employee ADD FOREIGN KEY (id) REFERENCES Person(id) ON DELETE CASCADE @@ -784,7 +811,10 @@ automatically for you: .. code-block:: sql - SELECT p0_.id AS id0, p0_.name AS name1, e1_.department AS department2, p0_.discr AS discr3 FROM Employee e1_ INNER JOIN Person p0_ ON e1_.id = p0_.id WHERE p0_.name = ? + SELECT p0_.id AS id0, p0_.name AS name1, e1_.department AS department2, + p0_.discr AS discr3 + FROM Employee e1_ INNER JOIN Person p0_ ON e1_.id = p0_.id + WHERE p0_.name = ? The Query class @@ -826,7 +856,7 @@ the Query class. Here they are: pure/mixed distinction does not apply. - ``Query#getArrayResult()``: Retrieves an array graph (a nested array) that is largely interchangeable with the object graph - generated by ``Query#getResultList()`` for read-only purposes. + generated by ``Query#getResult()`` for read-only purposes. .. note:: @@ -850,7 +880,7 @@ general-purpose method Using this method you can directly supply the hydration mode as the second parameter via one of the Query constants. In fact, the methods mentioned earlier are just convenient shortcuts for the -execute method. For example, the method ``Query#getResultList()`` +execute method. For example, the method ``Query#getResult()`` internally invokes execute, passing in ``Query::HYDRATE_OBJECT`` as the hydration mode. @@ -914,10 +944,10 @@ Here is how the result could look like: array array [0] => User (Object) - ['nameUpper'] => "Roman" + ['nameUpper'] => "ROMAN" array [0] => User (Object) - ['nameUpper'] => "Jonathan" + ['nameUpper'] => "JONATHAN" ... And here is how you would access it in PHP code: From be8d34dc21caa71a00f8f610bd7400c7a7984e9d Mon Sep 17 00:00:00 2001 From: Ray Rehbein Date: Thu, 16 Dec 2010 06:17:46 +0800 Subject: [PATCH 172/430] Whitespace correction --- en/reference/query-builder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst index 8cf459b15..da5bbf2cd 100644 --- a/en/reference/query-builder.rst +++ b/en/reference/query-builder.rst @@ -276,7 +276,7 @@ complete list of supported helper methods available: public function from($from, $alias); // Returns Expr\From instance // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', Expr\Join::ON, 'p.user_id = u.id AND p.country_code = 55'); - // Example - $qb->expr()->leftJoin('u. Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); + // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); public function leftJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance // Example - $qb->expr()->innerJoin('u.Group', 'g', Expr\Join::WITH, 'g.manager_level = 100'); From 2a38e5f408b3ec093c9277b3c52089e456644153 Mon Sep 17 00:00:00 2001 From: Ray Rehbein Date: Thu, 16 Dec 2010 22:58:37 +0800 Subject: [PATCH 173/430] Syntex and code correction in example --- en/reference/caching.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/caching.rst b/en/reference/caching.rst index 1fe17a153..24d99b541 100644 --- a/en/reference/caching.rst +++ b/en/reference/caching.rst @@ -79,7 +79,7 @@ driver by itself. $memcache->connect('memcache_host', 11211); $cacheDriver = new \Doctrine\Common\Cache\MemcacheCache(); - $cacheDriver->setMemcache() + $cacheDriver->setMemcache($memcache); $cacheDriver->save('cache_id', 'my_data'); Xcache From 8fc7eb295bf7b6e776ec5510133064687c9fbf88 Mon Sep 17 00:00:00 2001 From: Ivar Nesje Date: Sat, 22 Jan 2011 19:42:48 +0800 Subject: [PATCH 174/430] Fixed lifecycleCallbacks documentation bug when using YAML mapping --- en/reference/events.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/en/reference/events.rst b/en/reference/events.rst index 836980d41..0a0f760e1 100644 --- a/en/reference/events.rst +++ b/en/reference/events.rst @@ -275,9 +275,9 @@ can do it with the following. # ... name: type: string(50) - lifecycleCallbacks: - doStuffOnPrePersist: prePersist - doStuffOnPostPersist: postPersist + lifecycleCallbacks: + prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] + postPersist: [ doStuffOnPostPersist ] XML would look something like this: From af60471b629bbd6a18a8c6bccef2071b8557ee41 Mon Sep 17 00:00:00 2001 From: Ivar Nesje Date: Sat, 22 Jan 2011 19:47:15 +0800 Subject: [PATCH 175/430] fixed indentation on previous commit --- en/reference/events.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/en/reference/events.rst b/en/reference/events.rst index 0a0f760e1..ccdf199c8 100644 --- a/en/reference/events.rst +++ b/en/reference/events.rst @@ -275,9 +275,9 @@ can do it with the following. # ... name: type: string(50) - lifecycleCallbacks: - prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] - postPersist: [ doStuffOnPostPersist ] + lifecycleCallbacks: + prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] + postPersist: [ doStuffOnPostPersist ] XML would look something like this: From 0f974c562c26eb4ce2930b015a7be30fa8668f09 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 5 Feb 2011 13:43:45 +0100 Subject: [PATCH 176/430] Add Working with Indexed Associations Tutorial --- en/index.rst | 1 + en/reference/query-builder.rst | 2 +- .../working-with-indexed-assocations.rst | 294 ++++++++++++++++++ 3 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 en/tutorials/working-with-indexed-assocations.rst diff --git a/en/index.rst b/en/index.rst index 59c227056..3aca1abca 100644 --- a/en/index.rst +++ b/en/index.rst @@ -42,6 +42,7 @@ Tutorials :maxdepth: 1 tutorials/getting-started-xml-edition + tutorials/working-with-indexed-associations Cookbook -------- diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst index 8cf459b15..da5bbf2cd 100644 --- a/en/reference/query-builder.rst +++ b/en/reference/query-builder.rst @@ -276,7 +276,7 @@ complete list of supported helper methods available: public function from($from, $alias); // Returns Expr\From instance // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', Expr\Join::ON, 'p.user_id = u.id AND p.country_code = 55'); - // Example - $qb->expr()->leftJoin('u. Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); + // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); public function leftJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance // Example - $qb->expr()->innerJoin('u.Group', 'g', Expr\Join::WITH, 'g.manager_level = 100'); diff --git a/en/tutorials/working-with-indexed-assocations.rst b/en/tutorials/working-with-indexed-assocations.rst new file mode 100644 index 000000000..5e82617d9 --- /dev/null +++ b/en/tutorials/working-with-indexed-assocations.rst @@ -0,0 +1,294 @@ +Working with Indexed Assocations +================================ + +.. note: + + This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series. + +Doctrine 2 collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in +the first version of Doctrine keys retrieved from the database were always numerical unless ``INDEX BY`` +was used. Starting with Doctrine 2.1 you can index your collections by a value in the related entity. +This is a first step towards full ordered hashmap support through the Doctrine ORM. +The feature works like an implicit ``INDEX BY`` for the selected association but has several +downsides also: + +- You have to manage both the key and field if you want to change the index by field value. +- On each request the keys are regenerated from the field value not from the previous collection key. +- Values of the Index-By keys are never considered during persistence, it only exists for accessing purposes. +- Fields that are used for the index by feature **HAVE** to be unique in the database. The behavior for multiple entities + with the same index-by field value is undefined. + +As an example we will design a simple stock exchange list view. The domain consists of the entity ``Stock`` +and ``Market`` where each Stock has a symbol and is traded on a single market. Instead of having a numerical +list of stocks traded on a market they will be indexed by their symbol, which is unique across all markets. + +Mapping Indexed Assocations +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can map indexed assocations by adding: + + * ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation. + * ``index-by`` attribute to any ```` or ```` xml element. + * ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files. + +The code and mappings for the Market entity looks like this: + +.. configuration-block:: + .. code-block:: php + + name = $name; + $this->stocks = new ArrayCollection(); + } + + public function getId() + { + return $this->id; + } + + public function getName() + { + return $this->name; + } + + public function addStock(Stock $stock) + { + $this->stocks[$stock->getSymbol()] = $stock; + } + + public function getStock($symbol) + { + if (!isset($this->stocks[$symbol])) { + throw new \InvalidArgumentException("Symbol is not traded on this market."); + } + + return $this->stocks[$symbol]; + } + + public function getStocks() + { + return $this->stocks->toArray(); + } + } + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: yml + + Doctrine\Tests\Models\StockExchange\Market: + type: entity + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type:string + oneToMany: + stocks: + targetEntity: Stock + mappedBy: market + indexBy: symbol + +Inside the ``addStock()`` method you can see how we directly set the key of the association to the symbol, +so that we can work with the indexed assocation directly after invoking ``addStock()``. Inside ``getStock($symbol)`` +we pick a stock traded on the particular market by symbol. If this stock doesn't exist an exception is thrown. + +The ``Stock`` entity doesn't contain any special instructions that are new, but for completeness +here are the code and mappings for it: + +.. configuration-block:: + .. code-block:: php + + symbol = $symbol; + $this->market = $market; + $market->addStock($this); + } + + public function getSymbol() + { + return $this->symbol; + } + } + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: yml + + Doctrine\Tests\Models\StockExchange\Stock: + type: entity + id: + id: + type: integer + generator: + strategy: AUTO + fields: + symbol: + type: string + manyToOne: + market: + targetEntity: Market + inversedBy: stocks + +Querying indexed associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Now that we defined the stocks collection to be indexed by symbol we can take a look at some code, +that makes use of the indexing. + +First we will populate our database with two example stocks traded on a single market: + +.. code-block:: php + + persist($market); + $em->persist($stock1); + $em->persist($stock2); + $em->flush(); + +This code is not particular interesting since the indexing feature is not yet used. In a new request we could +now query for the market: + +.. code-block:: php + + find("Doctrine\Tests\Models\StockExchange\Market", $marketId); + + // Access the stocks by symbol now: + $stock = $market->getSymbol($symbol); + + echo $stock->getSymbol(); // will print "AAPL" + +The implementation ``Market::addStock()`` in combination with ``indexBy`` allows to access the collection +consistently by the Stock symbol. It does not matter if Stock is managed by Doctrine or not. + +The same applies to DQL queries: The ``indexBy`` configuration acts as implicit "INDEX BY" to a join association. + +.. code-block:: php + + createQuery($dql) + ->setParameter(1, $marketId) + ->getSingleResult(); + + // Access the stocks by symbol now: + $stock = $market->getSymbol($symbol); + + echo $stock->getSymbol(); // will print "AAPL" + +Outlook into the Future +~~~~~~~~~~~~~~~~~~~~~~~ + +For the inverse side of a many-to-many associations there will be a way to persist the keys and the order +as a third and fourth parameter into the join table. This feature is discussed in `DDC-213`_ +This feature cannot be implemeted for One-To-Many associations, because they are never the owning side. + From c0cefa874947d02a1909cccfb25be00f4af1261b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 5 Feb 2011 13:48:12 +0100 Subject: [PATCH 177/430] Fixed TOCTree for tutorial on indexed associations. --- en/_exts/configurationblock.pyc | Bin 3386 -> 0 bytes en/index.rst | 1 + ...rst => working-with-indexed-associations.rst} | 6 +++--- 3 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 en/_exts/configurationblock.pyc rename en/tutorials/{working-with-indexed-assocations.rst => working-with-indexed-associations.rst} (98%) diff --git a/en/_exts/configurationblock.pyc b/en/_exts/configurationblock.pyc deleted file mode 100644 index bd1959c3a04f9ead6f17f187c2009b906f7dc47a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3386 zcmd5;UvC>l5TCvC|JX^I(n^6sB|yliYE55)K&8+>sIAmS)g=OI6`d}=wY|;x&b?iy zi4?yO9(mvcAifh{g)acVnM-`3YTi;*pKo?%cXoIF&;9jxr=@M{$$*Z3HH_ckwSNG_ z_$%5aN)+`KB_8!XQatO?u1`sY`jvwAX}3yAjrujv719+lb%6~UdK6dbSE3`LXZyd=mn#ce@WDDKc!cNqejfAH&iz+4mHzRC5# znLX`VEd2>(yNWrg0BIKMQ{-$q)wx^4!XQhBW;DwqXR>rB$p*U%lkn!dcx?kfLr6uz zRu?!GH}iBDrqM))plPsPkAPX*GWndow^v2ml_jYue9`ptfr#Br>d#`M&-Hwh@ z+spFF^*9^Yo=$tA-gg#Uugi90dJt4_);b@`>erNLaql$e-T!FegG;pd1xTTVhCh9F zGQ7yJN<34eG4MK#;R`s)qm39$6%1AO-3HtSXKlbw@Q@5k#n8q8mQFD&V}PetFd}0%lZ>0c-%jxpFVslG^{0z&+Z)WIEcW( zXKcSRBXRNzlfH;7hX9{;tt17z#*^Yp990s+%7^o%k2W94Xj0>rK;KEz%t>fiy8;?~ z*Q0W7z#+o-arWH9Yu7PzC<+=y#v|j?T(PRA%6TyN%GE=ULUlsc1gQ&b5SEU7QX_2e zY5z|Sa-LFj|L5Q7xkvK~&8vi%I|RhpOLXK>_L3A6Z8@aFYw+XxI*U_MV4T-v)3OMJ zEAhwmRT_Ep<-9IyaG=~bhb#>;tIUM!)uAs$Lx`8pN#Xs`U67L?W0O}|hBe*~=HFDY ztw|$=)MXQII?kIUN=LJ3q#bbTUIY*iS{Pdk12SPY(Ww){YUfNkvf>+^?gbqBt`6{h zqJ^ssJk{3eIGjc~_<0fe+4TpRhR}G!w1|)t_HH zk|09p$#fi9W98@}vNdv6&cTJzD37LNp%QR}*gSC@6cGwd8cWVfK<&(uM7xlgx*Rw# z7NSNc!{GfBic6K;0nu{!}@*c95S9t59& z4sHUJW)bW>Z4+?tUBPQV1*oa=@m2i|Ps<;0K=ZpOB%gs;6cR>|PpHaLm{FIHJjMAK z6S9gUU{m}Nn>`7GdynsIZCTDLn6zwf*Cm)G3&r?5S$q)iMMaXiD0eOzNplO>D8c6d zrJc^xMD9Zzy@#B0?DbMakuc<%`~(cr;z%v5!xoY<%S~iq9T;U{3+C9gZ2wz5SmSo9 z(B^AQh^GIKav$NkSR*UX)b1%bUe}KET(o0{if4Idvv2?__ST)ka-Zpx1eJ1s3mosB z+*cvh+4dH}e>f#Lo0VS<6EXN#K#Fieu|+sJkC0FN0SlzUtqJ&%@)R@G2>FKsjZS(h z{{rqPl6-RAnaS5+HU?1~hg_beHw3pZ6MVzqHiK^gqzy6G;(J!=O7TtWg8{weEhQ({ zXmrWh^cf>8EvYu1j@s<5f^eLF+@YOG>(qmM;N@B#1RxECGYWRBoIR!e;aJJ*WDYThb7;BQ|7Zn+IzR;%b?SN+R3n||^9 E3vF@wG5`Po diff --git a/en/index.rst b/en/index.rst index 3aca1abca..c02cdba0e 100644 --- a/en/index.rst +++ b/en/index.rst @@ -44,6 +44,7 @@ Tutorials tutorials/getting-started-xml-edition tutorials/working-with-indexed-associations + Cookbook -------- diff --git a/en/tutorials/working-with-indexed-assocations.rst b/en/tutorials/working-with-indexed-associations.rst similarity index 98% rename from en/tutorials/working-with-indexed-assocations.rst rename to en/tutorials/working-with-indexed-associations.rst index 5e82617d9..88ffe0261 100644 --- a/en/tutorials/working-with-indexed-assocations.rst +++ b/en/tutorials/working-with-indexed-associations.rst @@ -120,7 +120,7 @@ The code and mappings for the Market entity looks like this:
    - .. code-block:: yml + .. code-block:: yaml Doctrine\Tests\Models\StockExchange\Market: type: entity @@ -207,7 +207,7 @@ here are the code and mappings for it:
    - .. code-block:: yml + .. code-block:: yaml Doctrine\Tests\Models\StockExchange\Stock: type: entity @@ -289,6 +289,6 @@ Outlook into the Future ~~~~~~~~~~~~~~~~~~~~~~~ For the inverse side of a many-to-many associations there will be a way to persist the keys and the order -as a third and fourth parameter into the join table. This feature is discussed in `DDC-213`_ +as a third and fourth parameter into the join table. This feature is discussed in `DDC-213 `_ This feature cannot be implemeted for One-To-Many associations, because they are never the owning side. From 94244683b0cf5b54eb1fd5bb29f7d38b5dcd8153 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 5 Feb 2011 15:23:39 +0100 Subject: [PATCH 178/430] Fix typo in new indexed assocations tutorial, thanks @gordonslondon --- en/tutorials/working-with-indexed-associations.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/tutorials/working-with-indexed-associations.rst b/en/tutorials/working-with-indexed-associations.rst index 88ffe0261..2934c1a30 100644 --- a/en/tutorials/working-with-indexed-associations.rst +++ b/en/tutorials/working-with-indexed-associations.rst @@ -259,7 +259,7 @@ now query for the market: $market = $em->find("Doctrine\Tests\Models\StockExchange\Market", $marketId); // Access the stocks by symbol now: - $stock = $market->getSymbol($symbol); + $stock = $market->getStock($symbol); echo $stock->getSymbol(); // will print "AAPL" @@ -281,7 +281,7 @@ The same applies to DQL queries: The ``indexBy`` configuration acts as implicit ->getSingleResult(); // Access the stocks by symbol now: - $stock = $market->getSymbol($symbol); + $stock = $market->getStock($symbol); echo $stock->getSymbol(); // will print "AAPL" From 93207c081e9f9fede4d7aacbb15611b60354e4b9 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 5 Feb 2011 15:26:58 +0100 Subject: [PATCH 179/430] Add another paragraph with functionalities not described yet. --- en/tutorials/working-with-indexed-associations.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/en/tutorials/working-with-indexed-associations.rst b/en/tutorials/working-with-indexed-associations.rst index 2934c1a30..d218d62f5 100644 --- a/en/tutorials/working-with-indexed-associations.rst +++ b/en/tutorials/working-with-indexed-associations.rst @@ -285,6 +285,10 @@ The same applies to DQL queries: The ``indexBy`` configuration acts as implicit echo $stock->getSymbol(); // will print "AAPL" +If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally +indexed associations also work with the ``Collection::slice()`` functionality, no matter if marked as +LAZY or EXTRA_LAZY. + Outlook into the Future ~~~~~~~~~~~~~~~~~~~~~~~ From eea5b71da56eb904645055a38c4fb3b0b1955253 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 5 Feb 2011 16:14:51 +0100 Subject: [PATCH 180/430] Add tutorial on extra lazy associations --- en/index.rst | 1 + en/tutorials/extra-lazy-associations.rst | 86 +++++++++++++++++++ .../working-with-indexed-associations.rst | 2 +- 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 en/tutorials/extra-lazy-associations.rst diff --git a/en/index.rst b/en/index.rst index c02cdba0e..f689e552a 100644 --- a/en/index.rst +++ b/en/index.rst @@ -43,6 +43,7 @@ Tutorials tutorials/getting-started-xml-edition tutorials/working-with-indexed-associations + tutorials/extra-lazy-associations Cookbook diff --git a/en/tutorials/extra-lazy-associations.rst b/en/tutorials/extra-lazy-associations.rst new file mode 100644 index 000000000..b854f72c1 --- /dev/null +++ b/en/tutorials/extra-lazy-associations.rst @@ -0,0 +1,86 @@ +Extra Lazy Associations +======================= + +.. note:: + + This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series. + +In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. +where posts can be commented, you always have to assume that a post draws hundrets of comments. +In Doctrine 2.0 if you accessed an association it would always get loaded completly into memory. This +can lead to pretty serious performance problems, if your associations contain several hundrets or thousands +of entities. + +With Doctrine 2.1 a feature called **Extra Lazy** is introduced for associations. Associations +are marked as **Lazy** by default, which means the whole collection object for an association is populated +the first time its accessed. If you mark an association as extra lazy the following methods on collections +can be called without triggering a full load of the collection: + +- ``Collection#contains($entity)`` +- ``Collection#count()`` +- ``Collection#slice($offset, $length = null)`` + +For each of this three methods the following semantics apply: + +- For each call, if the Collection is not yet loaded, issue a straight SELECT statement against the database. +- For each call, if the collection is already loaded, fallback to the default functionality for lazy collections. No additional SELECT statements are executed. + +Additionally even with Doctrine 2.0 the following methods do not trigger the collection load: + +- ``Collection#add($entity)`` +- ``Collection#offsetSet($key, $entity)`` - ArrayAccess with no specific key ``$coll[] = $entity`, it does + not work when setting specific keys like ``$coll[0] = $entity``. + +With extra lazy collections you can now not only add entities to large collections but also paginate them +easily using a combination of ``count`` and ``slice``. + + +Enabling Extra-Lazy Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The mapping configuration is simple. Instead of using the default value of ``fetch="LAZY"`` you have to +switch to extra lazy as shown in these examples: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + .. code-block:: yaml + + Doctrine\Tests\Models\CMS\CmsGroup: + type: entity + # ... + manyToMany: + users: + targetEntity: CmsUser + mappedBy: groups + fetch: EXTRA_LAZY + diff --git a/en/tutorials/working-with-indexed-associations.rst b/en/tutorials/working-with-indexed-associations.rst index d218d62f5..ab48fea86 100644 --- a/en/tutorials/working-with-indexed-associations.rst +++ b/en/tutorials/working-with-indexed-associations.rst @@ -1,7 +1,7 @@ Working with Indexed Assocations ================================ -.. note: +.. note:: This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series. From a592dc116d0b7d37357c0bc7241ba89670fff321 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 5 Feb 2011 18:01:10 +0100 Subject: [PATCH 181/430] First part of a tutorial on composite primary keys and identity through foreign entities. --- en/index.rst | 1 + en/tutorials/composite-primary-keys.rst | 211 ++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 en/tutorials/composite-primary-keys.rst diff --git a/en/index.rst b/en/index.rst index f689e552a..4bfc72693 100644 --- a/en/index.rst +++ b/en/index.rst @@ -44,6 +44,7 @@ Tutorials tutorials/getting-started-xml-edition tutorials/working-with-indexed-associations tutorials/extra-lazy-associations + tutorials/composite-primary-keys Cookbook diff --git a/en/tutorials/composite-primary-keys.rst b/en/tutorials/composite-primary-keys.rst new file mode 100644 index 000000000..8048fbb78 --- /dev/null +++ b/en/tutorials/composite-primary-keys.rst @@ -0,0 +1,211 @@ +Composite Primary Keys +====================== + +Doctrine 2 supports composite primary keys natively. Composite keys are a very powerful relational database concept +and we took good care to make sure Doctrine 2 supports as many of the composite primary key use-cases. +For Doctrine 2.0 composite keys of primitive data-types are supported, for Doctrine 2.1 even foreign keys as +primary keys are supported. + +This tutorial shows how the semantics of composite primary keys work and how they map to the database. + +General Considerations +~~~~~~~~~~~~~~~~~~~~~~ + +Every entity with a composite key cannot use an id generator other than "ASSIGNED". That means +the ID fields have to have their values set before you call ``EntityManager#persist($entity)``. + +Primitive Types only +~~~~~~~~~~~~~~~~~~~~ + +Even in version 2.0 you can have composite keys as long as they only consist of the primative types +``integer`` and ``string``. Suppose you want to create a database of cars and use the model-name +and year of production as primary keys: + +.. configuration-block:: + + .. code-block:: php + + name = $name; + $this->year = $year; + } + + public function getModelName() + { + return $this->name; + } + + public function getYearOfProduction() + { + return $this->year; + } + } + + .. code-block:: xml + + + + + + + + + + + .. code-block:: yaml + + VehicleCatalogue\Model\Car: + type: entity + id: + name: + type: string + year: + type: integer + +Now you can use this entity: + +.. code-block:: php + + persist($car); + $em->flush(); + +And for querying you can use arrays to both DQL and EntityRepositories: + +.. code-block:: php + + find("VehicleCatalogue\Model\Car", array("name" => "Audi A8", "year" => 2010)); + + $dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1"; + $audi = $em->createQuery($dql) + ->setParameter(1, array("name" => "Audi A8", "year" => 2010)) + ->getSingleResult(); + +You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name`` +and to ``year`` to the related entities. + +.. note:: + + This example shows how you can nicely solve the requirement for exisiting + values before ``EntityManager#persist()``: By adding them as mandatory values for the constructor. + +Identity through foreign Entities +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. note:: + + Identity through foreign entities is only supported with Doctrine 2.1 + +There are tons of use-cases where the identity of an Entity should be determined by the entity +of one or many parent entities. + +- Dynamic Attributes of an Entity (for example Article). Each Article has many + attributes with primary key "article_id" and "attribute_name". +- Address object of a Person, the primary key of the adress is "user_id". This is not a case of a composite primary + key, but the identity is derived through a foreign entity and a foreign key. +- Join Tables with metadata can be modelled as Entity, for example connections between two articles + with a little description and a score. + +The semantics of mapping identity through foreign entities are easy: + +- Only allowed on Many-To-One or One-To-One associations. +- Plug an ``@Id`` annotation onto every assocation. +- Set an attribute ``association-key`` with the field name of the association in XML. +- Set a key ``associationKey:`` with the field name of the association in YAML. + +Use-Case 1: Dynamic Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We keep up the example of an Article with arbitrary attributes, the mapping looks like this: + +.. code-block:: php + + attributes[$name] = new ArticleAttribute($name, $value, $this); + } + } + + /** + * @Entity + */ + class ArticleAttribute + { + /** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */ + private $article; + + /** @Id @Column(type="string") */ + private $attribute; + + /** @Column(type="string") */ + private $value; + + public function __construct($name, $value, $article) + { + $this->attribute = $name; + $this->value = $value; + $this->article = $article; + } + } + + +Use-Case 2: Simple Derived Identity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +TODO + +Use-Case 3: Join-Table with Metadata +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +TODO + +Performance Considerations +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +TODO \ No newline at end of file From b79464373501d93ee9f82bbc843d7f11637c17e9 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 5 Feb 2011 18:08:39 +0100 Subject: [PATCH 182/430] Updated Limitations and Known Issues Chapter --- en/reference/limitations-and-known-issues.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/en/reference/limitations-and-known-issues.rst b/en/reference/limitations-and-known-issues.rst index 3e98ef3ff..0dc4fb9cb 100644 --- a/en/reference/limitations-and-known-issues.rst +++ b/en/reference/limitations-and-known-issues.rst @@ -20,6 +20,10 @@ releases. Foreign Keys as Identifiers ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. note:: + + Foreign keys as identifiers are currently in master and will be included in Doctrine 2.1 + 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: @@ -222,6 +226,10 @@ benefit from custom persister implementations: Paginating Associations ~~~~~~~~~~~~~~~~~~~~~~~ +.. note:: + + Extra Lazy Collections are currently in master and will be included in Doctrine 2.1 + It is not possible to paginate only parts of an associations at the moment. You can always only load the whole association/collection into memory. This is rather problematic for large collections, but we already plan to add facilities to fix this for Doctrine 2.1 From 6309710cccfaa49aa67d5cd21edf247a70a791b2 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Feb 2011 12:47:51 +0100 Subject: [PATCH 183/430] Fix some glitches in the docs: --- en/reference/introduction.rst | 3 ++- en/reference/tools.rst | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst index fd19d0407..69bfbd085 100644 --- a/en/reference/introduction.rst +++ b/en/reference/introduction.rst @@ -134,7 +134,8 @@ the ``ClassLoader`` with the following code. register(); The packages are installed in to your shared PEAR PHP code folder in a folder named ``Doctrine``. You also get a nice command line diff --git a/en/reference/tools.rst b/en/reference/tools.rst index 5448e4a4c..74cf6246a 100644 --- a/en/reference/tools.rst +++ b/en/reference/tools.rst @@ -53,7 +53,7 @@ dealing exclusively with DBAL, the ConnectionHelper is required: .. code-block:: php new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn) )); $cli->setHelperSet($helperSet); @@ -64,7 +64,7 @@ required: .. code-block:: php new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); $cli->setHelperSet($helperSet); @@ -97,7 +97,7 @@ typical ``cli-config.php`` file looks as follows: $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); - $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( + $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array( 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); From 24defe71004b3fc34ed93af1002c96f731b7c867 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Feb 2011 12:57:04 +0100 Subject: [PATCH 184/430] Add notes to Tools about EntityGenerator, ConvertMapping and Reverse Engineering --- en/reference/tools.rst | 63 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/en/reference/tools.rst b/en/reference/tools.rst index 74cf6246a..2e26ba0e4 100644 --- a/en/reference/tools.rst +++ b/en/reference/tools.rst @@ -286,10 +286,64 @@ your cli-config.php properly. (or mapping files), i.e. ``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);`` +Entity Generation +----------------- + +Generate entity classes and method stubs from your mapping information. + +.. code-block:: php + + $ php doctrine orm:generate-entities + $ php doctrine orm:generate-entities --update-entities + $ php doctrine orm:generate-entities --regenerate-entities + +This command is not suited for constant usage. It is a little helper and does +not support all the mapping edge cases very well. You still have to put work +in your entities after using this command. + +It is possible to use the EntityGenerator on code that you have already written. It will +not be lost. The EntityGenerator will only append new code to your +file and will not delete the old code. However this approach may still be prone +to error and we suggest you use code repositories such as GIT or SVN to make +backups of your code. + +It makes sense to generate the entity code if you are using entities as Data +Access Objects only and dont put much additional logic on them. If you are +however putting much more logic on the entities you should refrain from using +the entity-generator and code your entities manually. + +.. note:: + + Even if you specified Inheritance options in your + XML or YAML Mapping files the generator cannot generate the base and + child classes for you correctly, because it doesn't know which + class is supposed to extend which. You have to adjust the entity + code manually for inheritance to work! + Convert Mapping Information --------------------------- +Convert mapping information between supported formats. + +This is an **execute one-time** command. It should not be necessary for +you to call this method multiple times, escpecially when using the ``--from-database`` +flag. + +Converting an existing databsae schema into mapping files only solves about 70-80% +of the necessary mapping information. Additionally the detection from an existing +database cannot detect inverse associations, inheritance types, +entities with foreign keys as primary keys and many of the +semantical operations on associations such as cascade. + +.. note:: + + There is no need to convert YAML or XML mapping files to annotations + every time you make changes. All mapping drivers are first class citizens + in Doctrine 2 and can be used as runtime mapping for the ORM. See the + docs on XML and YAML Mapping for an example how to register this metadata + drivers as primary mapping source. + To convert some mapping information between the various supported formats you can use the ``ClassMetadataExporter`` to get exporter instances for the different formats: @@ -335,6 +389,15 @@ You can use the ``DatabaseDriver`` to reverse engineer a database to an array of ``ClassMetadataInfo`` instances and generate YAML, XML, etc. from them. +.. note:: + + Reverse Engineering is a **one-time** process that can get you started with a project. + Converting an existing database schema into mapping files only detects about 70-80% + of the necessary mapping information. Additionally the detection from an existing + database cannot detect inverse associations, inheritance types, + entities with foreign keys as primary keys and many of the + semantical operations on associations such as cascade. + First you need to retrieve the metadata instances with the ``DatabaseDriver``: From 1d24cb48282de7070c83f15c0c19a8a2d57a6645 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Sun, 13 Feb 2011 16:24:52 -0600 Subject: [PATCH 185/430] Removing indices and tables as they are just broken links and don't do anything. --- en/index.rst | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/en/index.rst b/en/index.rst index 4bfc72693..33db4eca6 100644 --- a/en/index.rst +++ b/en/index.rst @@ -63,12 +63,4 @@ Cookbook cookbook/sql-table-prefixes cookbook/strategy-cookbook-introduction cookbook/validation-of-entities - cookbook/working-with-datetime - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - + cookbook/working-with-datetime \ No newline at end of file From 35dae4e1f46add253cd8c2d5879a397bb3e632de Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 15 Feb 2011 21:31:20 +0100 Subject: [PATCH 186/430] Add note about columnDefinition SchemaTool. --- en/reference/annotations-reference.rst | 7 +++++-- en/reference/introduction.rst | 11 +++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst index b918e85d3..363058944 100644 --- a/en/reference/annotations-reference.rst +++ b/en/reference/annotations-reference.rst @@ -80,11 +80,14 @@ Optional attributes: name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. However you should make careful use of this feature and the - consequences. Additionally you should remember that the "type" + consequences. SchemaTool will not detect changes on the column correctly + anymore if you use "columnDefinition". + + Additionally you should remember that the "type" attribute still handles the conversion between PHP and Database values. If you use this attribute on a column that is used for joins between tables you should also take a look at - :ref:`@JoinColumn `. + :ref:`@JoinColumn `. Examples: diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst index 69bfbd085..e832766c8 100644 --- a/en/reference/introduction.rst +++ b/en/reference/introduction.rst @@ -117,14 +117,17 @@ it with the following command. $ sudo pear install pear.doctrine-project.org/DoctrineORM- - **NOTE** The ```` tag above represents the version you - want to install. For example the current version at the time of - writing this is ``2.0.0BETA3`` for the ORM, so you could install it + +.. note:: + + The ```` tag above represents the version you + want to install. For example if the current version at the time of + writing this is ``2.0.7`` for the ORM, so you could install it like the following: .. code-block:: bash - $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.0BETA3 + $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.7 When you have a package installed via PEAR you can require and load From 755f0b3dbbc4f22bd62f52ab2754be9c9b3f90f7 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 15 Feb 2011 21:33:24 +0100 Subject: [PATCH 187/430] Add section about columnDefinition into basic mapping. --- en/reference/basic-mapping.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst index 95a1d8c4d..ca2cbb101 100644 --- a/en/reference/basic-mapping.rst +++ b/en/reference/basic-mapping.rst @@ -317,6 +317,17 @@ instance for Schema representation. Keep in mind that you can easily produce clashes this way, each database type can only map to exactly one Doctrine mapping type. +Custom ColumnDefinition +----------------------- + +You can define a custom definition for each column using the "columnDefinition" +attribute of ``@Column``. You have to define all the definitions that follow +the name of a column here. + +.. note:: + + Using columnDefinition will break change-detection in SchemaTool. + Identifiers / Primary Keys -------------------------- From 9a289e29824324446c143a671a514bfaa56398bb Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 20 Feb 2011 11:00:39 +0100 Subject: [PATCH 188/430] Adopt new Sphinx layout --- en/_static/configurationblock.css | 10 +- en/_static/default.css | 960 ++++++++++++++++++++++++++++++ en/_static/pygments.css | 70 +++ en/_templates/layout.html | 122 ++-- 4 files changed, 1122 insertions(+), 40 deletions(-) create mode 100644 en/_static/default.css create mode 100644 en/_static/pygments.css diff --git a/en/_static/configurationblock.css b/en/_static/configurationblock.css index 4f7043148..c1f987be7 100644 --- a/en/_static/configurationblock.css +++ b/en/_static/configurationblock.css @@ -1,13 +1,17 @@ div.configuration-block ul.simple { - padding: 5px 0 0 0; + margin: 0; + padding: 0; + margin-left: 30px; } div.configuration-block ul.simple li { - margin: 0; - margin-right: 5px; + margin: 0 !important; + margin-right: 5px !important; display: inline; + margin-left: 10px; + padding: 10px; } div.configuration-block em diff --git a/en/_static/default.css b/en/_static/default.css new file mode 100644 index 000000000..a65dc8057 --- /dev/null +++ b/en/_static/default.css @@ -0,0 +1,960 @@ +/* + * default.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- default theme. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +div.sphinxsidebar { + width: 300px; +} + +body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td,button { margin:0; padding:0; } +ul,li { list-style-type:none; } +select { min-width: 1.5em;} +select > option { padding: 0 2px 0 3px; } +form { margin: 0; padding: 0; } +img { border: 0; } +hr { clear:both; display: none; } +label { display: none; } +fieldset { border: 0; } +q:before,q:after { content:''; } +abbr,acronym { border:0; } + +.reset div, +.reset dl, +.reset dt, +.reset dd, +.reset ul, +.reset ol, +.reset li, +.reset h1, +.reset h2, +.reset h3, +.reset h4, +.reset h5, +.reset h6, +.reset pre, +.reset code, +.reset form, +.reset fieldset, +.reset legend, +.reset input, +.reset textarea, +.reset p, +.reset blockquote, +.reset th, +.reset td{margin:0 !important;padding:0 !important;} +.reset table{border-collapse:collapse !important;border-spacing:0 !important;} +.reset fieldset,.reset img{border:0 !important;} +.reset address, +.reset caption, +.reset cite, +.reset code, +.reset dfn, +.reset em, +.reset strong, +.reset th, +.reset var{font-style:normal !important;font-weight:normal !important;} +.reset li{list-style:none !important;} +.reset caption,.reset th{text-align:left !important;} +.reset h1,.reset h2,.reset h3,.reset h4,.reset h5,.reset h6{font-size:100% !important;font-weight:normal !important;} +.reset q:before,.reset q:after{content:'' !important;} +.reset abbr,.reset acronym {border:0 !important;font-variant:normal !important;} +/* to preserve line-height and selector appearance */ +.reset sup {vertical-align:text-top !important;} +.reset sub {vertical-align:text-bottom !important;} +.reset input,.reset textarea,.reset select{font-family:inherit !important;font-size:inherit !important;font-weight:inherit !important;} +/*to enable resizing for IE*/ +.reset input,.reset textarea,.reset select{*font-size:100% !important;} +/*because legend doesn't inherit in IE */ +.reset legend{color:#000 !important;} + +/* -- page layout ----------------------------------------------------------- */ + +div.document { + margin-right: 30px; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 300px; +} + +div.body { + background-color: #ffffff; + color: #000000; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #ffc; + width: 100%; + font-size: 90%; + margin-top: -40px; + height: 35px; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + float: left; + font-weight: bold; +} + +div.related li.right { + float: right; + margin-right: 5px !important; + margin-left: 5px !important; +} +div.sphinxsidebar { + background: #f2f2f2; +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #000000; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; + margin-left: 0 !important; +} + +div.sphinxsidebar h3 a { + color: #000000; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #000000; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; + margin-left: 0 !important; +} + +div.sphinxsidebar p { + color: #000000; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 0; + padding: 0; + color: #000000; +} + +div.sphinxsidebar ul li { + margin-left: 5px !important; +} + +div.sphinxsidebar a { + color: #000000; + font-weight: bold; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + text-align: justify; + line-height: 130%; +} + +div.highlight { + margin-left: 30px; +} + +div.highlight pre { + margin-left: 0; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border-left: none; + border-right: none; + margin-left: 30px; +} + +tt { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 1.4em; +} + +th { + background-color: #ede; +} + +.warning tt { + background: #efc2c2; +} + +.note tt { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +img +{ + vertical-align: middle; +} + +.js_active +{ + cursor: pointer; + display: inline-block; + background: url(/images/btn-open.png) no-repeat 550px center; +} + +ul.error_list +{ + margin: 0; + list-style: none; + color: #f22; +} + +ul.error_list li +{ + list-style: none; +} + +pre +{ + padding: 0.7em; + background-color: #000; + line-height: 1.3em; + font-size: 100%; + color: #fff; +} + +pre code +{ + background-color: #000; +} + +pre.command-line +{ + background-color: #333; + color: #eee; + padding-bottom: 10px; +} + +pre.command-line code +{ + background-color: #333; +} + +blockquote +{ + padding: 2px 20px 5px 45px; + margin: 15px 0; + background-color: #fff; +} + +div.admonition +{ + padding: 2px 20px 5px 45px; + background-color: #fff; + margin-left: 30px; +} + +div.note +{ + background: #fff url(http://www.doctrine-project.org/images/note.png) no-repeat -5px -5px; +} + +div.caution +{ + background: #fff url(http://www.doctrine-project.org/images/caution.png) no-repeat -5px -5px; +} + +div.tip +{ + background: #fff url(http://www.doctrine-project.org/images/tip.png) no-repeat -5px -5px; +} + +div.seealso +{ + background: #fff url(http://www.doctrine-project.org/images/tip.png) no-repeat -5px -5px; +} + +/* Note/Caution/Tip Boxes */ + +/* Note */ + +div.note { + padding: 5px 20px 5px 45px; + margin: 10px 0; + background: #ffd url(http://www.doctrine-project.org/images/note.png) no-repeat 5px 10px; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} + +/* Caution */ + +div.caution { + padding: 5px 20px 5px 45px; + margin: 10px 0; + background: #ffd url(http://www.doctrine-project.org/images/caution.png) no-repeat 5px 10px; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} + +/* Tip */ + +div.tip { + padding: 5px 20px 5px 45px; + margin: 10px 0; + background: #ffd url(http://www.doctrine-project.org/images/tip.png) no-repeat 5px 10px; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} + +/* Sidebar */ + +div.seealso { + margin: 10px 0; + padding: 10px; + background: #ccc; +} + +div.seealso ul { + margin: 10px; +} + +div.seealso p.title { + margin: -20px; + margin-bottom: 10px !important; + padding: 10px !important; + background: #aaa; + color: #fff; + font-weight: bold; +} + +/* Shared Styles */ +div.note, div.caution, div.tip, div.seealso { + overflow/**/: auto; + margin-left: 30px; + font-weight: normal; + min-height: 40px; +} + +div.note p, div.caution p, div.tip p, div.seealso p { + margin: 0 !important; + padding: 0 !important; + padding-top: 10px; +} + +.admonition-title +{ + display: none !important; +} + +blockquote.quote +{ + background: #D7CABA; +} + +blockquote.sidebar +{ + padding: 10px; + background: #eee; +} + +blockquote.sidebar p.title +{ + margin: -10px; + margin-bottom: 10px; + padding: 10px; + background: #ddd; + font-style: italic; +} + +.navigation +{ + font-family: Arial, sans-serif; + padding: 10px 15px; + font-size: 0.9em; + text-align:center; + background-color: #e3e3e3; + border: 1px solid #e3e3e3; + -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; +} + +.navigation a +{ + text-decoration: none; +} + +.navigation a:hover +{ + text-decoration: underline; +} + +.navigation .separator +{ + padding: 0 10px; + color: #ccc; +} + +.feedback p +{ + font-family: Arial, sans-serif; + color: #858585; + font-size: 0.8em; +} + +.feedback h3 +{ + border-top: 1px solid #ddd; + font-family: Georgia, "Times New Roman", serif; + margin-top: 10px; + padding-top: 20px; + color: #858585; +} + +.toc +{ + margin-top: 10px; + padding: 10px; + background-color: #f1f1f1; + border: 1px solid #e3e3e3; + -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; + font-family: Arial, sans-serif; +} + +.toc h2 +{ + margin: 0; + padding: 0; +} + +.pages +{ + padding: 10px 0 0 0; +} + +.pages ul.inline +{ + display: inline; + padding: 5px 0 0 0; +} + +.pages .inline li +{ + display: inline; + margin: 0 5px; +} + +#quick-tour ul.simple +{ + padding: 5px 0 0 0; +} + +#quick-tour ul.simple li +{ + margin: 0; + margin-right: 5px; + display: inline; +} + +.toc a +{ + text-decoration: none; + color: #777; +} + +.toc a:hover +{ + text-decoration: underline; +} + +.bd .content .toc li +{ + padding: 2px; + list-style: square; + margin-left: 15px; +} + +.bd .content .toc li.current +{ + font-weight: bold; + background-color: #e3e3e3; +} + +.bd .content .toc ul.inline +{ + padding: 0; + margin: 0; + margin-left: 3px; +} + +.bd .content .toc .inline li +{ + margin: 0; + padding: 0; +} + +.bd .content .toc li.separator +{ + color: #ccc; +} + +#release_info +{ + background-color: #e3e3e3; + border: 1px solid #e3e3e3; + margin-bottom: 15px; + -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; + width: 290px; +} + +#license +{ + margin-top: 40px; + line-height: 1.2em; + font-size: 80%; +} + +#license img +{ + margin-right: 5px; +} + +table.docutils +{ + margin-bottom: 10px; +} + +table.docutils th +{ + font-weight:bold; + background-color: #efefef; +} + +table.docutils td, table.docutils th +{ + padding: 4px 6px; + border: 0; + border-bottom: 1px solid #ddd; + text-align: left; + vertical-align: top; +} + +.menu +{ + float: right; + width: 300px; + margin: 15px; + font-size: 0.7em; + background-color: #fff; + position: relative; + z-index: 999; +} + +#searchform +{ + display: inline; +} + +#search +{ + -webkit-appearance: searchfield; +} + +span.pre +{ + font-size: 85%; +} + +.bd .content .navigation li +{ + margin-left: 0; +} + +a.headerlink +{ + padding: 2px; + color: #ddd; + text-decoration: none; + font-size: 80%; +} + +a.reference em, a.internal em +{ + font-style: normal; +} + +#guides ul ul, #contributing ul ul +{ + display: inline; + padding: 5px 0 0 0; +} + +#guides ul ul li, #contributing ul ul li +{ + display: inline; + margin: 0; +} + +.sidebarbox +{ + margin-top: 10px; + padding: 10px; + background-color: #f1f1f1; + border: 1px solid #e3e3e3; + -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; + font-family: Arial, sans-serif; +} + +.sidebarbox h2 +{ + margin: 0; + padding: 0; +} + +.sidebarbox h3 +{ + margin: 0; + padding: 0; + margin-top: 5px; +} + +#searchbox h3 a.nolink +{ + padding: 0; + text-decoration: none; +} + +#searchbox h3 a.nolink:hover +{ + text-decoration: underline; +} + +div.breadcrumb h3 +{ + display: none; +} + +.bd .content div.breadcrumb ul +{ + margin: 0; + padding: 0; + list-style: none; + margin-top: 5px; +} + +.bd .content div.breadcrumb li +{ + display: inline; + margin: 0; + padding: 0; + line-height: 0.9em; +} + +.bd .content div.breadcrumb li a +{ + color: #777; + text-decoration: none; +} + +.bd .content div.breadcrumb li a:hover +{ + text-decoration: underline; +} + +.p-Indicator +{ + color: #FF8400; +} + +.bd .content ul.search li +{ + margin-left: 0; + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +div.genindex-jumpbox +{ + font-size: 85%; + border: 0; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox +{ + color: #999; +} + +div.genindex-jumpbox strong +{ + font-weight: normal; +} + +div.genindex-jumpbox a +{ + padding: 0 4px; +} + +h2#A, h2#B, h2#C, h2#D, h2#E, h2#F, h2#G, h2#H, h2#I, h2#J, h2#K, h2#L, h2#M, h2#N, h2#O, +h2#P, h2#Q, h2#R, h2#S, h2#T, h2#U, h2#V, h2#W, h2#X, h2#Y, h2#Z +{ + background-color: #eee; + border-bottom: 1px solid #aaa; + font-size: 120%; + font-weight: bold; + margin: 20px 0; + padding: 5px; +} + +.indextable a, div.genindex-jumpbox a +{ + text-decoration: none; +} + +.indextable a:hover, div.genindex-jumpbox a:hover +{ + text-decoration: underline; +} + +div.configuration-block em +{ + margin-bottom: 10px; +} + +.bd .content div.configuration-block li +{ + padding: 5px; +} + +.bd .content div.configuration-block em +{ + font-style: normal; + font-size: 90%; +} + +div.jsactive +{ + position: relative; +} + +.bd .content div.jsactive ul +{ + list-style: none; +} + +.bd .content div.jsactive li +{ + float: left; + list-style: none; + margin-left: 0; + -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; + background-color: #ddd; + margin-right: 5px; +} + +.bd .content div.jsactive .selected +{ + background-color: #000; +} + +.bd .content div.jsactive .selected a +{ + color: #fff; + text-decoration: none; +} + +.bd .content div.jsactive .selected a:hover +{ + color: #fff; + text-decoration: underline; +} + +.bd .content div.jsactive a +{ + color: #000; + text-decoration: none; +} + +.bd .content div.jsactive a:hover +{ + color: #000; + text-decoration: underline; +} + +div.jsactive div +{ + position: absolute; + top: 30px; + left: 0; +} + +div.jsactive div div +{ + position: static; +} + +div.jsactive pre +{ + margin: 0; +} + +.highlight +{ + overflow: auto; + margin-bottom: 10px; +} \ No newline at end of file diff --git a/en/_static/pygments.css b/en/_static/pygments.css new file mode 100644 index 000000000..1247614e5 --- /dev/null +++ b/en/_static/pygments.css @@ -0,0 +1,70 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #000000; } +.highlight .c { color: #B729D9; font-style: italic } /* Comment */ +.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ +.highlight .g { color: #ffffff } /* Generic */ +.highlight .k { color: #FF8400 } /* Keyword */ +.highlight .l { color: #ffffff } /* Literal */ +.highlight .n { color: #ffffff } /* Name */ +.highlight .o { color: #E0882F } /* Operator */ +.highlight .x { color: #ffffff } /* Other */ +.highlight .p { color: #999999 } /* Punctuation */ +.highlight .cm { color: #B729D9; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #a0a0a0 } /* Comment.Preproc */ +.highlight .c1 { color: #B729D9; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #B729D9; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #a40000 } /* Generic.Deleted */ +.highlight .ge { color: #ffffff; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #ef2929 } /* Generic.Error */ +.highlight .gh { color: #000080 } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #808080 } /* Generic.Output */ +.highlight .gp { color: #745334 } /* Generic.Prompt */ +.highlight .gs { color: #ffffff; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ +.highlight .kc { color: #004461 } /* Keyword.Constant */ +.highlight .kd { color: #004461 } /* Keyword.Declaration */ +.highlight .kn { color: #004461 } /* Keyword.Namespace */ +.highlight .kp { color: #004461 } /* Keyword.Pseudo */ +.highlight .kr { color: #004461 } /* Keyword.Reserved */ +.highlight .kt { color: #004461 } /* Keyword.Type */ +.highlight .ld { color: #ffffff } /* Literal.Date */ +.highlight .m { color: #1299DA } /* Literal.Number */ +.highlight .s { color: #56DB3A } /* Literal.String */ +.highlight .na { color: #ffffff } /* Name.Attribute */ +.highlight .nb { color: #ffffff } /* Name.Builtin */ +.highlight .nc { color: #ffffff } /* Name.Class */ +.highlight .no { color: #ffffff } /* Name.Constant */ +.highlight .nd { color: #808080 } /* Name.Decorator */ +.highlight .ni { color: #ce5c00 } /* Name.Entity */ +.highlight .ne { color: #cc0000 } /* Name.Exception */ +.highlight .nf { color: #ffffff } /* Name.Function */ +.highlight .nl { color: #f57900 } /* Name.Label */ +.highlight .nn { color: #ffffff } /* Name.Namespace */ +.highlight .nx { color: #ffffff } /* Name.Other */ +.highlight .py { color: #ffffff } /* Name.Property */ +.highlight .nt { color: #cccccc } /* Name.Tag */ +.highlight .nv { color: #ffffff } /* Name.Variable */ +.highlight .ow { color: #E0882F } /* Operator.Word */ +.highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ +.highlight .mf { color: #1299DA } /* Literal.Number.Float */ +.highlight .mh { color: #1299DA } /* Literal.Number.Hex */ +.highlight .mi { color: #1299DA } /* Literal.Number.Integer */ +.highlight .mo { color: #1299DA } /* Literal.Number.Oct */ +.highlight .sb { color: #56DB3A } /* Literal.String.Backtick */ +.highlight .sc { color: #56DB3A } /* Literal.String.Char */ +.highlight .sd { color: #B729D9; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #56DB3A } /* Literal.String.Double */ +.highlight .se { color: #56DB3A } /* Literal.String.Escape */ +.highlight .sh { color: #56DB3A } /* Literal.String.Heredoc */ +.highlight .si { color: #56DB3A } /* Literal.String.Interpol */ +.highlight .sx { color: #56DB3A } /* Literal.String.Other */ +.highlight .sr { color: #56DB3A } /* Literal.String.Regex */ +.highlight .s1 { color: #56DB3A } /* Literal.String.Single */ +.highlight .ss { color: #56DB3A } /* Literal.String.Symbol */ +.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #ffffff } /* Name.Variable.Class */ +.highlight .vg { color: #ffffff } /* Name.Variable.Global */ +.highlight .vi { color: #ffffff } /* Name.Variable.Instance */ +.highlight .il { color: #1299DA } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/en/_templates/layout.html b/en/_templates/layout.html index 447c36b64..5a5d2f448 100644 --- a/en/_templates/layout.html +++ b/en/_templates/layout.html @@ -104,6 +104,7 @@ {{ title|striptags }}{{ titlesuffix }} + {%- if not embedded %} + -{%- block footer %} - -{%- endblock %} - + \ No newline at end of file From 654dbb1b1018ded810561037c499f8790e5d68ea Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 20 Feb 2011 11:34:59 +0100 Subject: [PATCH 189/430] Fix
     tag styling having a very small
     line-height.
    
    ---
     en/_static/default.css | 12 +-----------
     1 file changed, 1 insertion(+), 11 deletions(-)
    
    diff --git a/en/_static/default.css b/en/_static/default.css
    index a65dc8057..83d866b1b 100644
    --- a/en/_static/default.css
    +++ b/en/_static/default.css
    @@ -306,16 +306,6 @@ p.admonition-title:after {
         content: ":";
     }
     
    -pre {
    -    padding: 5px;
    -    background-color: #eeffcc;
    -    color: #333333;
    -    line-height: 120%;
    -    border-left: none;
    -    border-right: none;
    -    margin-left: 30px;
    -}
    -
     tt {
         background-color: #ecf0f3;
         padding: 0 1px 0 1px;
    @@ -372,7 +362,7 @@ pre
     {
       padding: 0.7em;
       background-color: #000;
    -  line-height: 1.3em;
    +  line-height: 1.3em !important;
       font-size: 100%;
       color: #fff;
     }
    
    From c8c4c4ed464568c5431cb8a41bfd930f9754665f Mon Sep 17 00:00:00 2001
    From: "Jonathan H. Wage" 
    Date: Sun, 20 Feb 2011 11:12:51 -0600
    Subject: [PATCH 190/430] Updating css.
    
    ---
     en/_static/default.css | 28 +++++++++++++++++++---------
     1 file changed, 19 insertions(+), 9 deletions(-)
    
    diff --git a/en/_static/default.css b/en/_static/default.css
    index 83d866b1b..dd2266c38 100644
    --- a/en/_static/default.css
    +++ b/en/_static/default.css
    @@ -306,6 +306,16 @@ p.admonition-title:after {
         content: ":";
     }
     
    +pre {
    +    padding: 5px;
    +    background-color: #eeffcc;
    +    color: #333333;
    +    line-height: 120%;
    +    border-left: none;
    +    border-right: none;
    +    margin-left: 30px;
    +}
    +
     tt {
         background-color: #ecf0f3;
         padding: 0 1px 0 1px;
    @@ -408,6 +418,11 @@ div.caution
       background: #fff url(http://www.doctrine-project.org/images/caution.png) no-repeat -5px -5px;
     }
     
    +div.warning
    +{
    +  background: #fff url(http://www.doctrine-project.org/images/caution.png) no-repeat -5px -5px;
    +}
    +
     div.tip
     {
       background: #fff url(http://www.doctrine-project.org/images/tip.png) no-repeat -5px -5px;
    @@ -426,18 +441,14 @@ div.note {
       padding: 5px 20px 5px 45px;
       margin: 10px 0;
       background: #ffd url(http://www.doctrine-project.org/images/note.png) no-repeat 5px 10px;
    -  border-top: 1px solid #ddd;
    -  border-bottom: 1px solid #ddd;
     }
     
     /* Caution */
     
    -div.caution {
    +div.caution, div.warning {
       padding: 5px 20px 5px 45px;
       margin: 10px 0;
       background: #ffd url(http://www.doctrine-project.org/images/caution.png) no-repeat 5px 10px;
    -  border-top: 1px solid #ddd;
    -  border-bottom: 1px solid #ddd;
     }
     
     /* Tip */
    @@ -446,8 +457,6 @@ div.tip {
       padding: 5px 20px 5px 45px;
       margin: 10px 0;
       background: #ffd url(http://www.doctrine-project.org/images/tip.png) no-repeat 5px 10px;
    -  border-top: 1px solid #ddd;
    -  border-bottom: 1px solid #ddd;
     }
     
     /* Sidebar */
    @@ -472,14 +481,15 @@ div.seealso p.title {
     }
     
     /* Shared Styles */
    -div.note, div.caution, div.tip, div.seealso {
    +div.note, div.caution, div.tip, div.seealso, div.warning {
       overflow/**/: auto;
       margin-left: 30px;
       font-weight: normal;
       min-height: 40px;
    +  border: 1px solid #ddd;
     }
     
    -div.note p, div.caution p, div.tip p, div.seealso p {
    +div.note p, div.caution p, div.tip p, div.seealso p, div.warning p {
       margin: 0 !important;
       padding: 0 !important;
       padding-top: 10px;
    
    From aab2303c373336d060af1526a2c9568244e5adf4 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 26 Feb 2011 11:12:58 +0100
    Subject: [PATCH 191/430] Fix Getting Started.
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 11 +++++------
     1 file changed, 5 insertions(+), 6 deletions(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 430a6756a..093386159 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -455,7 +455,7 @@ We then go on specifying the definition of a Bug:
                 /**
                  * @ManyToMany(targetEntity="Product")
                  */
    -            private $products = null; // Set private for encapsulation
    +            private $products;
             }
     
         .. code-block:: xml
    @@ -521,14 +521,13 @@ After the field definitions the two qualified references to the
     user entity are defined. They are created by the ``many-to-one``
     tag. The class name of the related entity has to be specified with
     the ``target-entity`` attribute, which is enough information for
    -the database mapper to access the foreign-table. The
    -``join-column`` tags are used to specify how the foreign and
    -referenced columns are named, an information Doctrine needs to
    -construct joins between those two entities correctly. Since
    +the database mapper to access the foreign-table. Since
     ``reporter`` and ``engineer`` are on the owning side of a
     bi-directional relation we also have to specify the ``inversed-by``
     attribute. They have to point to the field names on the inverse
    -side of the relationship.
    +side of the relationship. We will see in the next example that the ``inversed-by``
    +attribute has a counterpart ``mapped-by`` which makes that
    +the inverse side.
     
     The last missing property is the ``Bug::$products`` collection. It
     holds all products where the specific bug is occurring in. Again
    
    From e1c2084eeb6aab347cac93dceec8071b9cc293a6 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 26 Feb 2011 11:51:30 +0100
    Subject: [PATCH 192/430] Add section on EntityRepository into Getting Started
     Tutorial.
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 149 ++++++++++++++++++-
     1 file changed, 145 insertions(+), 4 deletions(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 093386159..a8b435b8c 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -1151,13 +1151,154 @@ entity is scheduled for an UPDATE against the database. Only the
     changed columns are updated, which offers a pretty good performance
     improvement compared to updating all the properties.
     
    +Entity Repositories
    +-------------------
    +
    +For now we have not discussed how to separate the Doctrine query logic from your model.
    +In Doctrine 1 there was the concept of ``Doctrine_Table`` instances for this
    +seperation. The similar concept in Doctrine2 is called Entity Repositories, integrating
    +the `repository pattern `_ at the heart of Doctrine.
    +
    +Every Entity uses a default repository by default and offers a bunch of convenience
    +methods that you can use to query for instances of that Entity. Take for example
    +our Product entity. If we wanted to Query by name, we can use:
    +
    +.. code-block:: php
    +
    +    getRepository('Product')
    +                             ->findOneBy(array('name' => $productName));
    +
    +The method ``findOneBy()`` takes an array of fields or association keys and the values to match against.
    +
    +If you want to find all entities matching a condition you can use ``findBy()``, for
    +example querying for all closed bugs:
    +
    +.. code-block:: php
    +
    +    getRepository('Bug')
    +                          ->findBy(array('status' => 'CLOSED'));
    +
    +    foreach ($bugs AS $bug) {
    +        // do stuff
    +    }
    +
    +Compared to DQL these query methods are falling short of functionality very fast.
    +Doctrine offers you a convenient way to extend the functionalities of the default ``EntityRepository``
    +and put all the specialized DQL query logic on it. For this you have to create a subclass
    +of ``Doctrine\ORM\EntityRepository``, in our case a ``BugRepository`` and group all
    +the previoiusly discussed query functionality in it:
    +
    +.. code-block:: php
    +
    +    createQuery($dql);
    +            $query->setMaxResults($number);
    +            return $query->getResult();
    +        }
    +
    +        public function getRecentBugsArray($number = 30)
    +        {
    +            $dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ".
    +                   "JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC";
    +            $query = $em->createQuery($dql);
    +            $query->setMaxResults($number);
    +            return $query->getArrayResult();
    +        }
    +
    +        public function getUsersBugs($userId, $number = 15)
    +        {
    +            $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ".
    +                   "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC";
    +
    +            return $entityManager->createQuery($dql)
    +                                 ->setParameter(1, $userId)
    +                                 ->setMaxResults($number)
    +                                 ->getResult();
    +        }
    +
    +        public function getOpenBugsByProduct()
    +        {
    +            $dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ".
    +                   "JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id";
    +            return $em->createQuery($dql)->getScalarResult();
    +        }
    +    }
    +
    +To be able to use this query logic through ``$entityManager->getRepository('Bug')``
    +we have to adjust the metadata slightly.
    +
    +.. configuration-block::
    +
    +    .. code-block:: php
    +
    +        
    +
    +              
    +
    +              
    +        
    +
    +    .. code-block:: yaml
    +
    +        Product:
    +          type: entity
    +          repositoryClass: BugRepository
    +
    +Now we can remove our query logic in all the places and instead use them through the EntityRepository.
    +As an example here is the code of the first use case "List of Bugs":
    +
    +.. code-block:: php
    +
    +    getRepository('Bug')->getRecentBugs();
    +
    +    foreach($bugs AS $bug) {
    +        echo $bug->description." - ".$bug->created->format('d.m.Y')."\n";
    +        echo "    Reported by: ".$bug->getReporter()->name."\n";
    +        echo "    Assigned to: ".$bug->getEngineer()->name."\n";
    +        foreach($bug->getProducts() AS $product) {
    +            echo "    Platform: ".$product->name."\n";
    +        }
    +        echo "\n";
    +    }
    +
    +Using EntityRepositories you can avoid coupling your model with specific query logic.
    +You can also re-use query logic easily throughout your application.
    +
    +Conclusion
    +----------
    +
     This tutorial is over here, I hope you had fun. Additional content
     will be added to this tutorial incrementally, topics will include:
     
    -* Entity Repositories
    -* More on Association Mappings
    -* Lifecycle Events triggered in the UnitOfWork
    -* Ordering of Collections
    +-   More on Association Mappings
    +-   Lifecycle Events triggered in the UnitOfWork
    +-   Ordering of Collections
     
     Additional details on all the topics discussed here can be found in
     the respective manual chapters.
    
    From 0252368af23c4adaac79d71418ad358443174923 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 26 Feb 2011 11:51:43 +0100
    Subject: [PATCH 193/430] Fix some Sphinx/ReST warnings
    
    ---
     en/reference/dql-doctrine-query-language.rst  | 2 +-
     en/reference/limitations-and-known-issues.rst | 8 ++++----
     en/reference/query-builder.rst                | 2 +-
     en/reference/working-with-objects.rst         | 2 +-
     4 files changed, 7 insertions(+), 7 deletions(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index d745d8a98..bcc4043b9 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -2,7 +2,7 @@ Doctrine Query Language
     ===========================
     
     DQL stands for Doctrine Query Language and is an Object
    -Query Language derivate that is very similar to the **H**ibernate
    +Query Language derivate that is very similar to the Hibernate
     Query Language (HQL) or the Java Persistence Query Language (JPQL).
     
     In essence, DQL provides powerful querying capabilities over your
    diff --git a/en/reference/limitations-and-known-issues.rst b/en/reference/limitations-and-known-issues.rst
    index 0dc4fb9cb..3a042cfa9 100644
    --- a/en/reference/limitations-and-known-issues.rst
    +++ b/en/reference/limitations-and-known-issues.rst
    @@ -206,8 +206,8 @@ Cascade Merge with Bi-directional Associations
     There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
     Make sure to study the behavior of cascade merge if you are using it:
     
    --  `DDC-875 Merge can sometimes add the same entity twice into a collection`_
    --  `DDC-763 Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"`_
    +-  `DDC-875 `_ Merge can sometimes add the same entity twice into a collection
    +-  `DDC-763 `_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
     
     Custom Persisters
     ~~~~~~~~~~~~~~~~~
    @@ -267,8 +267,8 @@ blog posts we have written on this topics:
     -  `Doctrine2 "Behaviors" in a Nutshell `_
     -  `A re-usable Versionable behavior for Doctrine2 `_
     -  `Write your own ORM on top of Doctrine2 `_
    --  `Doctrine 2 Behavioral Extensions`_
    --  `Doctrator_
    +-  `Doctrine 2 Behavioral Extensions `_
    +-  `Doctrator _
     
     Doctrine 2 has enough hooks and extension points so that *you* can
     add whatever you want on top of it. None of this will ever become
    diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst
    index da5bbf2cd..7c1d2d8cd 100644
    --- a/en/reference/query-builder.rst
    +++ b/en/reference/query-builder.rst
    @@ -197,7 +197,7 @@ executing the Query. Additionally a set of parameters such as query hints
     cannot be set on the QueryBuilder itself. This is why you always have to convert
     a querybuilder instance into a Query object:
     
    -.. code-block::
    +.. code-block:: php
     
         // $qb instanceof QueryBuilder
         $query = $qb->getQuery();
    diff --git a/en/reference/working-with-objects.rst b/en/reference/working-with-objects.rst
    index ccb1fae08..f737807d3 100644
    --- a/en/reference/working-with-objects.rst
    +++ b/en/reference/working-with-objects.rst
    @@ -476,7 +476,7 @@ Effects of Database and UnitOfWork being Out-Of-Sync
     
     As soon as you begin to change the state ofentities, call persist or remove the
     contents of the UnitOfWork and the database will drive out of sync. They can
    -only be sychronized by calling ``EntityManager#flush()`. This section
    +only be sychronized by calling ``EntityManager#flush()``. This section
     describes the effects of database and UnitOfWork being out of sync.
     
     -  Entities that are scheduled for removal can still be queried from the database.
    
    From e94d15ec11d08b1d76ed6fa4cdbad734e4f946cf Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 26 Feb 2011 11:53:43 +0100
    Subject: [PATCH 194/430] Fix DDC-1039
    
    ---
     en/reference/dql-doctrine-query-language.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index bcc4043b9..93a36d0cb 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -279,7 +279,7 @@ With Nested Conditions in WHERE Clause:
         $query = $em->createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
         $query->setParameters(array(
             ':name' => 'Bob',
    -        ':name' => 'Alice',
    +        ':name2' => 'Alice',
             ':id' => 321,
         ));
         $users = $query->getResult(); // array of ForumUser objects
    
    From d0ae95604fcd029059a729b808b445378b2eacf5 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 26 Feb 2011 16:29:11 +0100
    Subject: [PATCH 195/430] Add warnining about date types assuming default
     timezone.
    
    ---
     en/reference/basic-mapping.rst | 16 +++++++++++++---
     1 file changed, 13 insertions(+), 3 deletions(-)
    
    diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst
    index ca2cbb101..525b39456 100644
    --- a/en/reference/basic-mapping.rst
    +++ b/en/reference/basic-mapping.rst
    @@ -137,12 +137,22 @@ built-in mapping types:
     
         Doctrine Mapping Types are NOT SQL types and NOT PHP
         types! They are mapping types between 2 types.
    +    Additionally Mapping types are *case-sensitive*. For example, using
    +    a DateTime column will NOT match the datetime type that ships with
    +    Doctrine 2.    
     
     .. warning::
     
    -    Mapping types are *case-sensitive*. For example, using
    -    a DateTime column will NOT match the datetime type that ships with
    -    Doctrine 2!
    +    All Date types assume that you are exclusively using the default timezone
    +    set by `date_default_timezone_set() `_
    +    or by the php.ini configuration ``date.timezone``. Working with
    +    different timezones will cause troubles and unexpected behavior.
    +
    +    If you need specific timezone handling you have to handle this
    +    in your domain, converting all the values back and forth from UTC.
    +    There is also a `cookbook entry <../cookbook/working-with-datetime>`
    +    on working with datetimes that gives hints for implementing
    +    multi timezone applications.
     
     
     Property Mapping
    
    From c551192b6b8f503fa01a28ea27c4182fa80ee6ce Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 27 Feb 2011 09:03:59 +0100
    Subject: [PATCH 196/430] DBAL-86 - Fix reference to nonexisting
     Configuration#setCustomTypes()
    
    ---
     en/reference/basic-mapping.rst | 9 ++-------
     1 file changed, 2 insertions(+), 7 deletions(-)
    
    diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst
    index 525b39456..970782279 100644
    --- a/en/reference/basic-mapping.rst
    +++ b/en/reference/basic-mapping.rst
    @@ -275,13 +275,8 @@ Restrictions to keep in mind:
     
     When you have implemented the type you still need to let Doctrine
     know about it. This can be achieved through the
    -``Doctrine\DBAL\Configuration#setCustomTypes(array $types)``
    -method. ``Doctrine\ORM\Configuration`` is a subclass of
    -``Doctrine\DBAL\Configuration``, so the methods are available on
    -your ORM Configuration instance as well.
    -
    -
    -Here is an example:
    +``Doctrine\DBAL\Types\Type#addType($name, $className)``
    +method. See the following example:
     
     .. code-block:: php
     
    
    From cca642b0944c31d497a09fad63cd8d5ad1cfced7 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 20 Mar 2011 12:05:28 +0100
    Subject: [PATCH 197/430] Remove CASE, COALESCSE, NULLIF EBNF rules for the
     time being, they are not yet supported.
    
    ---
     en/reference/dql-doctrine-query-language.rst | 17 +++++------------
     1 file changed, 5 insertions(+), 12 deletions(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index 93a36d0cb..cd9e6a97a 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -1457,18 +1457,7 @@ Scalar and Type Expressions
     .. code-block:: php
     
         ScalarExpression       ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression
    -                               BooleanPrimary | CaseExpression | EntityTypeExpression
    -    CaseExpression         ::= GeneralCaseExpression | SimpleCaseExpression |
    -                               CoalesceExpression | NullifExpression
    -    GeneralCaseExpression  ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression
    -                               "END"
    -    WhenClause             ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
    -    SimpleCaseExpression   ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}*
    -                               "ELSE" ScalarExpression "END"
    -    CaseOperand            ::= StateFieldPathExpression | TypeDiscriminator
    -    SimpleWhenClause       ::= "WHEN" ScalarExpression "THEN" ScalarExpression
    -    CoalesceExpression     ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
    -    NullifExpression       ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
    +                               BooleanPrimary | EntityTypeExpression
         StringExpression       ::= StringPrimary | "(" Subselect ")"
         StringPrimary          ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression
         BooleanExpression      ::= BooleanPrimary | "(" Subselect ")"
    @@ -1478,6 +1467,10 @@ Scalar and Type Expressions
         DatetimeExpression     ::= DatetimePrimary | "(" Subselect ")"
         DatetimePrimary        ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression
     
    +.. note::
    +
    +    Parts of CASE expressions are not yet implemented.
    +
     Aggregate Expressions
     ~~~~~~~~~~~~~~~~~~~~~
     
    
    From 126e592758f22151c63a45140c168f975a6e02a4 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 27 Mar 2011 22:23:37 +0200
    Subject: [PATCH 198/430] Fix YAML docs on cascade
    
    ---
     en/reference/yaml-mapping.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/yaml-mapping.rst b/en/reference/yaml-mapping.rst
    index 941ade9ef..219866e80 100644
    --- a/en/reference/yaml-mapping.rst
    +++ b/en/reference/yaml-mapping.rst
    @@ -70,7 +70,7 @@ of several common elements:
             phonenumbers:
               targetEntity: Phonenumber
               mappedBy: user
    -          cascade: cascadePersist
    +          cascade: ["persist", "merge"]
           manyToMany:
             groups:
               targetEntity: Group
    
    From 22ab3d1256c1f6901ad499af7877ac2b450005c4 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Mon, 28 Mar 2011 20:40:39 +0200
    Subject: [PATCH 199/430] Fixed several issues and merged pull requests into
     docs.
    
    ---
     en/reference/annotations-reference.rst       |  2 +-
     en/reference/caching.rst                     |  2 +-
     en/reference/change-tracking-policies.rst    |  4 ++--
     en/reference/dql-doctrine-query-language.rst | 22 ++++++++++----------
     en/reference/events.rst                      |  6 ++++--
     en/reference/query-builder.rst               |  1 +
     en/reference/working-with-associations.rst   | 13 ++++++------
     7 files changed, 27 insertions(+), 23 deletions(-)
    
    diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst
    index 363058944..5d4e9ee09 100644
    --- a/en/reference/annotations-reference.rst
    +++ b/en/reference/annotations-reference.rst
    @@ -494,7 +494,7 @@ Example:
     
         `_.
    +`in this blog post `_.
     
     
    diff --git a/en/reference/change-tracking-policies.rst b/en/reference/change-tracking-policies.rst
    index 7bd884910..d0f099895 100644
    --- a/en/reference/change-tracking-policies.rst
    +++ b/en/reference/change-tracking-policies.rst
    @@ -30,8 +30,8 @@ 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
    +comparison at commit time. The difference is that Doctrine 2 only
    +considers entities 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
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index cd9e6a97a..901f7f594 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -35,7 +35,7 @@ object model.
     DQL SELECT statements are a very powerful way of retrieving parts
     of your domain model that are not accessible via associations.
     Additionally they allow to retrieve entities and their associations
    -in one single sql select statement which can make a huge difference
    +in one single SQL select statement which can make a huge difference
     in performance in contrast to using several queries.
     
     DQL UPDATE and DELETE statements offer a way to execute bulk
    @@ -67,7 +67,7 @@ Lets examine the query:
     -  ``u`` is a so called identification variable or alias that
        refers to the ``MyProject\Model\User`` class. By placing this alias
        in the SELECT clause we specify that we want all instances of the
    -   User class that are matched by this query appear in the query
    +   User class that are matched by this query to appear in the query
        result.
     -  The FROM keyword is always followed by a fully-qualified class
        name which in turn is followed by an identification variable or
    @@ -160,7 +160,7 @@ with numbers, for example "?1", "?2" and so on. Named parameters
     are specified with ":name1", ":name2" and so on.
     
     When referencing the parameters in ``Query#setParameter($param, $value)``
    -both named and positional parameters are used **without** their prefixies.
    +both named and positional parameters are used **without** their prefixes.
     
     DQL SELECT Examples
     ~~~~~~~~~~~~~~~~~~~
    @@ -208,7 +208,7 @@ Retrieve the Username and Name of a CmsUser:
     
         createQuery('SELECT u.username, u.name FROM CmsUser u');
    -    $users = $query->getResults(); // array of CmsUser username and id values
    +    $users = $query->getResults(); // array of CmsUser username and name values
         echo $users[0]['username'];
     
     Retrieve a ForumUser and his single associated entity:
    @@ -268,7 +268,7 @@ With WHERE Clause and Named Parameter:
     
         createQuery('SELECT u FROM ForumUser u WHERE u.username = :name');
    -    $query->setParameter(':name', 'Bob');
    +    $query->setParameter('name', 'Bob');
         $users = $query->getResult(); // array of ForumUser objects
     
     With Nested Conditions in WHERE Clause:
    @@ -278,9 +278,9 @@ With Nested Conditions in WHERE Clause:
         createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
         $query->setParameters(array(
    -        ':name' => 'Bob',
    -        ':name2' => 'Alice',
    -        ':id' => 321,
    +        'name' => 'Bob',
    +        'name2' => 'Alice',
    +        'id' => 321,
         ));
         $users = $query->getResult(); // array of ForumUser objects
     
    @@ -384,7 +384,7 @@ Get all users who are members of $group.
     
         createQuery('SELECT u.id FROM CmsUser u WHERE :groupId MEMBER OF u.groups');
    -    $query->setParameter(':groupId', $group);
    +    $query->setParameter('groupId', $group);
         $ids = $query->getResult();
     
     Get all users that have more than 1 phonenumber
    @@ -915,12 +915,12 @@ structure:
     .. code-block:: php
     
         array
    -        array
    +        [0]
                 [0] => Object
                 [1] => "some scalar string"
                 ['count'] => 42
                 // ... more scalar values, either indexed numerically or with a name
    -        array
    +        [1]
                 [0] => Object
                 [1] => "some scalar string"
                 ['count'] => 42
    diff --git a/en/reference/events.rst b/en/reference/events.rst
    index ccdf199c8..c8b116cc9 100644
    --- a/en/reference/events.rst
    +++ b/en/reference/events.rst
    @@ -201,8 +201,8 @@ listeners:
         All Lifecycle events that happen during the ``flush()`` of
         an EntityManager have very specific constraints on the allowed
         operations that can be executed. Please read the
    -    *Implementing Event Listeners* section very carefully to understand
    -    which operations are allowed in which lifecycle event.
    +    :ref:`reference-events-implementing-listeners` section very carefully
    +    to understand which operations are allowed in which lifecycle event.
     
     
     Lifecycle Callbacks
    @@ -361,6 +361,8 @@ EntityManager was created:
         $entityManager->getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener());
         $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber());
     
    +.. _reference-events-implementing-listeners:
    +
     Implementing Event Listeners
     ----------------------------
     
    diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst
    index 7c1d2d8cd..1195be59a 100644
    --- a/en/reference/query-builder.rst
    +++ b/en/reference/query-builder.rst
    @@ -199,6 +199,7 @@ a querybuilder instance into a Query object:
     
     .. code-block:: php
     
    +    getQuery();
     
    diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst
    index 8edd54eaf..5a78f6ac7 100644
    --- a/en/reference/working-with-associations.rst
    +++ b/en/reference/working-with-associations.rst
    @@ -29,7 +29,7 @@ Association Example Entities
     We will use a simple comment system with Users and Comments as
     entities to show examples of association management. See the PHP
     docblocks of each association in the following example for
    -information about its type and if its the owning or inverse side.
    +information about its type and if it's the owning or inverse side.
     
     .. code-block:: php
     
    @@ -398,7 +398,7 @@ can show the possible caveats you can encounter:
         $user->getFavorites()->contains($favoriteComment); // TRUE
         $favoriteComment->getUserFavorites()->contains($user); // FALSE
     
    -There are to approaches to handle this problem in your code:
    +There are two approaches to handle this problem in your code:
     
     
     1. Ignore updating the inverse side of bidirectional collections,
    @@ -413,7 +413,7 @@ Transitive persistence / Cascade Operations
     -------------------------------------------
     
     Persisting, removing, detaching and merging individual entities can
    -become pretty cumbersome, especially when a larger object graph
    +become pretty cumbersome, especially when a large object graph
     with collections is involved. Therefore Doctrine 2 provides a
     mechanism for transitive persistence through cascading of these
     operations. Each association to another entity or a collection of
    @@ -531,7 +531,8 @@ OrphanRemoval works with both one-to-one and one-to-many associations.
     
         When using the ``orphanRemoval=true`` option Doctrine makes the assumption
         that the entities are privately owned and will **NOT** be reused by other entities.
    -    If you neglect this assumption your entities will get deleted by Doctrine anyways.
    +    If you neglect this assumption your entities will get deleted by Doctrine even if
    +    you assigned the orphaned entity to another one.
     
     As a better example consider an Addressbook application where you have Contacts, Addresses
     and StandingData:
    @@ -588,5 +589,5 @@ Now two examples what happens when you remove the references:
     
     In this case you have only changed the ``Contact`` entity but you removed
     the references for standing data and one address reference. When flush is called
    -not only are the references removed also both the old standing data and the one address entity
    -are deleted from the database.
    +not only are the references removed but both the old standing data and the one
    +address entity are also deleted from the database.
    \ No newline at end of file
    
    From abc8f29800536e8b9913de2b980c243c9f04820c Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Tue, 29 Mar 2011 20:30:10 +0200
    Subject: [PATCH 200/430] [DDC-692] Add docs on 2.1 read only entities feature
    
    ---
     en/reference/annotations-reference.rst | 3 +++
     en/reference/improving-performance.rst | 9 +++++++++
     en/reference/xml-mapping.rst           | 9 ++++++---
     3 files changed, 18 insertions(+), 3 deletions(-)
    
    diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst
    index 5d4e9ee09..e444ca7b9 100644
    --- a/en/reference/annotations-reference.rst
    +++ b/en/reference/annotations-reference.rst
    @@ -202,6 +202,9 @@ Optional attributes:
        Doctrine. Use of repositories for entities is encouraged to keep
        specialized DQL and SQL operations separated from the Model/Domain
        Layer.
    +-  **readOnly**: (>= 2.1) Specifies that this entity is marked as read only and not
    +   considered for change-tracking. Entities of this type can be persisted
    +   and removed though.
     
     Example:
     
    diff --git a/en/reference/improving-performance.rst b/en/reference/improving-performance.rst
    index 818c3d838..c5b99a189 100644
    --- a/en/reference/improving-performance.rst
    +++ b/en/reference/improving-performance.rst
    @@ -33,6 +33,15 @@ Make effective use of the available alternative query result
     formats like nested array graphs or pure scalar results, especially
     in scenarios where data is loaded for read-only purposes.
     
    +Read-Only Entities
    +------------------
    +
    +Starting with Doctrine 2.1 you can mark entities as read only (See metadata mapping
    +references for details). This means that the entity marked as read only is never considered
    +for updates, which means when you call flush on the EntityManager these entities are skipped
    +even if properties changed. Read-Only allows to persist new entities of a kind and remove existing
    +ones, they are just not considered for updates.
    +
     Apply Best Practices
     --------------------
     
    diff --git a/en/reference/xml-mapping.rst b/en/reference/xml-mapping.rst
    index 4a18d18d8..ef351da2d 100644
    --- a/en/reference/xml-mapping.rst
    +++ b/en/reference/xml-mapping.rst
    @@ -170,14 +170,17 @@ Required attributes:
     Optional attributes:
     
     
    --  table - The Table-Name to be used for this entity. Otherwise the
    +-  **table** - The Table-Name to be used for this entity. Otherwise the
        Unqualified Class-Name is used by default.
    --  repository-class - The fully qualified class-name of an
    +-  **repository-class** - The fully qualified class-name of an
        alternative ``Doctrine\ORM\EntityRepository`` implementation to be
        used with this entity.
    --  inheritance-type - The type of inheritance, defaults to none. A
    +-  **inheritance-type** - The type of inheritance, defaults to none. A
        more detailed description follows in the
        *Defining Inheritance Mappings* section.
    +-  **read-only** - (>= 2.1) Specifies that this entity is marked as read only and not
    +   considered for change-tracking. Entities of this type can be persisted
    +   and removed though.
     
     Defining Fields
     ~~~~~~~~~~~~~~~
    
    From 7639f1e99b145f27f1a26c5d926caa8f3cff43a6 Mon Sep 17 00:00:00 2001
    From: Gordon Franke 
    Date: Thu, 31 Mar 2011 08:57:27 +0200
    Subject: [PATCH 201/430] fix entity manager reference in repository code
     sample
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 10 +++++-----
     1 file changed, 5 insertions(+), 5 deletions(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index a8b435b8c..4377592c5 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -1202,7 +1202,7 @@ the previoiusly discussed query functionality in it:
             {
                 $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC";
     
    -            $query = $entityManager->createQuery($dql);
    +            $query = $this->getEntityManager()->createQuery($dql);
                 $query->setMaxResults($number);
                 return $query->getResult();
             }
    @@ -1211,7 +1211,7 @@ the previoiusly discussed query functionality in it:
             {
                 $dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ".
                        "JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC";
    -            $query = $em->createQuery($dql);
    +            $query = $this->getEntityManager()->createQuery($dql);
                 $query->setMaxResults($number);
                 return $query->getArrayResult();
             }
    @@ -1221,7 +1221,7 @@ the previoiusly discussed query functionality in it:
                 $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ".
                        "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC";
     
    -            return $entityManager->createQuery($dql)
    +            return $this->getEntityManager()->createQuery($dql)
                                      ->setParameter(1, $userId)
                                      ->setMaxResults($number)
                                      ->getResult();
    @@ -1231,11 +1231,11 @@ the previoiusly discussed query functionality in it:
             {
                 $dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ".
                        "JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id";
    -            return $em->createQuery($dql)->getScalarResult();
    +            return $this->getEntityManager()->createQuery($dql)->getScalarResult();
             }
         }
     
    -To be able to use this query logic through ``$entityManager->getRepository('Bug')``
    +To be able to use this query logic through ``$this->getEntityManager()->getRepository('Bug')``
     we have to adjust the metadata slightly.
     
     .. configuration-block::
    
    From 63c431f01dc520f001c77bbb56b9ae3b3050d4ad Mon Sep 17 00:00:00 2001
    From: Gordon Franke 
    Date: Thu, 31 Mar 2011 14:57:27 +0800
    Subject: [PATCH 202/430] fix entity manager reference in repository code
     sample
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 10 +++++-----
     1 file changed, 5 insertions(+), 5 deletions(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index a8b435b8c..4377592c5 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -1202,7 +1202,7 @@ the previoiusly discussed query functionality in it:
             {
                 $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC";
     
    -            $query = $entityManager->createQuery($dql);
    +            $query = $this->getEntityManager()->createQuery($dql);
                 $query->setMaxResults($number);
                 return $query->getResult();
             }
    @@ -1211,7 +1211,7 @@ the previoiusly discussed query functionality in it:
             {
                 $dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ".
                        "JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC";
    -            $query = $em->createQuery($dql);
    +            $query = $this->getEntityManager()->createQuery($dql);
                 $query->setMaxResults($number);
                 return $query->getArrayResult();
             }
    @@ -1221,7 +1221,7 @@ the previoiusly discussed query functionality in it:
                 $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ".
                        "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC";
     
    -            return $entityManager->createQuery($dql)
    +            return $this->getEntityManager()->createQuery($dql)
                                      ->setParameter(1, $userId)
                                      ->setMaxResults($number)
                                      ->getResult();
    @@ -1231,11 +1231,11 @@ the previoiusly discussed query functionality in it:
             {
                 $dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ".
                        "JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id";
    -            return $em->createQuery($dql)->getScalarResult();
    +            return $this->getEntityManager()->createQuery($dql)->getScalarResult();
             }
         }
     
    -To be able to use this query logic through ``$entityManager->getRepository('Bug')``
    +To be able to use this query logic through ``$this->getEntityManager()->getRepository('Bug')``
     we have to adjust the metadata slightly.
     
     .. configuration-block::
    
    From 04527a8bcb209217c12c0e2db0069af82f3fd3d2 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Thu, 31 Mar 2011 21:49:32 +0200
    Subject: [PATCH 203/430] Add cookbook entry on MySQL Enums.
    
    ---
     en/cookbook/mysql-enums.rst | 189 ++++++++++++++++++++++++++++++++++++
     en/index.rst                |   3 +-
     2 files changed, 191 insertions(+), 1 deletion(-)
     create mode 100644 en/cookbook/mysql-enums.rst
    
    diff --git a/en/cookbook/mysql-enums.rst b/en/cookbook/mysql-enums.rst
    new file mode 100644
    index 000000000..0e6e388df
    --- /dev/null
    +++ b/en/cookbook/mysql-enums.rst
    @@ -0,0 +1,189 @@
    +Mysql Enums
    +===========
    +
    +The type system of Doctrine 2 consists of flyweights, which means there is only
    +one instance of any given type. Additionally types do not contain state. Both
    +assumptions make it rather complicated to work with the Enum Type of MySQL that
    +is used quite a lot by developers.
    +
    +When using Enums with a non-tweaked Doctrine 2 application you will get
    +errors from the Schema-Tool commands due to the unknown database type "enum".
    +By default Doctrine does not map the MySQL enum type to a Doctrine type.
    +This is because Enums contain state (their allowed values) and Doctrine
    +types don't.
    +
    +This cookbook entry shows two possible solutions to work with MySQL enums.
    +But first a word of warning. The MySQL Enum type has considerable downsides:
    +
    +-  Adding new values requires to rebuild the whole table, which can take hours
    +   depending on the size.
    +-  Enums are ordered in the way the values are specified, not in their "natural" order.
    +-  Enums validation mechanism for allowed values is not necessarily good,
    +   specifying invalid values leads to an empty enum for the default MySQL error
    +   settings. You can easily replicate the "allow only some values" requirement
    +   in your Doctrine entities.
    +
    +Solution 1: Mapping to Varchars
    +-------------------------------
    +
    +You can map ENUMs to varchars. You can register MySQL ENUMs to map to Doctrine
    +varchars. This way Doctrine always resolves ENUMs to Doctrine varchars. It
    +will even detect this match correctly when using SchemaTool update commands.
    +
    +.. code-block:: php
    +
    +    getConnection();
    +    $conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'varchar');
    +
    +In this case you have to ensure that each varchar field that is an enum in the
    +database only gets passed the allowed values. You can easily enforce this in your
    +entities:
    +
    +.. code-block:: php
    +
    +    status = $status;
    +        }
    +    }
    +
    +If you want to actively create enums through the Doctrine Schema-Tool by using
    +the **columnDefinition** attribute.
    +
    +.. code-block:: php
    +
    +    values);
    +
    +            return "ENUM(".implode(", ", $values).") COMMENT '(DC2Type:".$this->name.")'";
    +        }
    +
    +        public function convertToPHPValue($value, AbstractPlatform $platform)
    +        {
    +            return $value;
    +        }
    +
    +        public function convertToDatabaseValue($value, AbstractPlatform $platform)
    +        {
    +            if (!in_array($value, $this->values)) {
    +                throw new \InvalidArgumentException("Invalid '".$this->name."' value.");
    +            }
    +            return $value;
    +        }
    +
    +        public function getName()
    +        {
    +            return $this->name;
    +        }
    +    }
    +
    +With this base class you can define an enum as easily as:
    +
    +.. code-block:: php
    +
    +    
    Date: Sun, 3 Apr 2011 20:00:43 +0200
    Subject: [PATCH 204/430] Update section on mixed query and add notes on
     multiple entities in FROM clause.
    
    ---
     en/reference/dql-doctrine-query-language.rst | 33 +++++++++++++++++---
     1 file changed, 29 insertions(+), 4 deletions(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index 901f7f594..879fc57d7 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -903,6 +903,8 @@ A pure result usually looks like this:
     
     .. code-block:: php
     
    +    $dql = "SELECT u FROM User u";
    +
         array
             [0] => Object
             [1] => Object
    @@ -914,16 +916,18 @@ structure:
     
     .. code-block:: php
     
    +    $dql = "SELECT u, 'some scalar string', count(u.groups) AS num FROM User u";
    +
         array
             [0]
                 [0] => Object
                 [1] => "some scalar string"
    -            ['count'] => 42
    +            ['num'] => 42
                 // ... more scalar values, either indexed numerically or with a name
             [1]
                 [0] => Object
                 [1] => "some scalar string"
    -            ['count'] => 42
    +            ['num'] => 42
                 // ... more scalar values, either indexed numerically or with a name
     
     To better understand mixed results, consider the following DQL
    @@ -937,6 +941,13 @@ This query makes use of the ``UPPER`` DQL function that returns a
     scalar value and because there is now a scalar value in the SELECT
     clause, we get a mixed result.
     
    +Conventions for mixed results are as follows:
    +
    +-  The object fetched in the FROM clause is always positioned with the key '0'.
    +-  Every scalar without a name is numbered in the order given in the query, starting with 1.
    +-  Every aliased scalar is given with its alias-name as the key. The case of the name is kept.
    +-  If several objects are fetched from the FROM clause they alternate every row.
    +
     Here is how the result could look like:
     
     .. code-block:: php
    @@ -960,8 +971,22 @@ And here is how you would access it in PHP code:
             echo "Name UPPER: " . $row['nameUpper'];
         }
     
    -You may have observed that in a mixed result, the object always
    -ends up on index 0 of a result row.
    +Fetching Multiple FROM Entities
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +If you fetch multiple entities that are listed in the FROM clause then the hydration
    +will return the rows iterating the different top-level entities.
    +
    +.. code-block:: php
    +
    +    $dql = "SELECT u, g FROM User u, Group g";
    +
    +    array
    +        [0] => Object (User)
    +        [1] => Object (Group)
    +        [2] => Object (User)
    +        [3] => Object (Group)
    +
     
     Hydration Modes
     ~~~~~~~~~~~~~~~
    
    From a0b41feb72101d6d5ed2b35f69282de918e4a7aa Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 3 Apr 2011 20:13:34 +0200
    Subject: [PATCH 205/430] Update section on mixed query and add notes on
     multiple entities in FROM clause. (update paragraph)
    
    ---
     en/reference/dql-doctrine-query-language.rst | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index 879fc57d7..e592b9e02 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -943,11 +943,13 @@ clause, we get a mixed result.
     
     Conventions for mixed results are as follows:
     
    +
     -  The object fetched in the FROM clause is always positioned with the key '0'.
     -  Every scalar without a name is numbered in the order given in the query, starting with 1.
     -  Every aliased scalar is given with its alias-name as the key. The case of the name is kept.
     -  If several objects are fetched from the FROM clause they alternate every row.
     
    +
     Here is how the result could look like:
     
     .. code-block:: php
    
    From f76728818b56c786e1dd59b25b87cc9ad9f24304 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Wed, 6 Apr 2011 23:36:50 +0200
    Subject: [PATCH 206/430] Add note box on cascade operations being performed in
     memory and reference to foreign key /database-level onDelete option for
     deleting associtions
    
    ---
     en/reference/working-with-associations.rst | 19 +++++++++++++++++--
     1 file changed, 17 insertions(+), 2 deletions(-)
    
    diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst
    index 5a78f6ac7..f426ecf03 100644
    --- a/en/reference/working-with-associations.rst
    +++ b/en/reference/working-with-associations.rst
    @@ -413,8 +413,8 @@ Transitive persistence / Cascade Operations
     -------------------------------------------
     
     Persisting, removing, detaching and merging individual entities can
    -become pretty cumbersome, especially when a large object graph
    -with collections is involved. Therefore Doctrine 2 provides a
    +become pretty cumbersome, especially when a highly interveawed object graph
    +is involved. Therefore Doctrine 2 provides a
     mechanism for transitive persistence through cascading of these
     operations. Each association to another entity or a collection of
     entities can be configured to automatically cascade certain
    @@ -431,6 +431,21 @@ The following cascade options exist:
     -  all : Cascades persist, remove, merge and detach operations to
        associated entities.
     
    +.. note::
    +
    +    Cascade operations are performed in memory. That means collections and related entities
    +    are fetched into memory, even if they are still marked as lazy when
    +    the cascade operation is about to be performed. However this approach allows
    +    entity lifecycle events to be performed for each of these operations.
    +
    +    However, pulling objects graph into memory on cascade can cause considerable performance
    +    overhead, especially when cascading collections are large. Makes sure
    +    to weigh the benefits and downsides of each cascade operation that you define.
    +
    +    To rely on the database level cascade operations for the delete operation instead, you can
    +    configure each join column with the **onDelete** option. See the respective
    +    mapping driver chapters for more information.
    +
     The following example is an extension to the User-Comment example
     of this chapter. Suppose in our application a user is created
     whenever he writes his first comment. In this case we would use the
    
    From 3a80896173c8deb9cf82d1e916a678730b33573f Mon Sep 17 00:00:00 2001
    From: Wil Moore III 
    Date: Fri, 8 Apr 2011 10:44:55 -0700
    Subject: [PATCH 207/430] Changed registerDoctrineTypeMapping('enum',
     'varchar'); to registerDoctrineTypeMapping('enum', 'string'); as 'string' is
     the correct type to map to whereas varchar is only an alias.
    
    ---
     en/cookbook/mysql-enums.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/cookbook/mysql-enums.rst b/en/cookbook/mysql-enums.rst
    index 0e6e388df..b8cef7b21 100644
    --- a/en/cookbook/mysql-enums.rst
    +++ b/en/cookbook/mysql-enums.rst
    @@ -34,7 +34,7 @@ will even detect this match correctly when using SchemaTool update commands.
     
         getConnection();
    -    $conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'varchar');
    +    $conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
     
     In this case you have to ensure that each varchar field that is an enum in the
     database only gets passed the allowed values. You can easily enforce this in your
    
    From 01bfc4f2f1ed7236b6e34e936f6add3ee8feea36 Mon Sep 17 00:00:00 2001
    From: asartalo 
    Date: Sun, 24 Apr 2011 10:38:35 +0800
    Subject: [PATCH 208/430] Removed '--force' Option for the
     orrm:schema-tool:create command as this is not available for this command.
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 4377592c5..c8e3553df 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -745,7 +745,7 @@ Doctrine command-line tool:
     ::
     
         doctrine@my-desktop> cd myproject/
    -    doctrine@my-desktop> doctrine orm:schema-tool:create --force
    +    doctrine@my-desktop> doctrine orm:schema-tool:create
     
     .. note::
     
    
    From 511268ddd79781f1dd40749e931cb3408a4fb61f Mon Sep 17 00:00:00 2001
    From: asartalo 
    Date: Sun, 24 Apr 2011 10:52:18 +0800
    Subject: [PATCH 209/430] Removed another instance of '--force' Option for the
     orrm:schema-tool:create command as this is not available for this command.
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index c8e3553df..80b3aa227 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -766,7 +766,7 @@ either re-create the database:
     ::
     
         doctrine@my-desktop> doctrine orm:schema-tool:drop --force
    -    doctrine@my-desktop> doctrine orm:schema-tool:create --force
    +    doctrine@my-desktop> doctrine orm:schema-tool:create
     
     Or use the update functionality:
     
    
    From 3cac853dd46f630b631f7c84fac8edb0c5ea711e Mon Sep 17 00:00:00 2001
    From: asartalo 
    Date: Sun, 24 Apr 2011 13:21:34 +0800
    Subject: [PATCH 210/430] Changed instances of entity manager variable '' to ''
     for consistency.
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 80b3aa227..03cf5681a 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -975,7 +975,7 @@ can rewrite our code:
         createQuery($dql);
    +    $query = $entityManager->createQuery($dql);
         $bugs = $query->getArrayResult();
         
         foreach ($bugs AS $bug) {
    @@ -1116,7 +1116,7 @@ grouped by product:
         createQuery($dql)->getScalarResult();
    +    $productBugs = $entityManager->createQuery($dql)->getScalarResult();
         
         foreach($productBugs as $productBug) {
             echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n";
    
    From 16f132bff09438664a03a6850fdc0f5e42f745d1 Mon Sep 17 00:00:00 2001
    From: asartalo 
    Date: Sun, 24 Apr 2011 13:24:54 +0800
    Subject: [PATCH 211/430] Corrected annotation for sample code: moved
     repositoryClass attribute from @Table to @Entity.
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 3 ++-
     1 file changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 03cf5681a..14069f4ea 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -1244,7 +1244,8 @@ we have to adjust the metadata slightly.
     
             
    Date: Tue, 3 May 2011 22:48:40 -0400
    Subject: [PATCH 212/430] first draft of cookbook article
    
    ---
     en/cookbook/decorator-pattern.rst | 280 ++++++++++++++++++++++++++++++
     1 file changed, 280 insertions(+)
     create mode 100644 en/cookbook/decorator-pattern.rst
    
    diff --git a/en/cookbook/decorator-pattern.rst b/en/cookbook/decorator-pattern.rst
    new file mode 100644
    index 000000000..5b29aceb8
    --- /dev/null
    +++ b/en/cookbook/decorator-pattern.rst
    @@ -0,0 +1,280 @@
    +Persisting the Decorator Pattern
    +================================
    +
    +.. sectionauthor:: Chris Woodford 
    +
    +
    +
    +Decorator Pattern
    +-----------------
    +
    +Let's take a quick look at a visual representation of the Decorator 
    +pattern
    +
    +
    +Doctrine Implementation
    +-----------------------
    +
    +In order to persist this pattern, we need fields in the Component 
    +class to be stored and shared among the entire hierarchy, and we 
    +needed any additional fields in the ConcreteComponent and 
    +ConcreteDecorator to be stored as well. In this design, the Decorator 
    +class just delegates calls to the Component decorated and doesn't 
    +require any persistence of its own. However, the Decorator class 
    +does have an association to the persistent Component that it's 
    +decorating, and does need to be a part of this persistence hierarchy. 
    +
    +Since the Component class needs to be persisted, it's going to be a 
    +Doctrine Entity. As the top of the inheritance hierarchy, it's going 
    +to have to define the persistent inheritance. For this example, we 
    +will use Single Table Inheritance, but Class Table Inheritance 
    +would work as well. 
    +
    +In the discriminator map, we need to define two concrete subclasses, 
    +ConcreteComponent and ConcreteDecorator. 
    +
    +Component
    +---------
    +
    +.. code-block:: php
    +
    +    id;
    +        }
    + 
    +        /**
    +         * Set name
    +         * @param string $name
    +         */
    +        public function setName($name)
    +        {
    +            $this->name = $name;
    +        }
    + 
    +        /**
    +         * Get name
    +         * @return string $name
    +         */
    +        public function getName()
    +        {
    +            return $this->name;
    +        }
    + 
    +    }
    +    
    +ConcreteComponent
    +-----------------
    +
    +The ConcreteComponent class is pretty simple and doesn't do much more 
    +than extend the abstract Component class (only for the purpose of 
    +keeping this example simple).
    +
    +.. code-block:: php
    +
    +    setDecorates($c);
    +        }
    + 
    +        /**
    +         * (non-PHPdoc)
    +         * @see ImedevacTest.Component::getName()
    +         */
    +        public function getName()
    +        {
    +    	    return 'Decorated ' . $this->getDecorates()->getName();
    +        }
    + 
    +        /**
    +         * the component being decorated
    +         * @return Component
    +         */
    +        protected function getDecorates()
    +        {
    +    	    return $this->decorates;
    +        }
    + 
    +        /**
    +         * sets the component being decorated
    +         * @param Component $c
    +         */
    +        protected function setDecorates(Component $c)
    +        {
    +    	    $this->decorates = $c;
    +        }
    + 
    +    }
    +
    +All operations on the Decorator (i.e. persist, remove, etc) will 
    +cascade from the Decorator to the Component. This means that when we 
    +persist a Decorator, Doctrine will take care of persisting the chain 
    +of decorated objects for us. A Decorator can be treated exactly as a 
    +Component when it comes time to persisting it.
    + 
    +The Decorator's constructor accepts an instance of a Component, as 
    +defined by the Decorator pattern (using constructor injection). The 
    +setDecorates/getDecorates methods have been defined as protected to 
    +hide the fact that a Decorator is decorating a Component and keeps 
    +the Component interface and the Decorator interface identical.
    +
    +To illustrate the purpose of the Decorator pattern, the getName() 
    +method has been overridden to append a string to the Component's 
    +getName() method.
    +
    +ConcreteDecorator
    +-----------------
    +
    +.. code-block:: php
    +
    +    special = $special;
    +        }
    + 
    +        /**
    +         * Get special
    +         * @return string $special
    +         */
    +        public function getSpecial()
    +        {
    +            return $this->special;
    +        }
    + 
    +        /**
    +         * (non-PHPdoc)
    +         * @see ImedevacTest.Component::getName()
    +         */
    +        public function getName()
    +        {
    +            return '[' . $this->getSpecial()
    +                . '] ' . parent::getName(); 
    +        }
    + 
    +    }
    +    
    +Tests
    +-----
    +
    +.. code-block:: php
    +
    +    setName('Test Component 1');
    +    $em->persist($c); // assigned unique ID = 1
    + 
    +    // create a new concrete decorator
    +    $c = new ConcreteComponent();
    +    $c->setName('Test Component 2');
    + 
    +    $d = new ConcreteDecorator($c);
    +    $d->setSpecial('Really');
    +    $em->persist($d); 
    +    // assigns c as unique ID = 2, and d as unique ID = 3
    +    
    +    $em->flush();
    +
    +    $c = $em->find('Test\Component', 1);
    +    $d = $em->find('Test\Component', 3);
    + 
    +    echo get_class($c);
    +    // prints: Test\Component\Concrete\Component
    + 
    +    echo $c->getName();
    +    // prints: Test Component 1 
    + 
    +    echo get_class($d) 
    +    // prints: Test\Component\Concrete\Decorator
    + 
    +    echo $d->getName();
    +    // prints: [Really] Decorated Test Component 2
    +    
    
    From c242ab437152f042fea9412951022fb8a260d094 Mon Sep 17 00:00:00 2001
    From: Chris Woodford 
    Date: Tue, 3 May 2011 23:04:41 -0400
    Subject: [PATCH 213/430] some tweaks to class descriptions
    
    ---
     en/cookbook/decorator-pattern.rst | 46 ++++++++++++++-----------------
     1 file changed, 20 insertions(+), 26 deletions(-)
    
    diff --git a/en/cookbook/decorator-pattern.rst b/en/cookbook/decorator-pattern.rst
    index 5b29aceb8..052c9f8b5 100644
    --- a/en/cookbook/decorator-pattern.rst
    +++ b/en/cookbook/decorator-pattern.rst
    @@ -3,38 +3,22 @@ Persisting the Decorator Pattern
     
     .. sectionauthor:: Chris Woodford 
     
    -
    -
    -Decorator Pattern
    ------------------
    +INTRO
     
     Let's take a quick look at a visual representation of the Decorator 
     pattern
     
    +DECORATOR PATTERN IMAGE
     
    -Doctrine Implementation
    ------------------------
    -
    -In order to persist this pattern, we need fields in the Component 
    -class to be stored and shared among the entire hierarchy, and we 
    -needed any additional fields in the ConcreteComponent and 
    -ConcreteDecorator to be stored as well. In this design, the Decorator 
    -class just delegates calls to the Component decorated and doesn't 
    -require any persistence of its own. However, the Decorator class 
    -does have an association to the persistent Component that it's 
    -decorating, and does need to be a part of this persistence hierarchy. 
    +Component
    +---------
     
     Since the Component class needs to be persisted, it's going to be a 
     Doctrine Entity. As the top of the inheritance hierarchy, it's going 
     to have to define the persistent inheritance. For this example, we 
     will use Single Table Inheritance, but Class Table Inheritance 
    -would work as well. 
    -
    -In the discriminator map, we need to define two concrete subclasses, 
    -ConcreteComponent and ConcreteDecorator. 
    -
    -Component
    ----------
    +would work as well. In the discriminator map, we will define two 
    +concrete subclasses, ConcreteComponent and ConcreteDecorator. 
     
     .. code-block:: php
     
    @@ -46,8 +30,8 @@ Component
          * @Entity
          * @InheritanceType("SINGLE_TABLE")
          * @DiscriminatorColumn(name="discr", type="string")
    -     * @DiscriminatorMap({"cc" = "TestComponentConcreteComponent", 
    -        "cd" = "TestDecoratorConcreteDecorator"})
    +     * @DiscriminatorMap({"cc" = "Test\Component\Concrete\Component", 
    +        "cd" = "Test\Decorator\Concrete\Decorator"})
          */
         abstract class Component
         {
    @@ -189,6 +173,13 @@ getName() method.
     ConcreteDecorator
     -----------------
     
    +The final class required to complete a simple implementation of the 
    +Decorator pattern is the ConcreteDecorator. In order to further 
    +illustrate how the Decorator can alter data as it moves through the 
    +chain of decoration, a new field, "special", has been added to this 
    +class. The getName() has been overridden and appends the value of the 
    +getSpecial() method to its return value.  
    +
     .. code-block:: php
     
         
    Date: Thu, 5 May 2011 21:16:40 -0400
    Subject: [PATCH 214/430] fixed some typos
    
    ---
     en/cookbook/decorator-pattern.rst | 79 +++++++++++++++----------------
     1 file changed, 38 insertions(+), 41 deletions(-)
    
    diff --git a/en/cookbook/decorator-pattern.rst b/en/cookbook/decorator-pattern.rst
    index 052c9f8b5..f84bade45 100644
    --- a/en/cookbook/decorator-pattern.rst
    +++ b/en/cookbook/decorator-pattern.rst
    @@ -5,20 +5,15 @@ Persisting the Decorator Pattern
     
     INTRO
     
    -Let's take a quick look at a visual representation of the Decorator 
    -pattern
    -
    -DECORATOR PATTERN IMAGE
    -
     Component
     ---------
     
    -Since the Component class needs to be persisted, it's going to be a 
    -Doctrine Entity. As the top of the inheritance hierarchy, it's going 
    +Since the ``Component`` class needs to be persisted, it's going to 
    +be an ``Entity``. As the top of the inheritance hierarchy, it's going 
     to have to define the persistent inheritance. For this example, we 
    -will use Single Table Inheritance, but Class Table Inheritance 
    +will use Single Table Inheritance, but Class Table Inheritance  
     would work as well. In the discriminator map, we will define two 
    -concrete subclasses, ConcreteComponent and ConcreteDecorator. 
    +concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``. 
     
     .. code-block:: php
     
    @@ -30,8 +25,8 @@ concrete subclasses, ConcreteComponent and ConcreteDecorator.
          * @Entity
          * @InheritanceType("SINGLE_TABLE")
          * @DiscriminatorColumn(name="discr", type="string")
    -     * @DiscriminatorMap({"cc" = "Test\Component\Concrete\Component", 
    -        "cd" = "Test\Decorator\Concrete\Decorator"})
    +     * @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent", 
    +        "cd" = "Test\Decorator\ConcreteDecorator"})
          */
         abstract class Component
         {
    @@ -77,9 +72,9 @@ concrete subclasses, ConcreteComponent and ConcreteDecorator.
     ConcreteComponent
     -----------------
     
    -The ConcreteComponent class is pretty simple and doesn't do much more 
    -than extend the abstract Component class (only for the purpose of 
    -keeping this example simple).
    +The ``ConcreteComponent`` class is pretty simple and doesn't do much 
    +more than extend the abstract ``Component`` class (only for the 
    +purpose of keeping this example simple).
     
     .. code-block:: php
     
    @@ -96,9 +91,9 @@ keeping this example simple).
     Decorator
     ---------
     
    -The Decorator class doesn't need to be persisted, but it does need to 
    -define an association with a persisted Entity. We can use a 
    -MappedSuperclass for this.
    +The ``Decorator`` class doesn't need to be persisted, but it does 
    +need to define an association with a persisted ``Entity``. We can 
    +use a ``MappedSuperclass`` for this.
     
     .. code-block:: php
     
    @@ -111,7 +106,7 @@ MappedSuperclass for this.
         {
      
             /**
    -         * @OneToOne(targetEntity="TestComponent", cascade={"all"})
    +         * @OneToOne(targetEntity="Test\Component", cascade={"all"})
              * @JoinColumn(name="decorates", referencedColumnName="id")
              */
             protected $decorates;
    @@ -127,7 +122,7 @@ MappedSuperclass for this.
      
             /**
              * (non-PHPdoc)
    -         * @see ImedevacTest.Component::getName()
    +         * @see Test.Component::getName()
              */
             public function getName()
             {
    @@ -154,31 +149,33 @@ MappedSuperclass for this.
      
         }
     
    -All operations on the Decorator (i.e. persist, remove, etc) will 
    -cascade from the Decorator to the Component. This means that when we 
    -persist a Decorator, Doctrine will take care of persisting the chain 
    -of decorated objects for us. A Decorator can be treated exactly as a 
    -Component when it comes time to persisting it.
    +All operations on the ``Decorator`` (i.e. persist, remove, etc) will 
    +cascade from the ``Decorator`` to the ``Component``. This means that 
    +when we persist a ``Decorator``, Doctrine will take care of 
    +persisting the chain of decorated objects for us. A ``Decorator`` can 
    +be treated exactly as a ``Component`` when it comes time to 
    +persisting it.
      
    -The Decorator's constructor accepts an instance of a Component, as 
    -defined by the Decorator pattern (using constructor injection). The 
    -setDecorates/getDecorates methods have been defined as protected to 
    -hide the fact that a Decorator is decorating a Component and keeps 
    -the Component interface and the Decorator interface identical.
    +The ``Decorator's`` constructor accepts an instance of a 
    +``Component``, as defined by the ``Decorator`` pattern (using 
    +constructor injection). The setDecorates/getDecorates methods have 
    +been defined as protected to hide the fact that a ``Decorator`` is 
    +decorating a ``Component`` and keeps the ``Component`` interface and 
    +the ``Decorator`` interface identical.
     
    -To illustrate the purpose of the Decorator pattern, the getName() 
    -method has been overridden to append a string to the Component's 
    +To illustrate the purpose of the ``Decorator`` pattern, the getName() 
    +method has been overridden to append a string to the ``Component's`` 
     getName() method.
     
     ConcreteDecorator
     -----------------
     
     The final class required to complete a simple implementation of the 
    -Decorator pattern is the ConcreteDecorator. In order to further 
    -illustrate how the Decorator can alter data as it moves through the 
    -chain of decoration, a new field, "special", has been added to this 
    -class. The getName() has been overridden and appends the value of the 
    -getSpecial() method to its return value.  
    +Decorator pattern is the ``ConcreteDecorator``. In order to further 
    +illustrate how the ``Decorator`` can alter data as it moves through 
    +the chain of decoration, a new field, "special", has been added to 
    +this class. The getName() has been overridden and appends the value 
    +of the getSpecial() method to its return value.  
     
     .. code-block:: php
     
    @@ -215,7 +212,7 @@ getSpecial() method to its return value.
      
             /**
              * (non-PHPdoc)
    -         * @see ImedevacTest.Component::getName()
    +         * @see Test.Component::getName()
              */
             public function getName()
             {
    @@ -235,8 +232,8 @@ objects
     
         find('Test\Component', 3);
      
         echo get_class($c);
    -    // prints: Test\Component\Concrete\Component
    +    // prints: Test\Component\ConcreteComponent
      
         echo $c->getName();
         // prints: Test Component 1 
      
         echo get_class($d) 
    -    // prints: Test\Component\Concrete\Decorator
    +    // prints: Test\Component\ConcreteDecorator
      
         echo $d->getName();
         // prints: [Really] Decorated Test Component 2
    
    From e9d096e411f2d6b79c775475437dc25ce93935d0 Mon Sep 17 00:00:00 2001
    From: Chris Woodford 
    Date: Thu, 5 May 2011 21:23:10 -0400
    Subject: [PATCH 215/430] cleaning up some sentences. better flow
    
    ---
     en/cookbook/decorator-pattern.rst | 16 ++++++++--------
     1 file changed, 8 insertions(+), 8 deletions(-)
    
    diff --git a/en/cookbook/decorator-pattern.rst b/en/cookbook/decorator-pattern.rst
    index f84bade45..7fe7c2dda 100644
    --- a/en/cookbook/decorator-pattern.rst
    +++ b/en/cookbook/decorator-pattern.rst
    @@ -157,15 +157,15 @@ be treated exactly as a ``Component`` when it comes time to
     persisting it.
      
     The ``Decorator's`` constructor accepts an instance of a 
    -``Component``, as defined by the ``Decorator`` pattern (using 
    -constructor injection). The setDecorates/getDecorates methods have 
    -been defined as protected to hide the fact that a ``Decorator`` is 
    -decorating a ``Component`` and keeps the ``Component`` interface and 
    -the ``Decorator`` interface identical.
    +``Component``, as defined by the ``Decorator`` pattern. The 
    +setDecorates/getDecorates methods have been defined as protected to 
    +hide the fact that a ``Decorator`` is decorating a ``Component`` and 
    +keeps the ``Component`` interface and the ``Decorator`` interface 
    +identical.
     
    -To illustrate the purpose of the ``Decorator`` pattern, the getName() 
    -method has been overridden to append a string to the ``Component's`` 
    -getName() method.
    +To illustrate the intended result of the ``Decorator`` pattern, the 
    +getName() method has been overridden to append a string to the 
    +``Component's`` getName() method.
     
     ConcreteDecorator
     -----------------
    
    From 6cd778f940d56aacb1352f9842333253794dfcdf Mon Sep 17 00:00:00 2001
    From: Chris Woodford 
    Date: Thu, 5 May 2011 21:36:18 -0400
    Subject: [PATCH 216/430] wrote a quick introduction
    
    ---
     en/cookbook/decorator-pattern.rst | 6 ++++--
     1 file changed, 4 insertions(+), 2 deletions(-)
    
    diff --git a/en/cookbook/decorator-pattern.rst b/en/cookbook/decorator-pattern.rst
    index 7fe7c2dda..38bf3ef54 100644
    --- a/en/cookbook/decorator-pattern.rst
    +++ b/en/cookbook/decorator-pattern.rst
    @@ -3,12 +3,14 @@ Persisting the Decorator Pattern
     
     .. sectionauthor:: Chris Woodford 
     
    -INTRO
    +This recipe will show you a simple example of how you can use 
    +Doctrine 2 to persist an implementaton of the 
    +`Decorator Pattern `_
     
     Component
     ---------
     
    -Since the ``Component`` class needs to be persisted, it's going to 
    +The ``Component`` class needs to be persisted, so it's going to 
     be an ``Entity``. As the top of the inheritance hierarchy, it's going 
     to have to define the persistent inheritance. For this example, we 
     will use Single Table Inheritance, but Class Table Inheritance  
    
    From d436ea19dfc58a54d69873beb55b2f3006f9c3f1 Mon Sep 17 00:00:00 2001
    From: Brikou CARRE 
    Date: Wed, 18 May 2011 02:51:22 -0700
    Subject: [PATCH 217/430] fixed setQueryHint > setHint
    
    ---
     en/cookbook/dql-custom-walkers.rst | 5 ++---
     1 file changed, 2 insertions(+), 3 deletions(-)
    
    diff --git a/en/cookbook/dql-custom-walkers.rst b/en/cookbook/dql-custom-walkers.rst
    index 19ce8bda6..2887a5d68 100644
    --- a/en/cookbook/dql-custom-walkers.rst
    +++ b/en/cookbook/dql-custom-walkers.rst
    @@ -174,8 +174,8 @@ SQL\_NO\_CACHE query hint.
         createQuery($dql);
    -    $query->setQueryHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker');
    -    $query->setQueryHint("mysqlWalker.sqlNoCache", true);
    +    $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker');
    +    $query->setHint("mysqlWalker.sqlNoCache", true);
         $results = $query->getResult();
     
     Our ``MysqlWalker`` will extend the default ``SqlWalker``. We will
    @@ -215,4 +215,3 @@ huge benefits with using vendor specific features. This would still
     allow you write DQL queries instead of NativeQueries to make use of
     vendor specific features.
     
    -
    
    From 6d457d99275ffa874c0f1e4cf71ab318c69afb00 Mon Sep 17 00:00:00 2001
    From: "Berengar W. Lehr" 
    Date: Wed, 18 May 2011 19:49:48 +0200
    Subject: [PATCH 218/430] =?UTF-8?q?1.=20Changed=20external=20links=20synta?=
     =?UTF-8?q?x=20in=20internal=20link=20syntax=20to=20repair=20link=20(dql-?=
     =?UTF-8?q?=E2=80=A6=20file)=202.=20Replaced=20unicode=20"fi"-ligatur=20wi?=
     =?UTF-8?q?th=20"fi"=20to=20enable=20pdflatex-ing=20b/c=20utf8-inputenc-pa?=
     =?UTF-8?q?ckage=20does=20-=20for=20unknown=20reasons=20-=20not=20support?=
     =?UTF-8?q?=20unicode=20"fi"-ligatur?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    ---
     en/reference/annotations-reference.rst       | 4 ++--
     en/reference/dql-doctrine-query-language.rst | 2 +-
     2 files changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst
    index e444ca7b9..18328534f 100644
    --- a/en/reference/annotations-reference.rst
    +++ b/en/reference/annotations-reference.rst
    @@ -525,7 +525,7 @@ Optional attributes:
     -  **mappedBy**: This option specifies the property name on the
        targetEntity that is the owning side of this relation. Its a
        required attribute for the inverse side of a relationship.
    --  **inversedBy**: The inversedBy attribute designates the field in the
    +-  **inversedBy**: The inversedBy attribute designates the field in the
        entity that is the inverse side of the relationship.
     -  **cascade**: Cascade Option
     -  **fetch**: One of LAZY or EAGER
    @@ -599,7 +599,7 @@ Optional attributes:
     -  **orphanRemoval**: Boolean that specifies if orphans, inverse
        OneToOne entities that are not connected to any owning instance,
        should be removed by Doctrine. Defaults to false.
    --  **inversedBy**: The inversedBy attribute designates the field in the
    +-  **inversedBy**: The inversedBy attribute designates the field in the
        entity that is the inverse side of the relationship.
     
     Example:
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index e592b9e02..29ba90c47 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -1119,7 +1119,7 @@ There are situations when a query you want to execute returns a
     very large result-set that needs to be processed. All the
     previously described hydration modes completely load a result-set
     into memory which might not be feasible with large result sets. See
    -the `Batch Processing `_ section on details how
    +the :doc:`Batch Processing ` section on details how
     to iterate large result sets.
     
     Functions
    
    From f58dcbaa8abc90a018dbb48b2678191d24b82d62 Mon Sep 17 00:00:00 2001
    From: "Berengar W. Lehr" 
    Date: Wed, 18 May 2011 20:08:49 +0200
    Subject: [PATCH 219/430] 1. Replaced "fi"-ligature to actually "fi"-letters as
     pdflatex inputenc package does not support that unicode character 2. Replaced
     external link to internal document with internal link (now actuall link in
     documentation should work)
    
    ---
     en/reference/annotations-reference.rst       | 4 ++--
     en/reference/dql-doctrine-query-language.rst | 2 +-
     2 files changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst
    index 18328534f..e444ca7b9 100644
    --- a/en/reference/annotations-reference.rst
    +++ b/en/reference/annotations-reference.rst
    @@ -525,7 +525,7 @@ Optional attributes:
     -  **mappedBy**: This option specifies the property name on the
        targetEntity that is the owning side of this relation. Its a
        required attribute for the inverse side of a relationship.
    --  **inversedBy**: The inversedBy attribute designates the field in the
    +-  **inversedBy**: The inversedBy attribute designates the field in the
        entity that is the inverse side of the relationship.
     -  **cascade**: Cascade Option
     -  **fetch**: One of LAZY or EAGER
    @@ -599,7 +599,7 @@ Optional attributes:
     -  **orphanRemoval**: Boolean that specifies if orphans, inverse
        OneToOne entities that are not connected to any owning instance,
        should be removed by Doctrine. Defaults to false.
    --  **inversedBy**: The inversedBy attribute designates the field in the
    +-  **inversedBy**: The inversedBy attribute designates the field in the
        entity that is the inverse side of the relationship.
     
     Example:
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index 29ba90c47..e592b9e02 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -1119,7 +1119,7 @@ There are situations when a query you want to execute returns a
     very large result-set that needs to be processed. All the
     previously described hydration modes completely load a result-set
     into memory which might not be feasible with large result sets. See
    -the :doc:`Batch Processing ` section on details how
    +the `Batch Processing `_ section on details how
     to iterate large result sets.
     
     Functions
    
    From 457abbacefaa645de09d5ffbd2c43b568cb41f46 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Renan=20Gon=C3=A7alves=20aka=20renan=2Esaddam?=
     
    Date: Thu, 19 May 2011 06:20:50 -0700
    Subject: [PATCH 220/430] Version 2.0.5 no longer needs $cli call and the
     namespace for Components has changed.
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 3 +--
     1 file changed, 1 insertion(+), 2 deletions(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 14069f4ea..8b24c3c2c 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -734,10 +734,9 @@ doctrine command. Its a fairly simple file:
     .. code-block:: php
     
          new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager)
         ));
    -    $cli->setHelperSet($helperSet);
     
     You can then change into your project directory and call the
     Doctrine command-line tool:
    
    From 729ad9e5c99cb7c26d557ee9be62aaf3d1c22c3f Mon Sep 17 00:00:00 2001
    From: Eriksen Costa 
    Date: Thu, 2 Jun 2011 00:14:46 -0300
    Subject: [PATCH 221/430] fixed constants typos in "Entity State"
    
    ---
     en/reference/working-with-objects.rst | 8 ++++----
     1 file changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/en/reference/working-with-objects.rst b/en/reference/working-with-objects.rst
    index f737807d3..736401ac8 100644
    --- a/en/reference/working-with-objects.rst
    +++ b/en/reference/working-with-objects.rst
    @@ -604,13 +604,13 @@ underlying ``UnitOfWork``:
     
         getUnitOfWork()->getEntityState($entity)) {
    -        case UnitOfWork::MANAGED:
    +        case UnitOfWork::STATE_MANAGED:
                 ...
    -        case UnitOfWork::REMOVED:
    +        case UnitOfWork::STATE_REMOVED:
                 ...
    -        case UnitOfWork::DETACHED:
    +        case UnitOfWork::STATE_DETACHED:
                 ...
    -        case UnitOfWork::NEW:
    +        case UnitOfWork::STATE_NEW:
                 ...
         }
     
    
    From a5cfd2321f06435a86177663ea5fdc7b855b2c3d Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 5 Jun 2011 17:21:11 +0200
    Subject: [PATCH 222/430] Document Mssql Datetim2 behavior
    
    ---
     en/reference/limitations-and-known-issues.rst | 5 +++++
     1 file changed, 5 insertions(+)
    
    diff --git a/en/reference/limitations-and-known-issues.rst b/en/reference/limitations-and-known-issues.rst
    index 3a042cfa9..64134251c 100644
    --- a/en/reference/limitations-and-known-issues.rst
    +++ b/en/reference/limitations-and-known-issues.rst
    @@ -316,3 +316,8 @@ support all CRUD operations on views that semantically map to
     certain tables. You can create views for all your problematic
     tables and column names to avoid the legacy quoting nightmare.
     
    +Microsoft SQL Server and Doctrine "datetime"
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +Doctrine assumes that you use DateTime2 data-types. If your legacy database contains DateTime
    +datatypes then you have to add your own data-type (see Basic Mapping for an example).
    \ No newline at end of file
    
    From 6f909b6e695af7491a4fea97f8e51e0a6378003d Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 11 Jun 2011 08:59:33 +0200
    Subject: [PATCH 223/430] Clarifications and additions in DQL and Working with
     objects chapter
    
    ---
     en/reference/dql-doctrine-query-language.rst | 5 ++++-
     en/reference/working-with-objects.rst        | 7 +++++++
     2 files changed, 11 insertions(+), 1 deletion(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index e592b9e02..e26d8ecab 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -253,6 +253,9 @@ Using Aggregate Functions:
         $query = $em->createQuery('SELECT COUNT(u.id) FROM Entities\User u');
         $count = $query->getSingleScalarResult();
     
    +    $query = $em->createQuery('SELECT u, count(g.id) FROM Entities\User u JOIN u.groups g GROUP BY u.id');
    +    $result  $query->getResult();
    +
     With WHERE Clause and Positional Parameter:
     
     .. code-block:: php
    @@ -916,7 +919,7 @@ structure:
     
     .. code-block:: php
     
    -    $dql = "SELECT u, 'some scalar string', count(u.groups) AS num FROM User u";
    +    $dql = "SELECT u, 'some scalar string', count(u.groups) AS num FROM User u JOIN u.groups g GROUP BY u.id";
     
         array
             [0]
    diff --git a/en/reference/working-with-objects.rst b/en/reference/working-with-objects.rst
    index 736401ac8..a47b13ef4 100644
    --- a/en/reference/working-with-objects.rst
    +++ b/en/reference/working-with-objects.rst
    @@ -690,6 +690,13 @@ methods on a repository as follows:
         // A single user by its nickname
         $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
     
    +You can also load by owning side associations through the repository:
    +
    +    $number = $em->find('MyProject\Domain\Phonenumber', 1234);
    +    $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId()));
    +
    +Take not that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself.
    +
     An EntityRepository also provides a mechanism for more concise
     calls through its use of ``__call``. Thus, the following two
     examples are equivalent:
    
    From 71c47f69ec2af71d6aad3465e6691dd42241a0c9 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 11 Jun 2011 09:52:17 +0200
    Subject: [PATCH 224/430] Mention 5.3 dependency in getting started tutorial,
     and link to entity restrictions aswell. Add the func_get_args() restriction
     to the list in architecture.
    
    ---
     en/reference/architecture.rst                |  3 +++
     en/tutorials/getting-started-xml-edition.rst | 10 ++++++----
     2 files changed, 9 insertions(+), 4 deletions(-)
    
    diff --git a/en/reference/architecture.rst b/en/reference/architecture.rst
    index 362ff9ea1..9a1d5ee44 100644
    --- a/en/reference/architecture.rst
    +++ b/en/reference/architecture.rst
    @@ -28,6 +28,9 @@ be any regular PHP class observing the following restrictions:
        property with the same name. That is, if B inherits from A then B
        must not have a mapped field with the same name as an already
        mapped field that is inherited from A.
    +-  An entity cannot make use of func_get_args() to implement variable parameters.
    +   Generated proxies do not support this for performance reasons and your code might
    +   actually fail to work when violating this restriction.
     
     Entities support inheritance, polymorphic associations, and
     polymorphic queries. Both abstract and concrete classes can be
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 8b24c3c2c..8a9facd00 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -1,12 +1,11 @@
     Getting Started
     ===============
     
    -Doctrine 2 is a project that aims to handle the persistence of the
    -domain model in a non-interfering way. The Data Mapper pattern is
    -at the heart of this project, aiming for a complete separation of
    +Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides transparent persistence for PHP objects.
    +It uses the Data Mapper pattern at the heart of this project, aiming for a complete separation of
     the domain/business logic from the persistence in a relational
     database management system. The benefit of Doctrine for the
    -programmer is the ability to focus solely on the business logic and
    +programmer is the ability to focus solely on the object-oriented business logic and
     worry about persistence only as a secondary task. This doesn't mean
     persistence is not important to Doctrine 2, however it is our
     belief that there are considerable benefits for object-oriented
    @@ -21,6 +20,9 @@ abstract base class or interface. An entity class must not be final
     or contain final methods. Additionally it must not implement
     **clone** nor **wakeup** or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
     
    +See the `architecture chapter <../reference/architecture>` for a full list of the restrictions
    +that your entities need to comply with.
    +
     An entity contains persistable properties. A persistable property
     is an instance variable of the entity that is saved into and retrieved from the database
     by Doctrine's data mapping capabilities.
    
    From 06e45f75877daef7fa0647d874ffe9a5f6271bcf Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 11 Jun 2011 09:59:17 +0200
    Subject: [PATCH 225/430] Fix bug in previous commit
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 8a9facd00..24ea4ab67 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -20,7 +20,7 @@ abstract base class or interface. An entity class must not be final
     or contain final methods. Additionally it must not implement
     **clone** nor **wakeup** or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
     
    -See the `architecture chapter <../reference/architecture>` for a full list of the restrictions
    +See the :doc:`architecture chapter <../reference/architecture>` for a full list of the restrictions
     that your entities need to comply with.
     
     An entity contains persistable properties. A persistable property
    
    From 6816816101975460c7b196c956ef63bf09ba0fb2 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 11 Jun 2011 10:07:18 +0200
    Subject: [PATCH 226/430] Fix bug in previous commit
    
    ---
     en/reference/dql-doctrine-query-language.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index e26d8ecab..de26eda9a 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -254,7 +254,7 @@ Using Aggregate Functions:
         $count = $query->getSingleScalarResult();
     
         $query = $em->createQuery('SELECT u, count(g.id) FROM Entities\User u JOIN u.groups g GROUP BY u.id');
    -    $result  $query->getResult();
    +    $result = $query->getResult();
     
     With WHERE Clause and Positional Parameter:
     
    
    From b5827ea83fafe23d5109d44009387f8c11d334f1 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 12 Jun 2011 23:07:21 +0200
    Subject: [PATCH 227/430] Add tons of xml and yaml configuration blocks to
     basic- and association-mapping chapters
    
    ---
     en/reference/association-mapping.rst | 315 ++++++++++++++++++++-------
     en/reference/basic-mapping.rst       | 281 ++++++++++++++++++------
     2 files changed, 449 insertions(+), 147 deletions(-)
    
    diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst
    index a992c15b4..869fc1235 100644
    --- a/en/reference/association-mapping.rst
    +++ b/en/reference/association-mapping.rst
    @@ -133,57 +133,151 @@ follows:
     
     As an example, consider this mapping:
     
    -.. code-block:: php
    +.. configuration-block::
     
    -    
    +            
    +                
    +            
    +        
    +
    +    .. code-block:: yaml
    +
    +        Product:
    +          type: entity
    +          oneToOne:
    +            shipping:
    +              targetEntity: Shipping
     
     This is essentially the same as the following, more verbose,
     mapping:
     
    -.. code-block:: php
    +.. configuration-block::
     
    -    
    +            
    +                
    +                    
    +                
    +            
    +        
    +
    +    .. code-block:: yaml
    +
    +        Product:
    +          type: entity
    +          oneToOne:
    +            shipping:
    +              targetEntity: Shipping
    +              joinColumn:
    +                name: shipping_id
    +                referencedColumnName: id
     
     The @JoinTable definition used for many-to-many mappings has
     similar defaults. As an example, consider this mapping:
     
    -.. code-block:: php
    +.. code-configuration::
     
    -    
    +            
    +                
    +            
    +        
    +
    +    .. code-block:: yaml
    +
    +        User:
    +          type: entity
    +          manyToMany:
    +            groups:
    +              targetEntity: Group
     
     This is essentially the same as the following, more verbose,
     mapping:
     
    -.. code-block:: php
    +.. configuration-block::
     
    -    
    +            
    +                
    +                    
    +                        
    +                            
    +                        
    +                        
    +                            
    +                        
    +                    
    +                
    +            
    +        
    +
    +    .. code-block:: yaml
    +
    +        User:
    +          type: entity
    +          manyToMany:
    +            groups:
    +              targetEntity: Group
    +              joinTable:
    +                name: User_Group
    +                joinColumns:
    +                  User_id:
    +                    referencedColumnName: id
    +                inverseJoinColumns:
    +                  Group_id
    +                    referencedColumnName: id                  
     
     In that case, the name of the join table defaults to a combination
     of the simple, unqualified class names of the participating
    @@ -276,6 +370,7 @@ Or you can trigger the validation manually:
     
     .. code-block:: php
     
    +    
    +            
    +                
    +                    
    +                
    +            
    +        
    +
    +    .. code-block:: yaml
    +
    +        Product:
    +          type: entity
    +          oneToOne:
    +            shipping:
    +              targetEntity: Shipping
    +              joinColumn:
    +                name: shipping_id
    +                referencedColumnName: id
     
     Note that the @JoinColumn is not really necessary in this example,
     as the defaults would be the same.
    @@ -354,35 +472,66 @@ Here is a one-to-one relationship between a ``Customer`` and a
     ``Cart``. The ``Cart`` has a reference back to the ``Customer`` so
     it is bidirectional.
     
    -.. code-block:: php
    +.. configuration-block:: 
     
    -    
    +            
    +                
    +            
    +            
    +                
    +                    
    +                
    +            
    +        
    +
    +    .. code-block:: yaml
    +
    +        Customer:
    +          oneToOne:
    +            cart:
    +              targetEntity: Cart
    +              mappedBy: customer
    +        Cart:
    +          oneToOne:
    +            customer:
    +              targetEntity Customer
    +              inversedBy: cart
    +              joinColumn:
    +                customer_id:
    +                   referencedColumnName: id
     
     Note that the @JoinColumn is not really necessary in this example,
     as the defaults would be the same.
    diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst
    index 970782279..0a00ee702 100644
    --- a/en/reference/basic-mapping.rst
    +++ b/en/reference/basic-mapping.rst
    @@ -16,9 +16,11 @@ object-relational mapping metadata:
     -  XML
     -  YAML
     
    -This manual usually uses docblock annotations in all the examples
    -that are spread throughout all chapters. There are dedicated
    -chapters for XML and YAML mapping, respectively.
    +This manual usually mentions docblock annotations in all the examples
    +that are spread throughout all chapters, however for many examples
    +alternative YAML and XML examples are given aswell. There are dedicated
    +reference chapters for XML and YAML mapping, respectively that explain them
    +in more detail. There is also an Annotation reference chapter.
     
     .. note::
     
    @@ -56,7 +58,9 @@ annotations support namespaces and nested annotations among other
     things. The Doctrine 2 ORM defines its own set of docblock
     annotations for supplying object-relational mapping metadata.
     
    -    **NOTE** If you're not comfortable with the concept of docblock
    +.. note::
    +
    +    If you're not comfortable with the concept of docblock
         annotations, don't worry, as mentioned earlier Doctrine 2 provides
         XML and YAML alternatives and you could easily implement your own
         favourite mechanism for defining ORM metadata.
    @@ -69,30 +73,63 @@ In order to mark a class for object-relational persistence it needs
     to be designated as an entity. This can be done through the
     ``@Entity`` marker annotation.
     
    -.. code-block:: php
    +.. configuration-block::
     
    -    
    +          
    +              
               
             
     
    @@ -120,7 +120,7 @@ name as the class name. In order to change that, you can use the
     
             
               
    -              
               
             
     
    
    From de5952f597c695a81892331a4ca3af247cd89c3c Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Thu, 16 Jun 2011 21:52:51 +0200
    Subject: [PATCH 236/430] Fix association mappings
    
    ---
     en/reference/association-mapping.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst
    index b00b9248e..e46fd7df2 100644
    --- a/en/reference/association-mapping.rst
    +++ b/en/reference/association-mapping.rst
    @@ -601,7 +601,7 @@ the join columns enforces the one-to-many cardinality. The
     following example sets up such a unidirectional one-to-many
     association:
     
    -.. configuration-block::
    +.. configuration-block
     
         .. code-block:: php
     
    
    From 36e500682a793c2fdf1b8b56a6a6065187cdf33d Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Mon, 20 Jun 2011 21:20:23 +0200
    Subject: [PATCH 237/430] Started FAQ
    
    ---
     en/index.rst         |   1 +
     en/reference/faq.rst | 124 +++++++++++++++++++++++++++++++++++++++++++
     2 files changed, 125 insertions(+)
     create mode 100644 en/reference/faq.rst
    
    diff --git a/en/index.rst b/en/index.rst
    index 2164541c8..a577e01e9 100644
    --- a/en/index.rst
    +++ b/en/index.rst
    @@ -11,6 +11,7 @@ Reference Guide
        reference/introduction
        reference/architecture
        reference/configuration
    +   reference/faq
        reference/basic-mapping
        reference/association-mapping
        reference/inheritance-mapping
    diff --git a/en/reference/faq.rst b/en/reference/faq.rst
    new file mode 100644
    index 000000000..d103b8bc5
    --- /dev/null
    +++ b/en/reference/faq.rst
    @@ -0,0 +1,124 @@
    +Frequently Asked Questions
    +==========================
    +
    +.. note::
    +
    +    This FAQ is a work in progress. We will add lots of questions and not answer them right away just to remember
    +    what is often asked. If you stumble accross an unanswerd question please write a mail to the mailing-list or
    +    join the #doctrine channel on Freenode IRC.
    +
    +Entity Classes
    +--------------
    +
    +I access a variable and its null, what is wrong?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +Proxy, not use public. Private and protected variables instead.
    +
    +How can I add default values to a column?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +Doctrine does not support to set the default values in columns through the "DEFAULT" keyword in SQL.
    +This is not necessary however, you can just use your class properties as default values. These are then used
    +upon insert:
    +
    +.. code-block::
    +
    +    class User
    +    {
    +        const STATUS_DISABLED = 0;
    +        const STATUS_ENABLED = 1;
    +
    +        private $algorithm = "sha1";
    +        private $status = self:STATUS_DISABLED;
    +    }
    +
    +.
    +
    +Mapping
    +-------
    +
    +Why do I get exceptions about unique constraint failures during ``$em->flush()``?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +-
    +
    +Associations
    +------------
    +
    +What is wrong when I get an InvalidArgumentException "A new entity was found through the relationship.."?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +
    +How can I filter an association?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +Natively you can't in 2.0 and 2.1. You should use DQL queries to query for the filtered set of entities.
    +
    +I call clear() on a One-To-Many collection but the entities are not deleted
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +.
    +
    +How can I add columns to a many-to-many table?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +.
    +
    +
    +How can i paginate fetch-joined collections?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +.
    +
    +Why does pagination not work correctly with fetch joins?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +.
    +
    +Inheritance
    +-----------
    +
    +Can I use Inheritance with Doctrine 2?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + 
    +.
    +
    +EntityGenerator
    +---------------
    +
    +Why does the EntityGenerator not do X?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +.
    +
    +Why does the EntityGenerator not generate inheritance correctly?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +.
    +
    +
    +
    +.
    +
    +Performance
    +-----------
    +
    +Why is an extra SQL query executed every time I fetch an entity with a one-to-one relation?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +.
    +
    +Doctrine Query Language
    +-----------------------
    +
    +What is DQL and why does it have such a strange syntax?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +Can I sort by a function (for example ORDER BY RAND()) in DQL?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +No, it is not supported to sort by function in DQL. If you need this functionality you should either
    +use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow
    +starting with 1000 rows.
    +
    
    From ad012a63cbc8de900e247e8fb869bcf4297fef8b Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Nicolas=20de=20Marqu=C3=A9=20Fromentin?=
     
    Date: Tue, 28 Jun 2011 16:22:14 +0200
    Subject: [PATCH 238/430] Little typo
    
    ---
     en/reference/association-mapping.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst
    index e46fd7df2..bd46217a1 100644
    --- a/en/reference/association-mapping.rst
    +++ b/en/reference/association-mapping.rst
    @@ -530,8 +530,8 @@ it is bidirectional.
                   targetEntity Customer
                   inversedBy: cart
                   joinColumn:
    -                customer_id:
    -                   referencedColumnName: id
    +                name: customer_id:
    +                referencedColumnName: id
     
     Note that the @JoinColumn is not really necessary in this example,
     as the defaults would be the same.
    
    From 8af162711e878c8387c0ad8019552c0bd89afe62 Mon Sep 17 00:00:00 2001
    From: Michael Ridgway 
    Date: Tue, 28 Jun 2011 16:34:12 -0400
    Subject: [PATCH 239/430] Removed onUpdate property
    
    ---
     en/reference/annotations-reference.rst | 1 -
     1 file changed, 1 deletion(-)
    
    diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst
    index e444ca7b9..932ec4539 100644
    --- a/en/reference/annotations-reference.rst
    +++ b/en/reference/annotations-reference.rst
    @@ -401,7 +401,6 @@ Optional attributes:
     -  **nullable**: Determine if the related entity is required, or if
        null is an allowed state for the relation. Defaults to true.
     -  **onDelete**: Cascade Action (Database-level)
    --  **onUpdate**: Cascade Action (Database-level)
     -  **columnDefinition**: DDL SQL snippet that starts after the column
        name and specifies the complete (non-portable!) column definition.
        This attribute allows to make use of advanced RMDBS features. Using
    
    From 09d5169a8b638a369cd9ecef7d87fa08ce0b45c8 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Tue, 28 Jun 2011 23:28:55 +0200
    Subject: [PATCH 240/430] Remove methods that not exist anymore from Expr
     object documentation
    
    ---
     en/reference/query-builder.rst | 23 -----------------------
     1 file changed, 23 deletions(-)
    
    diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst
    index 1195be59a..51adcb2b1 100644
    --- a/en/reference/query-builder.rst
    +++ b/en/reference/query-builder.rst
    @@ -268,29 +268,6 @@ complete list of supported helper methods available:
         expr()->select('u')
    -        public function select($select = null); // Returns Expr\Select instance
    -    
    -        // Example - $qb->expr()->from('User', 'u')
    -        public function from($from, $alias); // Returns Expr\From instance
    -    
    -        // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', Expr\Join::ON, 'p.user_id = u.id AND p.country_code = 55');
    -        // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55'));
    -        public function leftJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance
    -    
    -        // Example - $qb->expr()->innerJoin('u.Group', 'g', Expr\Join::WITH, 'g.manager_level = 100');
    -        // Example - $qb->expr()->innerJoin('u.Group', 'g', 'WITH', $qb->expr()->eq('g.manager_level', '100'));
    -        public function innerJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance
    -    
    -        // Example - $qb->expr()->orderBy('u.surname', 'ASC')->add('u.firstname', 'ASC')->...
    -        public function orderBy($sort = null, $order = null); // Returns Expr\OrderBy instance
    -    
    -        // Example - $qb->expr()->groupBy()->add('u.id')->...
    -        public function groupBy($groupBy = null); // Returns Expr\GroupBy instance
    -    
    -    
             /** Conditional objects **/        
         
             // Example - $qb->expr()->andx($cond1 [, $condN])->add(...)->...
    
    From 653add64f936934a239d729d63ca8826cc3d66ac Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 9 Jul 2011 21:26:13 +0200
    Subject: [PATCH 241/430] Fix bug in mappings of Aggregate Column cookbook
     entry
    
    ---
     en/cookbook/aggregate-fields.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/cookbook/aggregate-fields.rst b/en/cookbook/aggregate-fields.rst
    index 521f5d025..5d0981b06 100644
    --- a/en/cookbook/aggregate-fields.rst
    +++ b/en/cookbook/aggregate-fields.rst
    @@ -46,7 +46,7 @@ Our entities look like:
             private $no;
         
             /**
    -         * @OneToMany(targetEntity="Entry", mappedBy="entries", cascade={"persist"})
    +         * @OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"})
              */
             private $entries;
         
    
    From fac1a517df4eac193330c106c1a9b1db605819af Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 10 Jul 2011 20:03:59 +0200
    Subject: [PATCH 242/430] Finish chapter on composite primary keys (still
     missing XML and YAML example though)
    
    ---
     en/tutorials/composite-primary-keys.rst | 109 +++++++++++++++++++++++-
     1 file changed, 106 insertions(+), 3 deletions(-)
    
    diff --git a/en/tutorials/composite-primary-keys.rst b/en/tutorials/composite-primary-keys.rst
    index 8048fbb78..7c7fa9baa 100644
    --- a/en/tutorials/composite-primary-keys.rst
    +++ b/en/tutorials/composite-primary-keys.rst
    @@ -198,14 +198,117 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
     Use-Case 2: Simple Derived Identity
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -TODO
    +Sometimes you have the requirement that two objects are related by a One-To-One association
    +and that the dependent class should re-use the primary key of the class it depends on.
    +One good example for this is a user-address relationship:
    +
    +.. code-block:: php
    +
    +    customer = $customer;
    +            $this->items = new ArrayCollection();
    +            $this->created = new \DateTime("now");
    +        }
    +    }
    +
    +    /** @Entity */
    +    class Product
    +    {
    +        /** @Id @Column(type="integer") @GeneratedValue */
    +        private $id;
    +
    +        /** @Column(type="string")
    +        private $name;
    +
    +        /** @Column(type="decimal")
    +        private $currentPrice;
    +
    +        public function getCurrentPrice()
    +        {
    +            return $this->currentPrice;
    +        }
    +    }
    +
    +    /** @Entity */
    +    class OrderItem
    +    {
    +        /** @Id @ManyToOne(targetEntity="Order") */
    +        private $order;
    +
    +        /** @Id @ManyToOne(targetEntity="Product") */
    +        private $product;
    +
    +        /** @Column(type="integer") */
    +        private $amount = 1;
    +
    +        /** @Column(type="decimal") */
    +        private $offeredPrice;
    +
    +        public function __construct(Order $order, Product $product, $amount = 1)
    +        {
    +            $this->order = $order;
    +            $this->product = $product;
    +            $this->offeredPrice = $product->getCurrentPrice();
    +        }
    +    }
    +
     
     Performance Considerations
     ~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -TODO
    \ No newline at end of file
    +Using composite keys always comes with a performance hit compared to using entities with
    +a simple surrogate key. This performance impact is mostly due to additional PHP code that is
    +necessary to handle this kind of keys, most notably when using derived identifiers.
    +
    +On the SQL side there is not much overhead as no additional or unexpected queries have to be
    +executed to manage entities with derived foreign keys.
    \ No newline at end of file
    
    From 609e3b8c925cd165c0a9bf66f96c75b4cf67c126 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 10 Jul 2011 20:31:16 +0200
    Subject: [PATCH 243/430] Write something about all FAQ entries that were empty
    
    ---
     en/reference/faq.rst | 90 ++++++++++++++++++++++++++++++++++++--------
     1 file changed, 75 insertions(+), 15 deletions(-)
    
    diff --git a/en/reference/faq.rst b/en/reference/faq.rst
    index d103b8bc5..5d33906ff 100644
    --- a/en/reference/faq.rst
    +++ b/en/reference/faq.rst
    @@ -13,7 +13,8 @@ Entity Classes
     I access a variable and its null, what is wrong?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -Proxy, not use public. Private and protected variables instead.
    +If this variable is a public variable then you are violating one of the criteria for entities.
    +All properties have to be protected or private for the proxy object pattern to work.
     
     How can I add default values to a column?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @@ -41,7 +42,18 @@ Mapping
     Why do I get exceptions about unique constraint failures during ``$em->flush()``?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    --
    +Doctrine does not check if you are re-adding entities with a primary key that already exists
    +or adding entities to a collection twice. You have to check for both conditions yourself
    +in the code before calling ``$em->flush()`` if you know that unique constraint failures
    +can occur.
    +
    +In `Symfony 2`_ for example there is a Unique Entity Validator
    +to achieve this task.
    +
    +For collections you can check with ``$collection->contains($entity)`` if an entity is already
    +part of this collection. For a FETCH=LAZY collection this will initialize the collection,
    +however for FETCH=EXTRA_LAZY this method will use SQL to determine if this entity is already
    +part of the collection.
     
     Associations
     ------------
    @@ -49,32 +61,64 @@ Associations
     What is wrong when I get an InvalidArgumentException "A new entity was found through the relationship.."?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    +This exception is thrown during ``EntityManager#flush()`` when there exists an object in the identity map
    +that contains a reference to an object that Doctrine does not know about. Say for example you grab
    +a "User"-entity from the database with a specific id and set a completly new object into one of the associations
    +of the User object. If you then call ``EntityManager#flush()`` without letting Doctrine know about
    +this new object using ``EntityManager#persist($newObject)`` you will see this exception.
    +
    +You can solve this exception by:
    +
    +* Calling ``EntityManager#persist($newObject)`` on the new object
    +* Using cascade=persist on the association that contains the new object
     
     How can I filter an association?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -Natively you can't in 2.0 and 2.1. You should use DQL queries to query for the filtered set of entities.
    +Natively you can't filter associations in 2.0 and 2.1. You should use DQL queries to query for the filtered set of entities.
     
     I call clear() on a One-To-Many collection but the entities are not deleted
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -.
    +This is an expected behavior that has to do with the inverse/owning side handling of Doctrine.
    +By definition a One-To-Many association is on the inverse side, that means changes to it
    +will not be recognized by Doctrine.
    +
    +If you want to perform the equivalent of the clear operation you have to iterate the
    +collection and set the owning side many-to-one reference to NULL aswell to detach all entities
    +from the collection. This will trigger the appropriate UPDATE statements on the database.
     
     How can I add columns to a many-to-many table?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -.
    +The many-to-many association is only supporting foreign keys in the table definition
    +To work with many-to-many tables containing extra columns you have to use the
    +foreign keys as primary keys feature of Doctrine introduced in version 2.1.
    +
    +See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
     
     
     How can i paginate fetch-joined collections?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -.
    +If you are issuing a DQL statement that fetches a collection as well you cannot easily iterate
    +over this collection using a LIMIT statement (or vendor equivalent).
    +
    +Doctrine does not offer a solution for this out of the box but there are several extensions
    +that do:
    +
    +* `DoctrineExtensions `_
    +* `Pagerfanta `_
     
     Why does pagination not work correctly with fetch joins?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -.
    +Pagination in Doctrine uses a LIMIT clause (or vendor equivalent) to restrict the results.
    +However when fetch-joining this is not returning the correct number of results since joining
    +with a one-to-many or many-to-many association muliplies the number of rows by the number
    +of associated entities.
    +
    +See the previous question for a solution to this task.
     
     Inheritance
     -----------
    @@ -82,7 +126,10 @@ Inheritance
     Can I use Inheritance with Doctrine 2?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      
    -.
    +Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2.
    +
    +See the documentation chapter on :doc:`inheritance mapping `_ for
    +the details.
     
     EntityGenerator
     ---------------
    @@ -95,10 +142,6 @@ Why does the EntityGenerator not do X?
     Why does the EntityGenerator not generate inheritance correctly?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -.
    -
    -
    -
     .
     
     Performance
    @@ -107,13 +150,30 @@ Performance
     Why is an extra SQL query executed every time I fetch an entity with a one-to-one relation?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -.
    +If Doctrine detects that you are fetching an inverse side one-to-one association
    +it has to execute an additional query to load this object, because it cannot know
    +if there is no such object (setting null) or if it should set a proxy and which id this proxy has.
    +
    +To solve this problem currently a query has to be executed to find out this information.
     
     Doctrine Query Language
     -----------------------
     
    -What is DQL and why does it have such a strange syntax?
    -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +What is DQL?
    +~~~~~~~~~~~~
    +
    +DQL stands for Doctrine Query Language, a query language that very much looks like SQL
    +but has some important benefits when using Doctrine:
    +
    +* It uses class names and fields instead of tables and columns, separating concerns between backend and your object model.
    +* It utilizes the metadata defined to offer a range of shortcuts when writing. For example you do not have to specify the ON clause of joins, since Doctrine already knows about them.
    +* It adds some functionality that is related to object management and transforms them into SQL.
    +
    +It also has some drawbacks of course:
    +
    +* The syntax is slightly different to SQL so you have to learn and remember the differences.
    +* To be vendor independent it can only implement a subset of all the existing SQL dialects. Vendor specific functionality and optimizations cannot be used through DQL unless implemented by you explicitly.
    +* For some DQL constructs subselects are used which are known to be slow in MySQL.
     
     Can I sort by a function (for example ORDER BY RAND()) in DQL?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    From 82419813df46fc984bdf787688dd95c68502144c Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 10 Jul 2011 20:34:34 +0200
    Subject: [PATCH 244/430] Another inheritance question
    
    ---
     en/reference/faq.rst | 7 +++++++
     1 file changed, 7 insertions(+)
    
    diff --git a/en/reference/faq.rst b/en/reference/faq.rst
    index 5d33906ff..019ab1bdd 100644
    --- a/en/reference/faq.rst
    +++ b/en/reference/faq.rst
    @@ -131,6 +131,13 @@ Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2.
     See the documentation chapter on :doc:`inheritance mapping `_ for
     the details.
     
    +Why does Doctrine not create proxy objects for my inheritance hierachy?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +If you set a many-to-one or one-to-one association target-entity to any parent class of
    +an inheritance hierachy Doctrine does not know what PHP class the foreign is actually of.
    +To find this out it has to execute an SQL query to look this information up in the database.
    +
     EntityGenerator
     ---------------
     
    
    From 175faeb5f22b6287ca253e52bcd0406fcc68fc1d Mon Sep 17 00:00:00 2001
    From: Michael Ridgway 
    Date: Mon, 11 Jul 2011 13:25:15 -0400
    Subject: [PATCH 245/430] [DDC-1270] Fixed invalid expr()->*() function calls;
     Added isNull and isNotNull functions; Fixed casing on orX and andX
    
    ---
     en/reference/query-builder.rst | 34 ++++++++++++++++++++--------------
     1 file changed, 20 insertions(+), 14 deletions(-)
    
    diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst
    index 51adcb2b1..521f31d2a 100644
    --- a/en/reference/query-builder.rst
    +++ b/en/reference/query-builder.rst
    @@ -240,10 +240,10 @@ To simplify some of these efforts, we introduce what we call as
     The Expr class
     ^^^^^^^^^^^^^^
     
    -To workaround most of the issues that ``add()`` method may cause,
    +To workaround some of the issues that ``add()`` method may cause,
     Doctrine created a class that can be considered as a helper for
    -building queries. This class is called ``Expr``, which provides a
    -set of useful static methods to help building queries:
    +building expressions. This class is called ``Expr``, which provides a
    +set of useful methods to help build expressions:
     
     .. code-block:: php
     
    @@ -251,13 +251,13 @@ set of useful static methods to help building queries:
         // $qb instanceof QueryBuilder
         
         // example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.surname DESC" using Expr class
    -    $qb->add('select', $qb->expr()->select('u'))
    -       ->add('from', $qb->expr()->from('User', 'u'))
    -       ->add('where', $qb->expr()->orx(
    +    $qb->add('select', new Expr\Select(array('u')))
    +       ->add('from', new Expr\From('User', 'u'))
    +       ->add('where', $qb->expr()->orX(
                $qb->expr()->eq('u.id', '?1'),
                $qb->expr()->like('u.nickname', '?2')
            ))
    -       ->add('orderBy', $qb->expr()->orderBy('u.surname', 'ASC'));
    +       ->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
     
     Although it still sounds complex, the ability to programmatically
     create conditions are the main feature of ``Expr``. Here it is a
    @@ -270,11 +270,11 @@ complete list of supported helper methods available:
         {
             /** Conditional objects **/        
         
    -        // Example - $qb->expr()->andx($cond1 [, $condN])->add(...)->...
    -        public function andx($x = null); // Returns Expr\Andx instance
    +        // Example - $qb->expr()->andX($cond1 [, $condN])->add(...)->...
    +        public function andX($x = null); // Returns Expr\AndX instance
         
    -        // Example - $qb->expr()->orx($cond1 [, $condN])->add(...)->...
    -        public function orx($x = null); // Returns Expr\Orx instance
    +        // Example - $qb->expr()->orX($cond1 [, $condN])->add(...)->...
    +        public function orX($x = null); // Returns Expr\OrX instance
         
         
             /** Comparison objects **/
    @@ -296,6 +296,12 @@ complete list of supported helper methods available:
         
             // Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1
             public function gte($x, $y); // Returns Expr\Comparison instance
    +
    +        // Example - $qb->expr()->isNull('u.id') => u.id IS NULL
    +        public function isNull($x); // Returns string
    +
    +        // Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
    +        public function isNotNull($x); // Returns string
         
         
             /** Arithmetic objects **/
    @@ -425,7 +431,7 @@ suggested standard way to build queries:
         // example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ?1 OR u.nickname LIKE ?2 ORDER BY u.surname DESC" using QueryBuilder helper methods
         $qb->select(array('u')) // string 'u' is converted to array internally
            ->from('User', 'u')
    -       ->where($qb->expr()->orx(
    +       ->where($qb->expr()->orX(
                $qb->expr()->eq('u.id', '?1'),
                $qb->expr()->like('u.nickname', '?2')
            ))
    @@ -469,11 +475,11 @@ Here is a complete list of helper methods available in
             // NOTE: ->where() overrides all previously set conditions
             //
             // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2'))
    -        // Example - $qb->where($qb->expr()->andx($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2')))
    +        // Example - $qb->where($qb->expr()->andX($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2')))
             // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2')    
             public function where($where);
         
    -        // Example - $qb->andWhere($qb->expr()->orx($qb->expr()->lte('u.age', 40), 'u.numChild = 0'))    
    +        // Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0'))
             public function andWhere($where);
         
             // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10));
    
    From f4074dbe1df079705c709a8073b37ed2cdba5c70 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Tue, 12 Jul 2011 22:58:24 +0200
    Subject: [PATCH 246/430] Removed solved limitations and added missing ones
    
    ---
     en/reference/limitations-and-known-issues.rst | 107 +-----------------
     1 file changed, 5 insertions(+), 102 deletions(-)
    
    diff --git a/en/reference/limitations-and-known-issues.rst b/en/reference/limitations-and-known-issues.rst
    index 64134251c..74d87c25d 100644
    --- a/en/reference/limitations-and-known-issues.rst
    +++ b/en/reference/limitations-and-known-issues.rst
    @@ -17,94 +17,12 @@ 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
    -~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +Join-Columns with non-primary keys
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -.. note::
    -
    -    Foreign keys as identifiers are currently in master and will be included in Doctrine 2.1
    -
    -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:
    -
    -.. code-block:: 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``.
    -
    -.. code-block:: 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):
    -
    -.. code-block:: php
    -
    -    `_.
    +It is not possible to use join columns pointing to non-primary keys. Doctrine will think these are the primary
    +keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance
    +reasons not validate the correctness of this settings at runtime but only through the Validate Schema command.
     
     Mapping Arrays to a Join Table
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @@ -223,21 +141,6 @@ benefit from custom persister implementations:
     -  `Evaluate possible ways in which stored-procedures can be used `_
     -  The previous Filter Rules Feature Request
     
    -Paginating Associations
    -~~~~~~~~~~~~~~~~~~~~~~~
    -
    -.. note::
    -
    -    Extra Lazy Collections are currently in master and will be included in Doctrine 2.1
    -
    -It is not possible to paginate only parts of an associations at the moment. You can always only
    -load the whole association/collection into memory. This is rather problematic for large collections,
    -but we already plan to add facilities to fix this for Doctrine 2.1
    -
    --  `DDC-546 New Fetch Mode EXTRA_LAZY `_
    --  `Blog: Working with large collections (Workaround) `_
    --  `LargeCollections Helper `_
    -
     Persist Keys of Collections
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    
    From c876f9edb2030efc756a13fe649bf9a02b25f207 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Tue, 12 Jul 2011 23:01:42 +0200
    Subject: [PATCH 247/430] Add warning to Association Mapping chapter that
     referencedColumnName have to primary key columns
    
    ---
     en/reference/association-mapping.rst | 5 +++++
     1 file changed, 5 insertions(+)
    
    diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst
    index bd46217a1..1b42dcc3f 100644
    --- a/en/reference/association-mapping.rst
    +++ b/en/reference/association-mapping.rst
    @@ -384,6 +384,11 @@ Or you can trigger the validation manually:
     If the mapping is invalid the errors array contains a positive
     number of elements with error messages.
     
    +.. warning::
    +
    +    One mapping option that is not validated is the use of the referenced column name.
    +    It has to point to the equivalent primary key otherwise Doctrine will not work.
    +
     .. note::
     
         One common error is to use a backlash in front of the
    
    From 660ead4b0e6879f73b91caae077661bd7aa5574e Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Wed, 13 Jul 2011 19:04:21 +0200
    Subject: [PATCH 248/430] FAQ and Composite Key renaming
    
    ---
     en/reference/faq.rst                    | 12 ++++++------
     en/tutorials/composite-primary-keys.rst |  4 ++--
     2 files changed, 8 insertions(+), 8 deletions(-)
    
    diff --git a/en/reference/faq.rst b/en/reference/faq.rst
    index 019ab1bdd..4e0ae7537 100644
    --- a/en/reference/faq.rst
    +++ b/en/reference/faq.rst
    @@ -172,15 +172,15 @@ What is DQL?
     DQL stands for Doctrine Query Language, a query language that very much looks like SQL
     but has some important benefits when using Doctrine:
     
    -* It uses class names and fields instead of tables and columns, separating concerns between backend and your object model.
    -* It utilizes the metadata defined to offer a range of shortcuts when writing. For example you do not have to specify the ON clause of joins, since Doctrine already knows about them.
    -* It adds some functionality that is related to object management and transforms them into SQL.
    +-  It uses class names and fields instead of tables and columns, separating concerns between backend and your object model.
    +-  It utilizes the metadata defined to offer a range of shortcuts when writing. For example you do not have to specify the ON clause of joins, since Doctrine already knows about them.
    +-  It adds some functionality that is related to object management and transforms them into SQL.
     
     It also has some drawbacks of course:
     
    -* The syntax is slightly different to SQL so you have to learn and remember the differences.
    -* To be vendor independent it can only implement a subset of all the existing SQL dialects. Vendor specific functionality and optimizations cannot be used through DQL unless implemented by you explicitly.
    -* For some DQL constructs subselects are used which are known to be slow in MySQL.
    +-  The syntax is slightly different to SQL so you have to learn and remember the differences.
    +-  To be vendor independent it can only implement a subset of all the existing SQL dialects. Vendor specific functionality and optimizations cannot be used through DQL unless implemented by you explicitly.
    +-  For some DQL constructs subselects are used which are known to be slow in MySQL.
     
     Can I sort by a function (for example ORDER BY RAND()) in DQL?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    diff --git a/en/tutorials/composite-primary-keys.rst b/en/tutorials/composite-primary-keys.rst
    index 7c7fa9baa..83ccd73ac 100644
    --- a/en/tutorials/composite-primary-keys.rst
    +++ b/en/tutorials/composite-primary-keys.rst
    @@ -1,5 +1,5 @@
    -Composite Primary Keys
    -======================
    +Composite and Foreign Keys as Primary Key
    +=========================================
     
     Doctrine 2 supports composite primary keys natively. Composite keys are a very powerful relational database concept
     and we took good care to make sure Doctrine 2 supports as many of the composite primary key use-cases.
    
    From 91b2c82c58bae2051df4e8c8b227c033df525143 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Wed, 13 Jul 2011 20:31:01 +0200
    Subject: [PATCH 249/430] Brought most of the documentation up to date on 2.1
    
    ---
     en/reference/annotations-reference.rst       |  10 +-
     en/reference/dql-doctrine-query-language.rst |  26 +++++
     en/reference/faq.rst                         |  25 +++-
     en/reference/improving-performance.rst       |  13 +++
     en/reference/native-sql.rst                  |  25 +++-
     en/reference/working-with-objects.rst        |  20 +++-
     en/reference/xml-mapping.rst                 |  32 +++++-
     en/tutorials/composite-primary-keys.rst      | 115 ++++++++++++-------
     en/tutorials/extra-lazy-associations.rst     |   4 -
     9 files changed, 213 insertions(+), 57 deletions(-)
    
    diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst
    index e444ca7b9..3f739800d 100644
    --- a/en/reference/annotations-reference.rst
    +++ b/en/reference/annotations-reference.rst
    @@ -528,14 +528,16 @@ Optional attributes:
     -  **inversedBy**: The inversedBy attribute designates the field in the
        entity that is the inverse side of the relationship.
     -  **cascade**: Cascade Option
    --  **fetch**: One of LAZY or EAGER
    +-  **fetch**: One of LAZY, EXTRA_LAZY or EAGER
    +-  **indexBy**: Index the collection by a field on the target entity.
     
    -    **NOTE** For ManyToMany bidirectional relationships either side may
    +.. note::
    +
    +    For ManyToMany bidirectional relationships either side may
         be the owning side (the side that defines the @JoinTable and/or
         does not make use of the mappedBy attribute, thus using a default
         join table).
     
    -
     Example:
     
     .. code-block:: php
    @@ -635,6 +637,8 @@ Optional attributes:
     -  **mappedBy**: This option specifies the property name on the
        targetEntity that is the owning side of this relation. Its a
        required attribute for the inverse side of a relationship.
    +-  **fetch**: One of LAZY, EXTRA_LAZY or EAGER.
    +-  **indexBy**: Index the collection by a field on the target entity.
     
     Example:
     
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index de26eda9a..bd32720d1 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -557,6 +557,9 @@ clauses:
     -  TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim
        the string by the given trim char, defaults to whitespaces.
     -  UPPER(str) - Return the upper-case of the given string.
    +-  DATE_ADD(date, days) - Add the number of days to a given date.
    +-  DATE_SUB(date, days) - Substract the number of days from a given date.
    +-  DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2.
     
     Arithmetic operators
     ~~~~~~~~~~~~~~~~~~~~
    @@ -1263,6 +1266,29 @@ number of results:
         entity might appear in many rows, effectively hydrating less than
         the specified number of results.
     
    +.. _dql-temporarily-change-fetch-mode:
    +
    +Temporarily change fetch mode in DQL
    +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    +
    +While normally all your associations are marked as lazy or extra lazy you will have cases where you are using DQL and don't want to
    +fetch join a second, third or fourth level of entities into your result, because of the increased cost of the SQL JOIN. You
    +can mark a many-to-one or one-to-one association as fetched temporarily to batch fetch these entities using a WHERE .. IN query.
    +
    +.. code-block:: php
    +
    +    createQuery("SELECT u FROM MyProject\User u");
    +    $query->setFetchMode("MyProject\User", "address", "EAGER");
    +    $query->execute();
    +
    +Given that there are 10 users and corresponding addresses in the database the executed queries will look something like:
    +
    +.. code-block:: sql
    +
    +    SELECT * FROM users;
    +    SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    +
     
     EBNF
     ----
    diff --git a/en/reference/faq.rst b/en/reference/faq.rst
    index 4e0ae7537..91d39a291 100644
    --- a/en/reference/faq.rst
    +++ b/en/reference/faq.rst
    @@ -7,6 +7,17 @@ Frequently Asked Questions
         what is often asked. If you stumble accross an unanswerd question please write a mail to the mailing-list or
         join the #doctrine channel on Freenode IRC.
     
    +Database Schema
    +---------------
    +
    +How do I set the charset and collation for MySQL tables?
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +You can't set these values inside the annotations, yml or xml mapping files. To make a database
    +work with the default charset and collation you should configure MySQL to use it as default charset,
    +or create the database with charset and collation details. This way they get inherited to all newly
    +created database tables and columns.
    +
     Entity Classes
     --------------
     
    @@ -23,7 +34,7 @@ Doctrine does not support to set the default values in columns through the "DEFA
     This is not necessary however, you can just use your class properties as default values. These are then used
     upon insert:
     
    -.. code-block::
    +.. code-block:: php
     
         class User
         {
    @@ -47,7 +58,7 @@ or adding entities to a collection twice. You have to check for both conditions
     in the code before calling ``$em->flush()`` if you know that unique constraint failures
     can occur.
     
    -In `Symfony 2`_ for example there is a Unique Entity Validator
    +In `Symfony 2 `_ for example there is a Unique Entity Validator
     to achieve this task.
     
     For collections you can check with ``$collection->contains($entity)`` if an entity is already
    @@ -128,7 +139,7 @@ Can I use Inheritance with Doctrine 2?
      
     Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2.
     
    -See the documentation chapter on :doc:`inheritance mapping `_ for
    +See the documentation chapter on :doc:`inheritance mapping ` for
     the details.
     
     Why does Doctrine not create proxy objects for my inheritance hierachy?
    @@ -144,12 +155,16 @@ EntityGenerator
     Why does the EntityGenerator not do X?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -.
    +The EntityGenerator is not a full fledged code-generator that solves all tasks. Code-Generation
    +is not a first-class priority in Doctrine 2 anymore (compared to Doctrine 1). The EntityGenerator
    +is supposed to kick-start you, but not towards 100%.
     
     Why does the EntityGenerator not generate inheritance correctly?
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    -.
    +Just from the details of the discriminator map the EntityGenerator cannot guess the inheritance hierachy.
    +This is why the generation of inherited entities does not fully work. You have to adjust some additional
    +code to get this one working correctly.
     
     Performance
     -----------
    diff --git a/en/reference/improving-performance.rst b/en/reference/improving-performance.rst
    index c5b99a189..9bc4c8632 100644
    --- a/en/reference/improving-performance.rst
    +++ b/en/reference/improving-performance.rst
    @@ -42,6 +42,19 @@ for updates, which means when you call flush on the EntityManager these entities
     even if properties changed. Read-Only allows to persist new entities of a kind and remove existing
     ones, they are just not considered for updates.
     
    +Extra-Lazy Collections
    +----------------------
    +
    +If entities hold references to large collections you will get performance and memory problems initializing them.
    +To solve this issue you can use the EXTRA_LAZY fetch-mode feature for collections. See the :doc:`tutorial <../tutorials/extra-lazy-associations>`
    +for more information on how this fetch mode works.
    +
    +Temporarily change fetch mode in DQL
    +------------------------------------
    +
    +See :ref:`Doctrine Query Language chapter `
    +
    +
     Apply Best Practices
     --------------------
     
    diff --git a/en/reference/native-sql.rst b/en/reference/native-sql.rst
    index dfec36ff8..01b837d4a 100644
    --- a/en/reference/native-sql.rst
    +++ b/en/reference/native-sql.rst
    @@ -229,7 +229,6 @@ which meta-column is the discriminator column of this tree.
          * @param string $alias The alias of the entity result or joined entity result the discriminator
          *                      column should be used for.
          * @param string $discrColumn The name of the discriminator column in the SQL result set.
    -     * @todo Rename: addDiscriminatorColumn
          */
         public function setDiscriminatorColumn($alias, $discrColumn)
     
    @@ -363,4 +362,28 @@ are actually a subtype of User. When using DQL, Doctrine
     automatically includes the necessary joins for this mapping
     strategy but with native SQL it is your responsibility.
     
    +ResultSetMappingBuilder
    +-----------------------
     
    +There are some downsides with Native SQL queries. The primary one is that you have to adjust all result set mapping
    +definitions if names of columns change. In DQL this is detected dynamically when the Query is regenerated with
    +the current metadata.
    +
    +To avoid this hassle you can use the ``ResultSetMappingBuilder`` class. It allows to add all columns of an entity
    +to a result set mapping. To avoid clashes you can optionally rename specific columns when you are doing the same
    +in your sQL statement:
    +
    +.. code-block:: php
    +
    +    addRootEntityFromClassMetadata('MyProject\User', 'u');
    +    $rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', array('id' => 'address_id'));
    +
    +For entites with more columns the builder is very convenient to use. It extends the ``ResultSetMapping`` class
    +and as such has all the functionality of it as well. Currently the ``ResultSetMappingBuilder`` does not support
    +entities with inheritance.
    diff --git a/en/reference/working-with-objects.rst b/en/reference/working-with-objects.rst
    index a47b13ef4..c235828ae 100644
    --- a/en/reference/working-with-objects.rst
    +++ b/en/reference/working-with-objects.rst
    @@ -692,10 +692,28 @@ methods on a repository as follows:
     
     You can also load by owning side associations through the repository:
     
    +.. code-block:: php
    +
    +    find('MyProject\Domain\Phonenumber', 1234);
         $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId()));
     
    -Take not that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself.
    +Be careful that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself.
    +
    +The ``EntityRepository#findBy()`` method additionally accepts orderings, limit and offset as second to fourth parameters:
    +
    +.. code-block:: php
    +
    +    getRepository('MyProject\Domain\User')-findBy(array('age' => 20), array('name' => 'ASC'), 10, 0);
    +
    +If you pass an array of values Doctrine will convert the query into a WHERE field IN (..) query automatically:
    +
    +.. code-block:: php
    +
    +    getRepository('MyProject\Domain\User')-findBy(array('age' => array(20, 30, 40)));
    +    // translates roughly to: SELECT * FROM users WHERE age IN (20, 30, 40)
     
     An EntityRepository also provides a mechanism for more concise
     calls through its use of ``__call``. Thus, the following two
    diff --git a/en/reference/xml-mapping.rst b/en/reference/xml-mapping.rst
    index ef351da2d..758ff5b7c 100644
    --- a/en/reference/xml-mapping.rst
    +++ b/en/reference/xml-mapping.rst
    @@ -448,7 +448,7 @@ Optional attributes for owning One-to-One:
        field on the inverse entity that contains the back-reference.
     -  orphan-removal - If true, the inverse side entity is always
        deleted when the owning side entity is. Defaults to false.
    --  fetch - Either LAZY or FETCH, defaults to LAZY. This attribute
    +-  fetch - Either LAZY or EAGER, defaults to LAZY. This attribute
        makes only sense on the owning side, the inverse side *ALWAYS* has
        to use the ``FETCH`` strategy.
     
    @@ -501,7 +501,7 @@ Optional attributes:
        always deleted when the owning side entity is and it is not
        connected to any other owning side entity anymore. Defaults to
        false.
    --  fetch - Either LAZY or FETCH, defaults to LAZY.
    +-  fetch - Either LAZY or EAGER, defaults to LAZY.
     
     This definition relies on a bunch of mapping defaults with regards
     to the naming of the join-column/foreign key. The explicitly
    @@ -547,7 +547,8 @@ Required attributes:
     Optional attributes:
     
     
    --  fetch - Either LAZY or FETCH, defaults to LAZY.
    +-  fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
    +-  index-by: Index the collection by a field on the target entity.
     
     Defining Many-To-Many Associations
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @@ -579,7 +580,8 @@ Optional attributes:
     -  inversed-by - If the association is bidirectional the
        inversed-by attribute has to be specified with the name of the
        field on the inverse entity that contains the back-reference.
    --  fetch - Either LAZY or FETCH, defaults to LAZY.
    +-  fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
    +-  index-by: Index the collection by a field on the target entity.
     
     The mapping defaults would lead to a join-table with the name
     "User\_Group" being created that contains two columns "user\_id"
    @@ -698,4 +700,26 @@ table you can use the ```` and
     You have to specify the column and not the entity-class field names
     in the index and unique-constraint definitions.
     
    +Derived Entities ID syntax
    +~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    +If the primary key of an entity contains a foreign key to another entity we speak of a derived
    +entity relationship. You can define this in XML with the "association-key" attribute in the ```` tag.
    +
    +.. code-block:: xml
    +
    +    
    +
    +         
    +            
    +            
    +
    +            
    +
    +            
    +         
    +
    +    
    diff --git a/en/tutorials/composite-primary-keys.rst b/en/tutorials/composite-primary-keys.rst
    index 83ccd73ac..a091f573b 100644
    --- a/en/tutorials/composite-primary-keys.rst
    +++ b/en/tutorials/composite-primary-keys.rst
    @@ -144,55 +144,92 @@ Use-Case 1: Dynamic Attributes
     
     We keep up the example of an Article with arbitrary attributes, the mapping looks like this:
     
    -.. code-block:: php
    +.. configuration-block::
     
    -    attributes[$name] = new ArticleAttribute($name, $value, $this);
    +            /** @Id @Column(type="integer") @GeneratedValue */
    +            private $id;
    +            /** @Column(type="string") */
    +            private $title;
    +
    +            /**
    +             * @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
    +             */
    +            private $attributes;
    +
    +            public function addAttribute($name, $value)
    +            {
    +                $this->attributes[$name] = new ArticleAttribute($name, $value, $this);
    +            }
             }
    -    }
     
    -    /**
    -     * @Entity
    -     */
    -    class ArticleAttribute
    -    {
    -        /** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */
    -        private $article;
    -
    -        /** @Id @Column(type="string") */
    -        private $attribute;
    -
    -        /** @Column(type="string") */
    -        private $value;
    -
    -        public function __construct($name, $value, $article)
    +        /**
    +         * @Entity
    +         */
    +        class ArticleAttribute
             {
    -            $this->attribute = $name;
    -            $this->value = $value;
    -            $this->article = $article;
    +            /** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */
    +            private $article;
    +
    +            /** @Id @Column(type="string") */
    +            private $attribute;
    +
    +            /** @Column(type="string") */
    +            private $value;
    +
    +            public function __construct($name, $value, $article)
    +            {
    +                $this->attribute = $name;
    +                $this->value = $value;
    +                $this->article = $article;
    +            }
             }
    -    }
    +
    +    .. code-block:: xml
    +
    +        
    +
    +             
    +                
    +                
    +                
    +                
    +
    +                
    +             
    +
    +        
    +
    +    .. code-block:: yaml
    +
    +        Application\Model\ArticleAttribute:
    +          type: entity
    +          id:
    +            article:
    +              associationKey: true
    +            attribute:
    +              type: string
    +          fields:
    +            value:
    +              type: string
    +          manyToOne:
    +            article:
    +              targetEntity: Article
    +              inversedBy: attributes
     
     
     Use-Case 2: Simple Derived Identity
    @@ -311,4 +348,4 @@ a simple surrogate key. This performance impact is mostly due to additional PHP
     necessary to handle this kind of keys, most notably when using derived identifiers.
     
     On the SQL side there is not much overhead as no additional or unexpected queries have to be
    -executed to manage entities with derived foreign keys.
    \ No newline at end of file
    +executed to manage entities with derived foreign keys.
    diff --git a/en/tutorials/extra-lazy-associations.rst b/en/tutorials/extra-lazy-associations.rst
    index b854f72c1..ae58eb23c 100644
    --- a/en/tutorials/extra-lazy-associations.rst
    +++ b/en/tutorials/extra-lazy-associations.rst
    @@ -1,10 +1,6 @@
     Extra Lazy Associations
     =======================
     
    -.. note::
    -
    -    This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series.
    -
     In many cases associations between entities can get pretty large. Even in a simple scenario like a blog.
     where posts can be commented, you always have to assume that a post draws hundrets of comments.
     In Doctrine 2.0 if you accessed an association it would always get loaded completly into memory. This
    
    From d8125768a32780dcf5bdaa4c0b5b53e8500d0127 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Wed, 13 Jul 2011 21:19:09 +0200
    Subject: [PATCH 250/430] Copy new build process for docs into orm docs from
     DBAL
    
    ---
     .gitignore                  |  2 ++
     README.md                   |  8 ++++++++
     bin/generate-docs.sh        | 10 ++++++++++
     bin/install-dependencies.sh |  4 ++++
     convert.sh                  | 14 --------------
     en/conf.py                  |  8 ++++----
     generate-docs.sh            |  2 --
     7 files changed, 28 insertions(+), 20 deletions(-)
     create mode 100644 .gitignore
     create mode 100644 README.md
     create mode 100755 bin/generate-docs.sh
     create mode 100644 bin/install-dependencies.sh
     delete mode 100755 convert.sh
     delete mode 100755 generate-docs.sh
    
    diff --git a/.gitignore b/.gitignore
    new file mode 100644
    index 000000000..75ec69b23
    --- /dev/null
    +++ b/.gitignore
    @@ -0,0 +1,2 @@
    +en/_exts/configurationblock.pyc
    +build
    \ No newline at end of file
    diff --git a/README.md b/README.md
    new file mode 100644
    index 000000000..4315116f8
    --- /dev/null
    +++ b/README.md
    @@ -0,0 +1,8 @@
    +# Doctrine ORM Documentation
    +
    +## How to Generate
    +
    +1. Run ./bin/install-dependencies.sh
    +2. Run ./bin/generate-docs.sh
    +
    +It will generate the documentation into the build directory of the checkout.
    \ No newline at end of file
    diff --git a/bin/generate-docs.sh b/bin/generate-docs.sh
    new file mode 100755
    index 000000000..7d06d2a8d
    --- /dev/null
    +++ b/bin/generate-docs.sh
    @@ -0,0 +1,10 @@
    +#!/bin/bash
    +EXECPATH=`dirname $0`
    +cd $EXECPATH
    +cd ..
    +
    +rm build -Rf
    +sphinx-build en build
    +
    +sphinx-build -b latex en build/pdf
    +rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex
    \ No newline at end of file
    diff --git a/bin/install-dependencies.sh b/bin/install-dependencies.sh
    new file mode 100644
    index 000000000..86b3bdff7
    --- /dev/null
    +++ b/bin/install-dependencies.sh
    @@ -0,0 +1,4 @@
    +#!/bin/bash
    +sudo apt-get install python25 python25-dev texlive-full rubber
    +sudo easy_install pygments
    +sudo easy_install sphinx
    \ No newline at end of file
    diff --git a/convert.sh b/convert.sh
    deleted file mode 100755
    index 27e4a5b85..000000000
    --- a/convert.sh
    +++ /dev/null
    @@ -1,14 +0,0 @@
    -#!/bin/bash
    -FILES=`find -iname *.txt -print`
    -for FILE in $FILES
    -do
    -    # replace the + to # chars
    -    sed -i -r 's/^([+]{4})\s/#### /' $FILE
    -    sed -i -r 's/^([+]{3})\s/### /' $FILE
    -    sed -i -r 's/^([+]{2})\s/## /' $FILE
    -    sed -i -r 's/^([+]{1})\s/# /' $FILE
    -    sed -i -r 's/(\[php\])/ ${FILE%.txt}.rst
    -done
    \ No newline at end of file
    diff --git a/en/conf.py b/en/conf.py
    index a0192a66a..09d8c262f 100644
    --- a/en/conf.py
    +++ b/en/conf.py
    @@ -38,20 +38,20 @@ master_doc = 'index'
     
     # General information about the project.
     project = u'Doctrine 2 ORM'
    -copyright = u'2010, Doctrine Project Team'
    +copyright = u'2010-11, Doctrine Project Team'
     
     # The version info for the project you're documenting, acts as replacement for
     # |version| and |release|, also used in various other places throughout the
     # built documents.
     #
     # The short X.Y version.
    -version = '2.0'
    +version = '2.1'
     # The full version, including alpha/beta/rc tags.
    -release = '2.0.0'
    +release = '2.1'
     
     # The language for content autogenerated by Sphinx. Refer to documentation
     # for a list of supported languages.
    -language = 'php'
    +language = 'en'
     
     # There are two options for replacing |today|: either, you set today to some
     # non-false value, then it is used:
    diff --git a/generate-docs.sh b/generate-docs.sh
    deleted file mode 100755
    index 33e811f6c..000000000
    --- a/generate-docs.sh
    +++ /dev/null
    @@ -1,2 +0,0 @@
    -#!/bin/bash
    -sphinx-build en /var/www/docs
    
    From ecb13a87dcbab294289be7663f35f6522c481f91 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Wed, 13 Jul 2011 21:31:31 +0200
    Subject: [PATCH 251/430] Bugfix
    
    ---
     en/tutorials/extra-lazy-associations.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/tutorials/extra-lazy-associations.rst b/en/tutorials/extra-lazy-associations.rst
    index ae58eb23c..b0b90947a 100644
    --- a/en/tutorials/extra-lazy-associations.rst
    +++ b/en/tutorials/extra-lazy-associations.rst
    @@ -24,7 +24,7 @@ For each of this three methods the following semantics apply:
     Additionally even with Doctrine 2.0 the following methods do not trigger the collection load:
     
     -  ``Collection#add($entity)``
    --  ``Collection#offsetSet($key, $entity)`` - ArrayAccess with no specific key ``$coll[] = $entity`, it does
    +-  ``Collection#offsetSet($key, $entity)`` - ArrayAccess with no specific key ``$coll[] = $entity``, it does
        not work when setting specific keys like ``$coll[0] = $entity``.
     
     With extra lazy collections you can now not only add entities to large collections but also paginate them
    
    From bfe5bea68dae875ae4053a579556c03d97c9ea5e Mon Sep 17 00:00:00 2001
    From: Dan Patrick 
    Date: Sat, 30 Jul 2011 17:15:44 -0500
    Subject: [PATCH 252/430] Fixed grammatical errors in
     working-with-associations.rst
    
    ---
     en/reference/working-with-associations.rst | 28 ++++++++++++----------
     1 file changed, 15 insertions(+), 13 deletions(-)
    
    diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst
    index f426ecf03..bec659035 100644
    --- a/en/reference/working-with-associations.rst
    +++ b/en/reference/working-with-associations.rst
    @@ -246,8 +246,8 @@ the database permanently.
     
     Notice how both sides of the bidirectional association are always
     updated. Unidirectional associations are consequently simpler to
    -handle. Also note that if you type-hint your methods, i.e.
    -``setAddress(Address $address)``, then PHP does only allows null
    +handle. Also note that if you use type-hinting in your methods, i.e.
    +``setAddress(Address $address)``, PHP will only allow null
     values if ``null`` is set as default value. Otherwise
     setAddress(null) will fail for removing the association. If you
     insist on type-hinting a typical way to deal with this is to
    @@ -279,8 +279,9 @@ entities that have been re-added to the collection.
     
     Say you clear a collection of tags by calling
     ``$post->getTags()->clear();`` and then call
    -``$post->getTags()->add($tag)``. This will not recognize tag being
    -already added before and issue two database calls.
    +``$post->getTags()->add($tag)``. This will not recognize the tag having 
    +already been added previously and will consequently issue two separate database 
    +calls.
     
     Association Management Methods
     ------------------------------
    @@ -380,9 +381,9 @@ as your preferences.
     Synchronizing Bidirectional Collections
     ---------------------------------------
     
    -In the case of Many-To-Many associations you as the developer are
    -responsible to keep the collections on the owning and inverse side
    -up in sync, when you apply changes to them. Doctrine can only
    +In the case of Many-To-Many associations you as the developer have the 
    +responsibility of keeping the collections on the owning and inverse side
    + in sync when you apply changes to them. Doctrine can only
     guarantee a consistent state for the hydration, not for your client
     code.
     
    @@ -468,7 +469,7 @@ code would fail if you removed the call to
     cascade the persist operation to all nested entities that are new
     as well.
     
    -More complicated is the deletion of all a users comments when he is
    +More complicated is the deletion of all of a user's comments when he is
     removed from the system:
     
     .. code-block:: php
    @@ -590,7 +591,7 @@ and StandingData:
             }
         }
     
    -Now two examples what happens when you remove the references:
    +Now two examples of what happens when you remove the references:
     
     .. code-block:: php
     
    @@ -602,7 +603,8 @@ Now two examples what happens when you remove the references:
     
         $em->flush();
     
    -In this case you have only changed the ``Contact`` entity but you removed
    -the references for standing data and one address reference. When flush is called
    -not only are the references removed but both the old standing data and the one
    -address entity are also deleted from the database.
    \ No newline at end of file
    +In this case you have not only changed the ``Contact`` entity itself but 
    +you have also removed the references for standing data and as well as one 
    +address reference. When flush is called not only are the references removed 
    +but both the old standing data and the one address entity are also deleted 
    +from the database.
    
    From 6cd7d21db1e1fc4a6c09398e3271f71f09d18415 Mon Sep 17 00:00:00 2001
    From: Dan Patrick 
    Date: Mon, 1 Aug 2011 18:04:14 -0500
    Subject: [PATCH 253/430] Small grammar changes in section explaining
     lazyload/ArrayCollection.
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 24ea4ab67..4678834d9 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -126,8 +126,8 @@ or collections of all the relations that haven't been explicitly
     retrieved from the database yet.
     
     To be able to use lazyload with collections, simple PHP arrays have
    -to be replaced by a generic collection interface Doctrine which
    -tries to act as array as much as possible using ArrayAccess,
    +to be replaced by a generic collection interface for Doctrine which
    +tries to act as as much like an array as possible by using ArrayAccess,
     IteratorAggregate and Countable interfaces. The class is the most
     simple implementation of this interface.
     
    
    From 1de9b906fd745e32204643d1c58141bc79c024d0 Mon Sep 17 00:00:00 2001
    From: Christian Raue 
    Date: Fri, 5 Aug 2011 02:10:40 +0300
    Subject: [PATCH 254/430] completed the sentence in section "postUpdate,
     postRemove, postPersist"
    
    ---
     en/reference/events.rst | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/en/reference/events.rst b/en/reference/events.rst
    index c8b116cc9..0b20ba4a9 100644
    --- a/en/reference/events.rst
    +++ b/en/reference/events.rst
    @@ -570,7 +570,9 @@ postUpdate, postRemove, postPersist
     
     The three post events are called inside ``EntityManager#flush()``.
     Changes in here are not relevant to the persistence in the
    -database, but you can use this events to
    +database, but you can use these events to alter non-persistable items,
    +like non-mapped fields, logging or even associated classes that are
    +directly mapped by Doctrine.
     
     postLoad
     ~~~~~~~~
    
    From 5ee8861350856c86d90af5dcf11a798a3d2594eb Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 6 Aug 2011 19:12:42 +0200
    Subject: [PATCH 255/430] Add note about comparing datetime and object by
     reference
    
    ---
     en/reference/basic-mapping.rst | 7 ++++++-
     1 file changed, 6 insertions(+), 1 deletion(-)
    
    diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst
    index 12cf47c7b..e29760a4a 100644
    --- a/en/reference/basic-mapping.rst
    +++ b/en/reference/basic-mapping.rst
    @@ -176,7 +176,12 @@ built-in mapping types:
         types! They are mapping types between 2 types.
         Additionally Mapping types are *case-sensitive*. For example, using
         a DateTime column will NOT match the datetime type that ships with
    -    Doctrine 2.    
    +    Doctrine 2.
    +
    +.. note::
    +
    +    DateTime and Object types are compared by reference, not by value. Doctrine updates this values
    +    if the reference changes and therefore behaves as if these objects are immutable value objects.
     
     .. warning::
     
    
    From 189c729f15d2fafecf92662cad9553c2ec3dccd7 Mon Sep 17 00:00:00 2001
    From: Guilherme Blanco 
    Date: Mon, 8 Aug 2011 02:08:19 -0300
    Subject: [PATCH 256/430] Added support to CaseExpression.
    
    ---
     en/reference/dql-doctrine-query-language.rst | 21 +++++++++++++++++---
     1 file changed, 18 insertions(+), 3 deletions(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index bd32720d1..955f7ee1c 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -1469,6 +1469,7 @@ Conditional Expressions
                                         InExpression | NullComparisonExpression | ExistsExpression |
                                         EmptyCollectionComparisonExpression | CollectionMemberExpression
     
    +
     Collection Expressions
     ~~~~~~~~~~~~~~~~~~~~~~
     
    @@ -1505,7 +1506,7 @@ Arithmetic Expressions
         ArithmeticFactor           ::= [("+" | "-")] ArithmeticPrimary
         ArithmeticPrimary          ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")"
                                        | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
    -                                   | FunctionsReturningDatetime | IdentificationVariable | InputParameter
    +                                   | FunctionsReturningDatetime | IdentificationVariable | InputParameter | CaseExpression
     
     Scalar and Type Expressions
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @@ -1513,9 +1514,9 @@ Scalar and Type Expressions
     .. code-block:: php
     
         ScalarExpression       ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression
    -                               BooleanPrimary | EntityTypeExpression
    +                               BooleanPrimary | EntityTypeExpression | CaseExpression
         StringExpression       ::= StringPrimary | "(" Subselect ")"
    -    StringPrimary          ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression
    +    StringPrimary          ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
         BooleanExpression      ::= BooleanPrimary | "(" Subselect ")"
         BooleanPrimary         ::= StateFieldPathExpression | boolean | InputParameter
         EntityExpression       ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
    @@ -1535,6 +1536,20 @@ Aggregate Expressions
         AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
                                 "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")"
     
    +Case Expressions
    +~~~~~~~~~~~~~~~~
    +
    +.. code-block:: php
    +
    +    CaseExpression        ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression 
    +    GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" 
    +    WhenClause            ::= "WHEN" ConditionalExpression "THEN" ScalarExpression 
    +    SimpleCaseExpression  ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" 
    +    CaseOperand           ::= StateFieldPathExpression | TypeDiscriminator 
    +    SimpleWhenClause      ::= "WHEN" ScalarExpression "THEN" ScalarExpression 
    +    CoalesceExpression    ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" 
    +    NullifExpression      ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
    +
     Other Expressions
     ~~~~~~~~~~~~~~~~~
     
    
    From 31f34e95cc018d57b66e9bd769faef9e80e28b3b Mon Sep 17 00:00:00 2001
    From: Hugo Hamon 
    Date: Sun, 14 Aug 2011 18:21:31 +0200
    Subject: [PATCH 257/430] [QueryBuilder] added missing $
    
    ---
     en/reference/query-builder.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst
    index 521f31d2a..dc156bbe2 100644
    --- a/en/reference/query-builder.rst
    +++ b/en/reference/query-builder.rst
    @@ -163,7 +163,7 @@ mentioned syntax with "getParameter()" or "getParameters()":
         // $qb instanceof QueryBuilder
         
         // See example above
    -    $params = qb->getParameters(array(1, 2));
    +    $params = $qb->getParameters(array(1, 2));
         // Equivalent to
         $param  = array($qb->getParameter(1), $qb->getParameter(2));
     
    
    From a5ac76b1925bbcdb90d73b28069ab3518d6cecde Mon Sep 17 00:00:00 2001
    From: Guilherme Blanco 
    Date: Mon, 15 Aug 2011 01:57:02 -0300
    Subject: [PATCH 258/430] Added new support to DQL: ORDER BY now supports
     SingleValuedPathExpression and INSTANCE OF now supports multi-value checking.
    
    ---
     en/reference/dql-doctrine-query-language.rst | 15 +++++++++------
     1 file changed, 9 insertions(+), 6 deletions(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index 955f7ee1c..3cfce0204 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -1428,7 +1428,7 @@ Items
     .. code-block:: php
     
         UpdateItem  ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue
    -    OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"]
    +    OrderByItem ::= (ResultVariable | SingleValuedPathExpression) ["ASC" | "DESC"]
         GroupByItem ::= IdentificationVariable | SingleValuedPathExpression
         NewValue    ::= ScalarExpression | SimpleEntityExpression | "NULL"
     
    @@ -1450,11 +1450,11 @@ Select Expressions
     
     .. code-block:: php
     
    -    SelectExpression       ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")"  | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable]
    -    SimpleSelectExpression ::= ScalarExpression | IdentificationVariable |
    -                               (AggregateExpression [["AS"] AliasResultVariable])
    +    SelectExpression        ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")"  | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable]
    +    SimpleSelectExpression  ::= ScalarExpression | IdentificationVariable |
    +                                (AggregateExpression [["AS"] AliasResultVariable])
         PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
    -    PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
    +    PartialFieldSet         ::= "{" SimpleStateField {"," SimpleStateField}* "}"
     
     Conditional Expressions
     ~~~~~~~~~~~~~~~~~~~~~~~
    @@ -1467,7 +1467,8 @@ Conditional Expressions
         ConditionalPrimary          ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
         SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression |
                                         InExpression | NullComparisonExpression | ExistsExpression |
    -                                    EmptyCollectionComparisonExpression | CollectionMemberExpression
    +                                    EmptyCollectionComparisonExpression | CollectionMemberExpression |
    +                                    InstanceOfExpression
     
     
     Collection Expressions
    @@ -1561,6 +1562,8 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS
         BetweenExpression        ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
         ComparisonExpression     ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
         InExpression             ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
    +    InstanceOfExpression     ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
    +    InstanceOfParameter      ::= AbstractSchemaName | InputParameter
         LikeExpression           ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char]
         NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
         ExistsExpression         ::= ["NOT"] "EXISTS" "(" Subselect ")"
    
    From ffa2a545fa0d34c135629fe1048c26001a139a7d Mon Sep 17 00:00:00 2001
    From: Joseph Rouff 
    Date: Thu, 25 Aug 2011 01:36:21 +0300
    Subject: [PATCH 259/430] Fix typo
    
    ---
     en/tutorials/extra-lazy-associations.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/tutorials/extra-lazy-associations.rst b/en/tutorials/extra-lazy-associations.rst
    index b0b90947a..d6ed40d3d 100644
    --- a/en/tutorials/extra-lazy-associations.rst
    +++ b/en/tutorials/extra-lazy-associations.rst
    @@ -2,7 +2,7 @@ Extra Lazy Associations
     =======================
     
     In many cases associations between entities can get pretty large. Even in a simple scenario like a blog.
    -where posts can be commented, you always have to assume that a post draws hundrets of comments.
    +where posts can be commented, you always have to assume that a post draws hundreds of comments.
     In Doctrine 2.0 if you accessed an association it would always get loaded completly into memory. This
     can lead to pretty serious performance problems, if your associations contain several hundrets or thousands
     of entities.
    
    From c90f3cf275a709ec60089043dbb83168ab23b929 Mon Sep 17 00:00:00 2001
    From: Marc Weistroff 
    Date: Fri, 26 Aug 2011 02:07:56 +0300
    Subject: [PATCH 260/430] Fixed namespace in code-block
    
    ---
     en/reference/dql-doctrine-query-language.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index 3cfce0204..fb5e7123f 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -625,7 +625,7 @@ classes have to implement the base class :
         
    Date: Sat, 27 Aug 2011 12:36:37 +0200
    Subject: [PATCH 261/430] Fixed the tutorial, it was a mess! Now its explaining
     everything step by step and all bugs are removed. Changed introduction and
     configuration to use the simpler Setup helper class
    
    ---
     en/reference/configuration.rst               |  93 ++--
     en/reference/faq.rst                         |   6 +-
     en/reference/introduction.rst                |   6 +-
     en/tutorials/getting-started-xml-edition.rst | 479 ++++++++++++-------
     4 files changed, 380 insertions(+), 204 deletions(-)
    
    diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst
    index 27a54c6fe..178ff9bb4 100644
    --- a/en/reference/configuration.rst
    +++ b/en/reference/configuration.rst
    @@ -43,17 +43,29 @@ different types of Doctrine Installations:
         the following code in. Something like a ``test.php`` file.
     
     
    -PEAR or Tarball Download
    -^^^^^^^^^^^^^^^^^^^^^^^^
    +PEAR
    +^^^^
     
     .. code-block:: php
     
         register(); // register on SPL autoload stack
    +    require 'Doctrine/ORM/Tools/Setup.php';
    +
    +    Doctrine\ORM\Tools\Setup::registerAutoloadPEAR();
    +
    +Tarball Download
    +^^^^^^^^^^^^^^^^
    +
    +.. code-block:: php
    +
    +    register();
    -    
    -    $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\DBAL', $lib . 'vendor/doctrine-dbal/lib');
    -    $classLoader->register();
    -    
    -    $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\ORM', $lib);
    -    $classLoader->register();
    +    $lib = '/path/to/doctrine2-orm-root';
    +    Doctrine\ORM\Tools\Setup::registerAutoloadGit($lib);
    +
     
     Additional Symfony Components
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     
    -If you don't use Doctrine2 in combination with Symfony2 you have to
    -register an additional namespace to be able to use the Doctrine-CLI
    -Tool or the YAML Mapping driver:
    -
    -.. code-block:: php
    -
    -    register();
    -    
    -    // Git Setup
    -    $classloader = new \Doctrine\Common\ClassLoader('Symfony', $lib . 'vendor/');
    -    $classloader->register();
    -
    -For best class loading performance it is recommended that you keep
    -your include\_path short, ideally it should only contain the path
    -to the PEAR libraries, and any other class libraries should be
    -registered with their full base path.
    +All three autoloading setups described above also take care of
    +the autoloading of the Symfony Console and YAML component,
    +which are optional dependencies for Doctrine 2.
     
     Obtaining an EntityManager
     ~~~~~~~~~~~~~~~~~~~~~~~~~~
    @@ -108,9 +97,10 @@ Once you have prepared the class loading, you acquire an
     *EntityManager* instance. The EntityManager class is the primary
     access point to ORM functionality provided by Doctrine.
     
    -A simple configuration of the EntityManager requires a
    +The configuration of the EntityManager requires a
     ``Doctrine\ORM\Configuration`` instance as well as some database
    -connection parameters:
    +connection parameters. This example shows all the potential
    +steps of configuration.
     
     .. code-block:: php
     
    @@ -161,6 +151,35 @@ connection parameters:
         fast in-memory cache storage that you can use for the metadata and
         query caches as seen in the previous code snippet.
     
    +Configuration Shortcuts
    +~~~~~~~~~~~~~~~~~~~~~~~
    +
    +The above example is a complete setup of the required options for Doctrine.
    +You can have this step of your code much simpler and use one of the predefined
    +setup methods:
    +
    +.. code-block:: php
    +
    +    flush()`` if you know that unique constraint failures
     can occur.
     
    -In `Symfony 2`_ for example there is a Unique Entity Validator
    +In `Symfony 2 `_ for example there is a Unique Entity Validator
     to achieve this task.
     
     For collections you can check with ``$collection->contains($entity)`` if an entity is already
    @@ -128,7 +128,7 @@ Can I use Inheritance with Doctrine 2?
      
     Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2.
     
    -See the documentation chapter on :doc:`inheritance mapping `_ for
    +See the documentation chapter on :doc:`inheritance mapping ` for
     the details.
     
     Why does Doctrine not create proxy objects for my inheritance hierachy?
    diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst
    index e832766c8..fcc8c4917 100644
    --- a/en/reference/introduction.rst
    +++ b/en/reference/introduction.rst
    @@ -136,9 +136,9 @@ the ``ClassLoader`` with the following code.
     .. code-block:: php
     
         register();
    +    require 'Doctrine/ORM/Tools/Setup.php';
    +
    +    Doctrine\ORM\Tools\Setup\registerAutoloadPEAR();
     
     The packages are installed in to your shared PEAR PHP code folder
     in a folder named ``Doctrine``. You also get a nice command line
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 24ea4ab67..ef509d710 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -12,6 +12,10 @@ belief that there are considerable benefits for object-oriented
     programming if persistence and entities are kept perfectly
     separated.
     
    +.. note::
    +
    +    The code of this tutorial is `available on Github `_.
    +
     What are Entities?
     ------------------
     
    @@ -55,54 +59,105 @@ requirements to be:
         copy-paste the examples here, it is not production ready without
         the additional comments and knowledge this tutorial teaches.
     
    +Setup Project
    +-------------
    +
    +Make sure you get Doctrine from PEAR by calling the following commands
    +on your CLI:
    +
    +::
    +
    +    $ pear channel-discover pear.doctrine-project.org
    +    $ pear install doctrine/DoctrineORM --all-deps
    +
    +This should install the packages DoctrineCommon, DoctrineDBAL, DoctrineORM,
    +SymfonyConsole and SymfonyYAML.
    +
    +Now create a new directory for this tutorial project:
    +
    +::
    +
    +    $ mkdir project
    +    $ cd project
    +
    +You can prepare the directory structure so that in the end it looks like:
    +
    +::
    +
    +    project
    +    |-- config
    +    |   |-- xml
    +    |   `-- yaml
    +    `-- entities
     
     A first prototype
     -----------------
     
     A first simplified design for this domain model might look like the
    -following set of classes:
    +following set of classes. Put them into `entities/Bug.php`,
    +`entities/Product.php` and `entities/User.php` respectively.
     
     .. code-block:: php
     
         id;
    +        }
    +
    +        public function getName()
    +        {
    +            return $this->name;
    +        }
    +
    +        public function setName($name)
    +        {
    +            $this->name = $name;
    +        }
         }
         class User
         {
    -        public $id;
    -        public $name;
    -        public $reportedBugs = array();
    -        public $assignedBugs = array();
    +        protected $id;
    +        public $name; // public for educational purpose, see below
    +
    +        public function getId()
    +        {
    +            return $this->id;
    +        }
    +
    +        public function getName()
    +        {
    +            return $this->name;
    +        }
    +
    +        public function setName($name)
    +        {
    +            $this->name = $name;
    +        }
         }
     
     .. warning::
     
    -    This is only a prototype, please don't use public properties with
    -    Doctrine 2 at all, the "Queries for Application Use-Cases" section
    -    shows you why. In combination with proxies public properties can
    -    make up for pretty nasty bugs.
    +    Properties should never be public when working when using Doctrine.
    +    This will lead to bugs with the way lazy loading works in Doctrine.
     
    -
    -Because we will focus on the mapping aspect, no effort is being
    -made to encapsulate the business logic in this example. All
    -persistable properties are public in visibility. We will soon see
    -that this is not the best solution in combination with Doctrine 2,
    -one restriction that actually forces you to encapsulate your
    -properties. For persistence Doctrine 2 actually uses Reflection to
    -access the values in all your entities properties.
    +You see that all properties have getters and setters except `$id`.
    +Doctrine 2 uses Reflection to access the values in all your entities properties, so it
    +is possible to set the `$id` value for Doctrine, however not from
    +your application code. The use of reflection by Doctrine allows you
    +to completely encapsulate state and state changes in your entities.
     
     Many of the fields are single scalar values, for example the 3 ID
     fields of the entities, their names, description, status and change
    @@ -111,13 +166,14 @@ other ORM. From a point of our domain model they are ready to be
     used right now and we will see at a later stage how they are mapped
     to the database.
     
    -There are also several references between objects in this domain
    -model, whose semantics are discussed case by case at this point to
    +We will soon add references between objects in this domain
    +model. The semantics are discussed case by case to
     explain how Doctrine handles them. In general each OneToOne or
     ManyToOne Relation in the Database is replaced by an instance of
     the related object in the domain model. Each OneToMany or
     ManyToMany Relation is replaced by a collection of instances in the
    -domain model.
    +domain model. You never have to work with the foreign keys, only
    +with objects that represent the foreign key through their own identity.
     
     If you think this through carefully you realize Doctrine 2 will
     load up the complete database in memory if you access one object.
    @@ -137,22 +193,27 @@ with the assumptions about related collections:
     .. code-block:: php
     
         products = new ArrayCollection();
             }
         }
    -    
    +
    +.. code-block:: php
    +
    +    reporter;
             }
         }
    +
    +.. code-block:: php
    +
    +    
             
     
                   
    -                  
    +                  
                           
                       
     
    -                  
    +                  
                   
             
     
         .. code-block:: yaml
     
    +        # config/yaml/Product.dcm.yml
             Product:
               type: entity
               table: products
    @@ -422,6 +496,7 @@ We then go on specifying the definition of a Bug:
         .. code-block:: php
     
             
             
             
     
    -             
    +             
                      
                          
                      
    @@ -597,6 +676,7 @@ The last missing definition is that of the User entity:
     
         .. code-block:: yaml
     
    +        # config/xml/User.dcm.yml
             User:
               type: entity
               table: users
    @@ -639,86 +719,75 @@ step:
     .. code-block:: php
     
         ` for up to date autoloading details.
    +    use Doctrine\ORM\Tools\Setup;
    +
    +    require_once "Doctrine/ORM/Tools/Setup.php";
    +    Setup::registerAutoloadPEAR();
    +
    +    // Create a simple "default" Doctrine ORM configuration for XML Mapping
    +    $isDevMode = true;
    +    $config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
    +    // or if you prefer yaml or annotations
    +    //$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/entities"), $isDevMode);
    +    //$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
         
    -    $config = new Doctrine\ORM\Configuration(); // (2)
    -    
    -    // Proxy Configuration (3)
    -    $config->setProxyDir(__DIR__.'/lib/MyProject/Proxies');
    -    $config->setProxyNamespace('MyProject\Proxies');
    -    $config->setAutoGenerateProxyClasses((APPLICATION_ENV == "development"));
    -    
    -    // Mapping Configuration (4)
    -    $driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings/xml");
    -    //$driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings/yml");
    -    //$driverImpl = $config->newDefaultAnnotationDriver(__DIR__."/entities");
    -    $config->setMetadataDriverImpl($driverImpl);
    -    
    -    // Caching Configuration (5)
    -    if (APPLICATION_ENV == "development") {
    -        $cache = new \Doctrine\Common\Cache\ArrayCache();
    -    } else {
    -        $cache = new \Doctrine\Common\Cache\ApcCache();
    -    }
    -    $config->setMetadataCacheImpl($cache);
    -    $config->setQueryCacheImpl($cache);
    -    
    -    // database configuration parameters (6)
    +    // database configuration parameters
         $conn = array(
             'driver' => 'pdo_sqlite',
             'path' => __DIR__ . '/db.sqlite',
         );
         
    -    // obtaining the entity manager (7)
    -    $evm = new Doctrine\Common\EventManager()
    -    $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config, $evm);
    +    // obtaining the entity manager
    +    $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config);
     
    -The first block sets up the autoloading capabilities of Doctrine. I
    -am registering the Doctrine namespace to the given path. To add
    -your own namespace you can instantiate another ``ClassLoader`` with
    -different namespace and path arguments. There is no requirement to
    -use the Doctrine ``ClassLoader`` for your autoloading needs, you
    -can use whatever suits you best.
    +The first block sets up the autoloading capabilities of Doctrine.
    +We assume here that you have installed Doctrine using PEAR.
    +See :doc:`Configuration <../reference/configuration>` for more details
    +on other installation procedures.
     
     The second block contains of the instantiation of the ORM
    -Configuration object. Besides the configuration shown in the next
    -blocks there are several others with are all explained in the
    -:doc:`Configuration section of the manual <../reference/configuration>`.
    +Configuration object using the Setup helper. It assumes a bunch
    +of defaults that you don't have to bother about for now. You can
    +read up on the configuration details in the
    +:doc:`reference chapter on configuration <../reference/configuration>`.
     
    -The Proxy Configuration is a required block for your application,
    -you have to specify where Doctrine writes the PHP code for Proxy
    -Generation. Proxies are children of your entities generated by
    -Doctrine to allow for type-safe lazy loading. We will see in a
    -later chapter how exactly this works. Besides the path to the
    -proxies we also specify which namespace they will reside under as
    -well as a flag ``autoGenerateProxyClasses`` indicating that proxies
    -should be re-generated on each request, which is recommended for
    -development. In production this should be prevented at all costs,
    -the proxy class generation can be quite costly.
    -
    -The fourth block contains the mapping driver details. We will use
    -XML Mapping in this example, so we configure the ``XmlDriver``
    -instance with a path to mappings configuration folder where we put
    -the Bug.dcm.xml, Product.dcm.xml and User.dcm.xml.
    -
    -In the 5th block the caching configuration is set. In production we
    -use caching only on a per request-basis using the ArrayCache. In
    -production it is literally required to use Apc, Memcache or XCache
    -to get the full speed out of Doctrine. Internally Doctrine uses
    -caching heavily for the Metadata and DQL Query Language so make
    -sure you use a caching mechanism.
    -
    -The 6th block shows the configuration options required to connect
    +The third block shows the configuration options required to connect
     to a database, in my case a file-based sqlite database. All the
     configuration options for all the shipped drivers are given in the
     `DBAL Configuration section of the manual `_.
     
    +You should make sure to make it configurable if Doctrine should run
    +in dev or production mode using the `$devMode` variable. You can
    +use an environment variable for example, hook into your frameworks configuration
    +or check for the HTTP_HOST of your devsystem (localhost for example)
    +
    +.. code-block:: php
    +
    +     new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager)
         ));
    @@ -745,8 +817,8 @@ Doctrine command-line tool:
     
     ::
     
    -    doctrine@my-desktop> cd myproject/
    -    doctrine@my-desktop> doctrine orm:schema-tool:create
    +    $ cd project/
    +    $ doctrine orm:schema-tool:create
     
     .. note::
     
    @@ -766,14 +838,14 @@ either re-create the database:
     
     ::
     
    -    doctrine@my-desktop> doctrine orm:schema-tool:drop --force
    -    doctrine@my-desktop> doctrine orm:schema-tool:create
    +    $ doctrine orm:schema-tool:drop --force
    +    $ doctrine orm:schema-tool:create
     
     Or use the update functionality:
     
     ::
     
    -    doctrine@my-desktop> doctrine orm:schema-tool:update --force
    +    $ doctrine orm:schema-tool:update --force
     
     The updating of databases uses a Diff Algorithm for a given
     Database Schema, a cornerstone of the ``Doctrine\DBAL`` package,
    @@ -783,47 +855,68 @@ its not available in SQLite since it does not support ALTER TABLE.
     Writing Entities into the Database
     ----------------------------------
     
    +.. note::
    +
    +    This tutorial assumes you call all the example scripts from the CLI.
    +
     Having created the schema we can now start and save entities in the
     database. For starters we need a create user use-case:
     
     .. code-block:: php
     
         name = $newUsername;
    +    $user->setName($newUsername);
         
         $entityManager->persist($user);
         $entityManager->flush();
     
    +    echo "Created User with ID " . $user->getId() . "\n";
    +
     Products can also be created:
     
     .. code-block:: php
     
         name = $newProductName;
    +    $product->setName($newProductName);
         
         $entityManager->persist($product);
         $entityManager->flush();
     
    +    echo "Created Product with ID " . $product->getId() . "\n";
    +
    +Now call:
    +
    +..
    +
    +    $ php create_user.php beberlei
    +    $ php create_product.php MyProduct
    +
     So what is happening in those two snippets? In both examples the
    -class creation is pretty standard, the interesting bits are the
    +code that works on User and Product is pretty standard OOP. The interesting bits are the
     communication with the ``EntityManager``. To notify the
     EntityManager that a new entity should be inserted into the
     database you have to call ``persist()``. However the EntityManager
    -does not act on this, its merely notified. You have to explicitly
    +does not act on this command, its merely notified. You have to explicitly
     call ``flush()`` to have the EntityManager write those two entities
     to the database.
     
     You might wonder why does this distinction between persist
    -notification and flush exist? Doctrine 2 uses the UnitOfWork
    +notification and flush exist: Doctrine 2 uses the UnitOfWork
     pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one
    -single fast transaction, which is executed when flush is called.
    -Using this approach the write-performance is significantly faster
    +single transaction, which is executed when flush is called.
    +Using this approach the write-performance is significantly better
     than in a scenario where updates are done for each entity in
     isolation. In more complex scenarios than the previous two, you are
     free to request updates on many different entities and all flush
    @@ -834,10 +927,7 @@ retrieval from the database automatically when the flush operation
     is called, so that you only have to keep track of those entities
     that are new or to be removed and pass them to
     ``EntityManager#persist()`` and ``EntityManager#remove()``
    -respectively. This comparison to find dirty entities that need
    -updating is using a very efficient algorithm that has almost no
    -additional memory overhead and can even save you computing power by
    -only updating those database columns that really changed.
    +respectively.
     
     We are now getting to the "Create a New Bug" requirement and the
     code for this scenario may look like this:
    @@ -845,13 +935,24 @@ code for this scenario may look like this:
     .. code-block:: php
     
         find("User", $theReporterId);
         $engineer = $entityManager->find("User", $theDefaultEngineerId);
    +    if (!$reporter || !$engineer) {
    +        echo "No reporter and/or engineer found for the input.\n";
    +        exit(1);
    +    }
         
         $bug = new Bug();
    -    $bug->description = "Something does not work!";
    -    $bug->created = new DateTime("now");
    -    $bug->status = "NEW";
    +    $bug->setDescription("Something does not work!");
    +    $bug->setCreated(new DateTime("now"));
    +    $bug->setStatus("OPEN");
         
         foreach ($productIds AS $productId) {
             $product = $entityManager->find("Product", $productId);
    @@ -864,7 +965,13 @@ code for this scenario may look like this:
         $entityManager->persist($bug);
         $entityManager->flush();
         
    -    echo "Your new Bug Id: ".$bug->id."\n";
    +    echo "Your new Bug Id: ".$bug->getId()."\n";
    +
    +Since we only have one user and product, probably with the ID of 1, we can call this script with:
    +
    +..
    +
    +    php create_bug.php 1 1 1
     
     This is the first contact with the read API of the EntityManager,
     showing that a call to ``EntityManager#find($name, $id)`` returns a
    @@ -892,6 +999,9 @@ the first read-only use-case:
     .. code-block:: php
     
         createQuery($dql);
    @@ -974,6 +1084,9 @@ can rewrite our code:
     .. code-block:: php
     
         createQuery($dql);
    @@ -1004,22 +1117,25 @@ however there is a convenience method on the Entity Manager that
     handles loading by primary key, which we have already seen in the
     write scenarios:
     
    -.. code-block:: php
    -
    -    find("Bug", (int)$theBugId);
    -
     However we will soon see another problem with our entities using
     this approach. Try displaying the engineer's name:
     
     .. code-block:: php
     
         description."\n";
    +    // show_bug.php
    +    require_once "bootstrap.php";
    +
    +    $theBugId = $argv[1];
    +
    +    $bug = $entityManager->find("Bug", (int)$theBugId);
    +
    +    echo "Bug: ".$bug->getDescription()."\n";
    +    // Accessing our special public $name property here on purpose:
         echo "Engineer: ".$bug->getEngineer()->name."\n";
     
    -It will be null! What is happening? It worked in the previous
    -example, so it can't be a problem with the persistence code of
    +The output of the engineers name is null! What is happening?
    +It worked in the previous example, so it can't be a problem with the persistence code of
     Doctrine. What is it then? You walked in the public property trap.
     
     Since we only retrieved the bug by primary key both the engineer
    @@ -1063,13 +1179,23 @@ protected and add getters and setters to get a working example:
     .. code-block:: php
     
         find("Bug", (int)$theBugId);
    +
         echo "Bug: ".$bug->getDescription()."\n";
         echo "Engineer: ".$bug->getEngineer()->getName()."\n";
    -    
    -    /**
    +
    +Now prints:
    +
    +::
    +
    +    $ php show_bug.php 1
         Bug: Something does not work!
         Engineer: beberlei
    -    */
     
     Being required to use private or protected properties Doctrine 2
     actually enforces you to encapsulate your objects according to
    @@ -1086,20 +1212,24 @@ and usage of bound parameters:
     .. code-block:: php
     
         createQuery($dql)
                                 ->setParameter(1, $theUserId)
                                 ->setMaxResults(15)
                                 ->getResult();
         
    -    foreach ($myBugs AS $bug) {
    -        echo $bug->getDescription()."\n";
    -    }
    +    echo "You have created or assigned to " . count($myBugs) . " open bugs:\n\n";
     
    -That is it for the read-scenarios of this example, we will continue
    -with the last missing bit, engineers being able to close a bug.
    +    foreach ($myBugs AS $bug) {
    +        echo $bug->getId() . " - " . $bug->getDescription()."\n";
    +    }
     
     Number of Bugs
     --------------
    @@ -1115,6 +1245,9 @@ grouped by product:
     .. code-block:: php
     
         createQuery($dql)->getScalarResult();
    @@ -1132,6 +1265,24 @@ should be able to close a bug. This looks like:
     .. code-block:: php
     
         status = "CLOSE";
    +        }
    +    }
    +
    +.. code-block:: php
    +
    +    find("Bug", (int)$theBugId);
         $bug->close();
         
    @@ -1194,6 +1345,7 @@ the previoiusly discussed query functionality in it:
     .. code-block:: php
     
         getEntityManager()->getRepository('Bug')``
     we have to adjust the metadata slightly.
     
    @@ -1260,14 +1414,14 @@ we have to adjust the metadata slightly.
                   xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                                 http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
     
    -              
    +              
     
                   
             
     
         .. code-block:: yaml
     
    -        Product:
    +        Bug:
               type: entity
               repositoryClass: BugRepository
     
    @@ -1277,14 +1431,17 @@ As an example here is the code of the first use case "List of Bugs":
     .. code-block:: php
     
         getRepository('Bug')->getRecentBugs();
     
         foreach($bugs AS $bug) {
    -        echo $bug->description." - ".$bug->created->format('d.m.Y')."\n";
    -        echo "    Reported by: ".$bug->getReporter()->name."\n";
    -        echo "    Assigned to: ".$bug->getEngineer()->name."\n";
    +        echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n";
    +        echo "    Reported by: ".$bug->getReporter()->getName()."\n";
    +        echo "    Assigned to: ".$bug->getEngineer()->getName()."\n";
             foreach($bug->getProducts() AS $product) {
    -            echo "    Platform: ".$product->name."\n";
    +            echo "    Platform: ".$product->getName()."\n";
             }
             echo "\n";
         }
    
    From 79c113b53267f57104e252328417e3d6ebdb8419 Mon Sep 17 00:00:00 2001
    From: Michel Weimerskirch 
    Date: Fri, 2 Sep 2011 22:38:47 +0300
    Subject: [PATCH 262/430] Documented the onClear event. (using text from the
     docblock comment)
    
    ---
     en/reference/events.rst | 3 +++
     1 file changed, 3 insertions(+)
    
    diff --git a/en/reference/events.rst b/en/reference/events.rst
    index 0b20ba4a9..05c54f524 100644
    --- a/en/reference/events.rst
    +++ b/en/reference/events.rst
    @@ -166,6 +166,9 @@ the life-time of their registered entities.
     -  onFlush - The onFlush event occurs after the change-sets of all
        managed entities are computed. This event is not a lifecycle
        callback.
    +-  onClear - The onClear event occurs when the EntityManager#clear() operation is
    +   invoked, after all references to entities have been removed from the unit of
    +   work.
     
     .. warning::
     
    
    From 8084b6cbf05a9dbf0c09f0548329fe05d76ddfb7 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 4 Sep 2011 14:25:03 +0200
    Subject: [PATCH 263/430] Add docs on DDC-659 feature (ClassMetadataBuilder)
    
    ---
     en/reference/php-mapping.rst | 57 ++++++++++++++++++++++++++++++++++++
     1 file changed, 57 insertions(+)
    
    diff --git a/en/reference/php-mapping.rst b/en/reference/php-mapping.rst
    index 6ba0755c3..dafa2c0e1 100644
    --- a/en/reference/php-mapping.rst
    +++ b/en/reference/php-mapping.rst
    @@ -109,6 +109,63 @@ Now you just need to define a static function named
             }
         }
     
    +ClassMetadataBuilder
    +--------------------
    +
    +To ease the use of the ClassMetadata API (which is very raw) there is a ``ClassMetadataBuilder`` that you can use.
    +
    +.. code-block:: php
    +
    +    createField('id', 'integer')->isPrimaryKey()->generatedValue()->build();
    +            $builder->addField('username', 'string');
    +        }
    +    }
    +
    +The API of the ClassMetadataBuilder has the following methods with a fluent interface:
    +
    +-   ``addField($name, $type, array $mapping)``
    +-   ``setMappedSuperclass()``
    +-   ``setReadOnly()``
    +-   ``setCustomRepositoryClass($className)``
    +-   ``setTable($name)``
    +-   ``addIndex(array $columns, $indexName)``
    +-   ``addUniqueConstraint(array $columns, $constraintName)``
    +-   ``addNamedQuery($name, $dqlQuery)``
    +-   ``setJoinedTableInheritance()``
    +-   ``setSingleTableInheritance()``
    +-   ``setDiscriminatorColumn($name, $type = 'string', $length = 255)``
    +-   ``addDiscriminatorMapClass($name, $class)``
    +-   ``setChangeTrackingPolicyDeferredExplicit()``
    +-   ``setChangeTrackingPolicyNotify()``
    +-   ``addLifecycleEvent($methodName, $event)``
    +-   ``addManyToOne($name, $targetEntity, $inversedBy = null)``
    +-   ``addInverseOneToOne($name, $targetEntity, $mappedBy)``
    +-   ``addOwningOneToOne($name, $targetEntity, $inversedBy = null)``
    +-   ``addOwningManyToMany($name, $targetEntity, $inversedBy = null)``
    +-   ``addInverseManyToMany($name, $targetEntity, $mappedBy)``
    +-   ``addOneToMany($name, $targetEntity, $mappedBy)``
    +
    +It also has several methods that create builders (which are necessary for advanced mappings):
    +
    +-   ``createField($name, $type)`` returns a ``FieldBuilder`` instance
    +-   ``createManyToOne($name, $targetEntity)`` returns an ``AssocationBuilder`` instance
    +-   ``createOneToOne($name, $targetEntity)`` returns an ``AssocationBuilder`` instance
    +-   ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssocationBuilder`` instance
    +-   ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssocationBuilder`` instance
    +
     ClassMetadataInfo API
     ---------------------
     
    
    From a61c7e59d6e8714021dade087345b43c5467e43b Mon Sep 17 00:00:00 2001
    From: Ozan Yerli 
    Date: Sun, 4 Sep 2011 23:43:43 +0300
    Subject: [PATCH 264/430] Fixed typo in the orderBy method.
    
    ---
     en/reference/query-builder.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst
    index dc156bbe2..c9dbaace6 100644
    --- a/en/reference/query-builder.rst
    +++ b/en/reference/query-builder.rst
    @@ -415,7 +415,7 @@ re-written using ``QueryBuilder`` helper methods:
         $qb->select('u')
            ->from('User', 'u')
            ->where('u.id = ?1')
    -       ->orderBy('u.name ASC');
    +       ->orderBy('u.name', 'ASC');
     
     ``QueryBuilder`` helper methods are considered the standard way to
     build DQL queries. Although it is supported, it should be avoided
    
    From 3ec55d0cdd88e9d2feff1ddb871eb09520967261 Mon Sep 17 00:00:00 2001
    From: Toni Uebernickel 
    Date: Tue, 13 Sep 2011 20:32:04 +0200
    Subject: [PATCH 265/430] fix sequence generator php code on sequenceName
    
    ---
     en/reference/basic-mapping.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst
    index e29760a4a..1fa585a58 100644
    --- a/en/reference/basic-mapping.rst
    +++ b/en/reference/basic-mapping.rst
    @@ -557,7 +557,7 @@ besides specifying the sequence's name:
                 /**
                  * @Id
                  * @GeneratedValue(strategy="SEQUENCE")
    -             * @SequenceGenerator(name="tablename_seq", initialValue=1, allocationSize=100)
    +             * @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100)
                  */
                 protected $id = null;
             }
    
    From d3f6ffb09ef9e774cfbb256dab9de611ebcb99c1 Mon Sep 17 00:00:00 2001
    From: Chris Woodford 
    Date: Sun, 18 Sep 2011 18:39:36 -0400
    Subject: [PATCH 266/430] Added cookbook/decorator-pattern to index file
    
    ---
     en/index.rst | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/en/index.rst b/en/index.rst
    index 2164541c8..67dafce1f 100644
    --- a/en/index.rst
    +++ b/en/index.rst
    @@ -54,6 +54,7 @@ Cookbook
        :maxdepth: 1
     
        cookbook/aggregate-fields
    +   cookbook/decorator-pattern
        cookbook/dql-custom-walkers
        cookbook/dql-user-defined-functions
        cookbook/implementing-arrayaccess-for-domain-objects
    
    From 1a1d36c73f9a22967f33a11c22075f9422198d3d Mon Sep 17 00:00:00 2001
    From: Dan Patrick 
    Date: Tue, 4 Oct 2011 12:19:10 -0500
    Subject: [PATCH 267/430] Corrected a typo (interveawed)
    
    ---
     en/reference/working-with-associations.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst
    index f426ecf03..464167a32 100644
    --- a/en/reference/working-with-associations.rst
    +++ b/en/reference/working-with-associations.rst
    @@ -413,7 +413,7 @@ Transitive persistence / Cascade Operations
     -------------------------------------------
     
     Persisting, removing, detaching and merging individual entities can
    -become pretty cumbersome, especially when a highly interveawed object graph
    +become pretty cumbersome, especially when a highly interweaved object graph
     is involved. Therefore Doctrine 2 provides a
     mechanism for transitive persistence through cascading of these
     operations. Each association to another entity or a collection of
    
    From 9c389a49c7f7fa9065e75e839068232da56d957e Mon Sep 17 00:00:00 2001
    From: Guilherme Blanco 
    Date: Tue, 11 Oct 2011 01:27:30 -0300
    Subject: [PATCH 268/430] Fixes DDC-509
    
    ---
     en/reference/events.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/events.rst b/en/reference/events.rst
    index 0b20ba4a9..513de8b21 100644
    --- a/en/reference/events.rst
    +++ b/en/reference/events.rst
    @@ -369,7 +369,7 @@ Implementing Event Listeners
     This section explains what is and what is not allowed during
     specific lifecycle events of the UnitOfWork. Although you get
     passed the EntityManager in all of these events, you have to follow
    -this restrictions very carefully since operations in the wrong
    +these restrictions very carefully since operations in the wrong
     event may produce lots of different errors, such as inconsistent
     data and lost updates/persists/removes.
     
    
    From b88ef8b1a5a30a055e328f89a1b753bf9792fbfe Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 15 Oct 2011 09:47:56 +0200
    Subject: [PATCH 269/430] Add docs on SimplifiedXmlDriver and
     SimplifiedYamlDriver
    
    ---
     en/reference/xml-mapping.rst  | 22 ++++++++++++++++++++++
     en/reference/yaml-mapping.rst | 22 ++++++++++++++++++++++
     2 files changed, 44 insertions(+)
    
    diff --git a/en/reference/xml-mapping.rst b/en/reference/xml-mapping.rst
    index 758ff5b7c..5fbfdd1ff 100644
    --- a/en/reference/xml-mapping.rst
    +++ b/en/reference/xml-mapping.rst
    @@ -64,6 +64,28 @@ of the constructor, like this:
         $driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files1', '/path/to/files2'));
         $config->setMetadataDriverImpl($driver);
     
    +Simplified XML Driver
    +~~~~~~~~~~~~~~~~~~~~~
    +
    +The Symfony project sponsored a driver that simplifies usage of the XML Driver.
    +The changes between the original driver are:
    +
    +1. File Extension is .orm.xml
    +2. Filenames are shortened, "MyProject\Entities\User" will become User.orm.xml
    +3. You can add a global file and add multiple entities in this file.
    +
    +Configuration of this client works a little bit different:
    +
    +.. code-block:: php
    +
    +     '/path/to/files1',
    +        'OtherProject\Entities' => '/path/to/files2'
    +    );
    +    $driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver($namespaces);
    +    $driver->setGlobalBasename('global'); // global.orm.xml
    +
     Example
     -------
     
    diff --git a/en/reference/yaml-mapping.rst b/en/reference/yaml-mapping.rst
    index 219866e80..e173b2340 100644
    --- a/en/reference/yaml-mapping.rst
    +++ b/en/reference/yaml-mapping.rst
    @@ -39,6 +39,28 @@ of the constructor, like this:
         $driver = new YamlDriver(array('/path/to/files'));
         $config->setMetadataDriverImpl($driver);
     
    +Simplified YAML Driver
    +~~~~~~~~~~~~~~~~~~~~~
    +
    +The Symfony project sponsored a driver that simplifies usage of the YAML Driver.
    +The changes between the original driver are:
    +
    +1. File Extension is .orm.yml
    +2. Filenames are shortened, "MyProject\Entities\User" will become User.orm.yml
    +3. You can add a global file and add multiple entities in this file.
    +
    +Configuration of this client works a little bit different:
    +
    +.. code-block:: php
    +
    +     '/path/to/files1',
    +        'OtherProject\Entities' => '/path/to/files2'
    +    );
    +    $driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver($namespaces);
    +    $driver->setGlobalBasename('global'); // global.orm.yml
    +
     Example
     -------
     
    
    From 6d4337fc71ee6e14497318294951b423b896aee7 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 15 Oct 2011 16:14:36 +0200
    Subject: [PATCH 270/430] More details on partial objects
    
    ---
     en/reference/partial-objects.rst | 20 ++++++++++++++++++++
     1 file changed, 20 insertions(+)
    
    diff --git a/en/reference/partial-objects.rst b/en/reference/partial-objects.rst
    index 5ce07e5ef..396eecce3 100644
    --- a/en/reference/partial-objects.rst
    +++ b/en/reference/partial-objects.rst
    @@ -15,6 +15,14 @@ of Doctrine2 to this problem is.
         ``Query#getScalarResult()``, ``Query#getSingleScalarResult()``,
         etc.
     
    +.. warning::
    +
    +    Use of partial objects is tricky. Fields that are not retrieved
    +    from the database will not be updated by the UnitOfWork even if they
    +    get changed in your objects. You can only promote a partial object
    +    to a fully-loaded object by calling ``EntityManager#refresh()``
    +    or a DQL query with the refresh flag.
    +
     
     What is the problem?
     --------------------
    @@ -60,6 +68,18 @@ keyword as follows:
         createQuery("select partial u.{id,name} from MyApp\Domain\User u");
     
    +You can also get a partial reference instead of a proxy reference by
    +calling:
    +
    +.. code-block:: php
    +
    +    getPartialReference('MyApp\Domain\User', 1);
    +
    +Partial references are objects with only the identifiers set as they
    +are passed to the second argument of the ``getPartialReference()`` method.
    +All other fields are null.
    +
     When should I force partial objects?
     ------------------------------------
     
    
    From 237c20c9b69c8d502541a12e3fa5d1af1a3d885b Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 15 Oct 2011 18:09:27 +0200
    Subject: [PATCH 271/430] Enhanced docs on UnitOfWork#computeChangeSets
    
    ---
     en/reference/events.rst | 8 +++-----
     1 file changed, 3 insertions(+), 5 deletions(-)
    
    diff --git a/en/reference/events.rst b/en/reference/events.rst
    index 513de8b21..1a31cb1a5 100644
    --- a/en/reference/events.rst
    +++ b/en/reference/events.rst
    @@ -471,16 +471,14 @@ mentioned sets. See this example:
     The following restrictions apply to the onFlush event:
     
     
    --  Calling ``EntityManager#persist()`` does not suffice to trigger
    -   a persist on an entity. You have to execute an additional call to
    +-  If you create and persist a new entity in "onFlush", then
    +   calling ``EntityManager#persist()`` is not enough.
    +   You have to execute an additional call to
        ``$unitOfWork->computeChangeSet($classMetadata, $entity)``.
     -  Changing primitive fields or associations requires you to
        explicitly trigger a re-computation of the changeset of the
        affected entity. This can be done by either calling
    -   ``$unitOfWork->computeChangeSet($classMetadata, $entity)`` or
        ``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``.
    -   The second method has lower overhead, but only re-computes
    -   primitive fields, never associations.
     
     preUpdate
     ~~~~~~~~~
    
    From 0a989e63d2375797b43174b9eb29a0e41d83b1f2 Mon Sep 17 00:00:00 2001
    From: Luis Cordova 
    Date: Tue, 25 Oct 2011 15:08:52 -0500
    Subject: [PATCH 272/430] two typos fixed
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index e3920a5f0..6ef488ec4 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -364,8 +364,8 @@ or an engineer is assigned to the bug, we don't want to update the
     User to persist the reference, but the Bug. This is the case with
     the Bug being at the owning side of the relation.
     
    -Bugs reference Products by a uni-directional ManyToMany relation in
    -the database that points from from Bugs to Products.
    +Bugs reference Products by an uni-directional ManyToMany relation in
    +the database that points from Bugs to Products.
     
     .. code-block:: php
     
    
    From 1bc0efba436553d059272f4a3b81f83880cb42cb Mon Sep 17 00:00:00 2001
    From: Luis Cordova 
    Date: Tue, 25 Oct 2011 15:33:30 -0500
    Subject: [PATCH 273/430] typo change from contains to consists of
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 6ef488ec4..b6722563e 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -748,7 +748,7 @@ We assume here that you have installed Doctrine using PEAR.
     See :doc:`Configuration <../reference/configuration>` for more details
     on other installation procedures.
     
    -The second block contains of the instantiation of the ORM
    +The second block consists of the instantiation of the ORM
     Configuration object using the Setup helper. It assumes a bunch
     of defaults that you don't have to bother about for now. You can
     read up on the configuration details in the
    
    From cf44745d08416b5f1eeadcffbbd68290157ee538 Mon Sep 17 00:00:00 2001
    From: Luis Cordova 
    Date: Tue, 25 Oct 2011 15:50:29 -0500
    Subject: [PATCH 274/430] corrected rst for bash like code blocks
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 6ef488ec4..817b9aa04 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -898,7 +898,7 @@ Products can also be created:
     
     Now call:
     
    -..
    +::
     
         $ php create_user.php beberlei
         $ php create_product.php MyProduct
    @@ -969,7 +969,7 @@ code for this scenario may look like this:
     
     Since we only have one user and product, probably with the ID of 1, we can call this script with:
     
    -..
    +::
     
         php create_bug.php 1 1 1
     
    
    From e98cb4f145426860b0b26b47f633473073c7f665 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Wed, 26 Oct 2011 00:54:06 +0300
    Subject: [PATCH 275/430] Document AbstractQuery#getOneOrNullResult()
    
    ---
     en/reference/dql-doctrine-query-language.rst | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index fb5e7123f..b8540e17b 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -858,8 +858,10 @@ the Query class. Here they are:
        result is either a plain collection of objects (pure) or an array
        where the objects are nested in the result rows (mixed).
     -  ``Query#getSingleResult()``: Retrieves a single object. If the
    -   result contains more than one object, an exception is thrown. The
    +   result contains more than one or no object, an exception is thrown. The
        pure/mixed distinction does not apply.
    +-  ``Query#getOneOrNullResult()``: Retrieve a single object. If no
    +   object is found null will be returned.
     -  ``Query#getArrayResult()``: Retrieves an array graph (a nested
        array) that is largely interchangeable with the object graph
        generated by ``Query#getResult()`` for read-only purposes.
    
    From 78ef07f63031d116384af8e3840069b0f9953015 Mon Sep 17 00:00:00 2001
    From: Alexander 
    Date: Wed, 26 Oct 2011 14:12:32 +0300
    Subject: [PATCH 276/430] Update en/reference/working-with-objects.rst
    
    ---
     en/reference/working-with-objects.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/reference/working-with-objects.rst b/en/reference/working-with-objects.rst
    index c235828ae..5a9261f6c 100644
    --- a/en/reference/working-with-objects.rst
    +++ b/en/reference/working-with-objects.rst
    @@ -705,14 +705,14 @@ The ``EntityRepository#findBy()`` method additionally accepts orderings, limit a
     .. code-block:: php
     
         getRepository('MyProject\Domain\User')-findBy(array('age' => 20), array('name' => 'ASC'), 10, 0);
    +    $tenUsers = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20), array('name' => 'ASC'), 10, 0);
     
     If you pass an array of values Doctrine will convert the query into a WHERE field IN (..) query automatically:
     
     .. code-block:: php
     
         getRepository('MyProject\Domain\User')-findBy(array('age' => array(20, 30, 40)));
    +    $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => array(20, 30, 40)));
         // translates roughly to: SELECT * FROM users WHERE age IN (20, 30, 40)
     
     An EntityRepository also provides a mechanism for more concise
    
    From c21aaebbc4159f27d6db1194d585ba8895057f6a Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Micha=C5=82=20Pipa?= 
    Date: Mon, 31 Oct 2011 17:09:15 +0100
    Subject: [PATCH 277/430] Changed 'Symfony 2' to 'Symfony2'.
    
    http://symfony.com/blog/talk-about-symfony2-not-symfony-2
    ---
     en/reference/faq.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/faq.rst b/en/reference/faq.rst
    index 91d39a291..9c5b1faad 100644
    --- a/en/reference/faq.rst
    +++ b/en/reference/faq.rst
    @@ -58,7 +58,7 @@ or adding entities to a collection twice. You have to check for both conditions
     in the code before calling ``$em->flush()`` if you know that unique constraint failures
     can occur.
     
    -In `Symfony 2 `_ for example there is a Unique Entity Validator
    +In `Symfony2 `_ for example there is a Unique Entity Validator
     to achieve this task.
     
     For collections you can check with ``$collection->contains($entity)`` if an entity is already
    
    From 299642083b25a84d42d7986d49d01901968047be Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Micha=C5=82=20Pipa?= 
    Date: Mon, 31 Oct 2011 17:10:30 +0100
    Subject: [PATCH 278/430] Fixed broken link.
    
    ---
     en/reference/basic-mapping.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst
    index 1fa585a58..0e1ace490 100644
    --- a/en/reference/basic-mapping.rst
    +++ b/en/reference/basic-mapping.rst
    @@ -192,7 +192,7 @@ built-in mapping types:
     
         If you need specific timezone handling you have to handle this
         in your domain, converting all the values back and forth from UTC.
    -    There is also a `cookbook entry <../cookbook/working-with-datetime>`
    +    There is also a :doc:`cookbook entry <../cookbook/working-with-datetime>`
         on working with datetimes that gives hints for implementing
         multi timezone applications.
     
    
    From c0f86e796dc647fdc44bb6815dd146ad3ab382a4 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Micha=C5=82=20Pipa?= 
    Date: Mon, 7 Nov 2011 07:53:05 +0100
    Subject: [PATCH 279/430] Changed external link to internal link.
    
    ---
     en/reference/working-with-objects.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/working-with-objects.rst b/en/reference/working-with-objects.rst
    index 5a9261f6c..531a14f50 100644
    --- a/en/reference/working-with-objects.rst
    +++ b/en/reference/working-with-objects.rst
    @@ -788,7 +788,7 @@ statements native queries can be used. Native queries are built by
     using a hand-crafted SQL query and a ResultSetMapping that
     describes how the SQL result set should be transformed by Doctrine.
     More information about native queries can be found in
    -`the dedicated chapter `_.
    +:doc:`the dedicated chapter `.
     
     Custom Repositories
     ~~~~~~~~~~~~~~~~~~~
    
    From 0eed56fae9343abc5cd106b904fef87af1913f41 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Micha=C5=82=20Pipa?= 
    Date: Mon, 7 Nov 2011 08:18:22 +0100
    Subject: [PATCH 280/430] Fixed indentation.
    
    ---
     en/reference/working-with-associations.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst
    index 3772b8085..b93293e1e 100644
    --- a/en/reference/working-with-associations.rst
    +++ b/en/reference/working-with-associations.rst
    @@ -383,7 +383,7 @@ Synchronizing Bidirectional Collections
     
     In the case of Many-To-Many associations you as the developer have the 
     responsibility of keeping the collections on the owning and inverse side
    - in sync when you apply changes to them. Doctrine can only
    +in sync when you apply changes to them. Doctrine can only
     guarantee a consistent state for the hydration, not for your client
     code.
     
    
    From 64200c405e2b44da5663a61d3f520da75e374102 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Micha=C5=82=20Pipa?= 
    Date: Mon, 7 Nov 2011 08:19:20 +0100
    Subject: [PATCH 281/430] Fixed title underline.
    
    ---
     en/reference/yaml-mapping.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/yaml-mapping.rst b/en/reference/yaml-mapping.rst
    index e173b2340..ce614b6d1 100644
    --- a/en/reference/yaml-mapping.rst
    +++ b/en/reference/yaml-mapping.rst
    @@ -40,7 +40,7 @@ of the constructor, like this:
         $config->setMetadataDriverImpl($driver);
     
     Simplified YAML Driver
    -~~~~~~~~~~~~~~~~~~~~~
    +~~~~~~~~~~~~~~~~~~~~~~
     
     The Symfony project sponsored a driver that simplifies usage of the YAML Driver.
     The changes between the original driver are:
    
    From cfe5424cf9f695f7bdcab26ff80d4fcc28e38708 Mon Sep 17 00:00:00 2001
    From: Tomas Paladin Volf 
    Date: Tue, 8 Nov 2011 14:44:48 +0100
    Subject: [PATCH 282/430] Fixed pear command for install, "boostrap" =>
     "bootstrap"
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index b6722563e..61da8010c 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -68,7 +68,7 @@ on your CLI:
     ::
     
         $ pear channel-discover pear.doctrine-project.org
    -    $ pear install doctrine/DoctrineORM --all-deps
    +    $ pear install --alldeps doctrine/DoctrineORM
     
     This should install the packages DoctrineCommon, DoctrineDBAL, DoctrineORM,
     SymfonyConsole and SymfonyYAML.
    @@ -779,7 +779,7 @@ We also have to create a general bootstrap file for our application:
     .. code-block:: php
     
         
    Date: Tue, 15 Nov 2011 22:10:56 +0100
    Subject: [PATCH 283/430] Fix YAML syntax errors in examples
    
    ---
     en/reference/association-mapping.rst | 8 ++++----
     1 file changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst
    index 1b42dcc3f..6a9cb9d74 100644
    --- a/en/reference/association-mapping.rst
    +++ b/en/reference/association-mapping.rst
    @@ -276,7 +276,7 @@ mapping:
                       User_id:
                         referencedColumnName: id
                     inverseJoinColumns:
    -                  Group_id
    +                  Group_id:
                         referencedColumnName: id                  
     
     In that case, the name of the join table defaults to a combination
    @@ -532,10 +532,10 @@ it is bidirectional.
             Cart:
               oneToOne:
                 customer:
    -              targetEntity Customer
    +              targetEntity: Customer
                   inversedBy: cart
                   joinColumn:
    -                name: customer_id:
    +                name: customer_id
                     referencedColumnName: id
     
     Note that the @JoinColumn is not really necessary in this example,
    @@ -892,7 +892,7 @@ database perspective is known as an adjacency list approach.
             Category:
               type: entity
               oneToMany:
    -            children
    +            children:
                   targetEntity: Category
                   mappedBy: parent
               manyToOne:
    
    From 51211980a4c4f79bfa2166a94845e76d00410a7f Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Tue, 15 Nov 2011 22:21:06 +0100
    Subject: [PATCH 284/430] Remove ON in docs
    
    ---
     en/reference/query-builder.rst | 158 ++++++++++++++++-----------------
     1 file changed, 79 insertions(+), 79 deletions(-)
    
    diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst
    index c9dbaace6..0217719fd 100644
    --- a/en/reference/query-builder.rst
    +++ b/en/reference/query-builder.rst
    @@ -20,7 +20,7 @@ how to build a ``QueryBuilder`` object:
     
         createQueryBuilder();
     
    @@ -32,7 +32,7 @@ example is to inspect what type of object the ``QueryBuilder`` is.
     
         getType(); // Prints: 0
     
    @@ -51,13 +51,13 @@ you finish building your DQL.
     
         getEntityManager();
    -    
    +
         // example4: retrieve the DQL string of what was defined in QueryBuilder
         $dql = $qb->getDql();
    -    
    +
         // example5: retrieve the associated Query object with the processed DQL
         $q = $qb->getQuery();
     
    @@ -96,7 +96,7 @@ of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
     
         add('select', 'u')
            ->add('from', 'User u')
    @@ -116,7 +116,7 @@ allowed. Binding parameters can simply be achieved as follows:
     
         add('select', 'u')
            ->add('from', 'User u')
    @@ -131,7 +131,7 @@ alternative syntax is available:
     
         add('select', 'u')
            ->add('from', 'User u')
    @@ -150,7 +150,7 @@ following syntax:
     
         setParameters(array(1 => 'value for ?1', 2 => 'value for ?2'));
     
    @@ -161,7 +161,7 @@ mentioned syntax with "getParameter()" or "getParameters()":
     
         getParameters(array(1, 2));
         // Equivalent to
    @@ -226,7 +226,7 @@ same query of example 6 written using
     
         add('select', new Expr\Select(array('u')))
            ->add('from', new Expr\From('User', 'u'))
    @@ -249,7 +249,7 @@ set of useful methods to help build expressions:
     
         add('select', new Expr\Select(array('u')))
            ->add('from', new Expr\From('User', 'u'))
    @@ -268,32 +268,32 @@ complete list of supported helper methods available:
         expr()->andX($cond1 [, $condN])->add(...)->...
             public function andX($x = null); // Returns Expr\AndX instance
    -    
    +
             // Example - $qb->expr()->orX($cond1 [, $condN])->add(...)->...
             public function orX($x = null); // Returns Expr\OrX instance
    -    
    -    
    +
    +
             /** Comparison objects **/
    -    
    +
             // Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1
             public function eq($x, $y); // Returns Expr\Comparison instance
    -    
    +
             // Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1
             public function neq($x, $y); // Returns Expr\Comparison instance
    -    
    +
             // Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1
             public function lt($x, $y); // Returns Expr\Comparison instance
    -    
    +
             // Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1
             public function lte($x, $y); // Returns Expr\Comparison instance
    -    
    +
             // Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1
             public function gt($x, $y); // Returns Expr\Comparison instance
    -    
    +
             // Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1
             public function gte($x, $y); // Returns Expr\Comparison instance
     
    @@ -302,93 +302,93 @@ complete list of supported helper methods available:
     
             // Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
             public function isNotNull($x); // Returns string
    -    
    -    
    +
    +
             /** Arithmetic objects **/
    -    
    +
             // Example - $qb->expr()->prod('u.id', '2') => u.id * 2
             public function prod($x, $y); // Returns Expr\Math instance
    -    
    +
             // Example - $qb->expr()->diff('u.id', '2') => u.id - 2
             public function diff($x, $y); // Returns Expr\Math instance
    -    
    +
             // Example - $qb->expr()->sum('u.id', '2') => u.id + 2
             public function sum($x, $y); // Returns Expr\Math instance
    -    
    +
             // Example - $qb->expr()->quot('u.id', '2') => u.id / 2
             public function quot($x, $y); // Returns Expr\Math instance
    -    
    -    
    +
    +
             /** Pseudo-function objects **/
    -    
    +
             // Example - $qb->expr()->exists($qb2->getDql())
             public function exists($subquery); // Returns Expr\Func instance
    -    
    +
             // Example - $qb->expr()->all($qb2->getDql())
             public function all($subquery); // Returns Expr\Func instance
    -    
    +
             // Example - $qb->expr()->some($qb2->getDql())
             public function some($subquery); // Returns Expr\Func instance
    -    
    +
             // Example - $qb->expr()->any($qb2->getDql())
             public function any($subquery); // Returns Expr\Func instance
    -    
    +
             // Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1'))
             public function not($restriction); // Returns Expr\Func instance
    -    
    +
             // Example - $qb->expr()->in('u.id', array(1, 2, 3))
             // Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception.
             // Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above)
             public function in($x, $y); // Returns Expr\Func instance
    -    
    +
             // Example - $qb->expr()->notIn('u.id', '2')
             public function notIn($x, $y); // Returns Expr\Func instance
    -    
    +
             // Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%'))
             public function like($x, $y); // Returns Expr\Comparison instance
    -    
    +
             // Example - $qb->expr()->between('u.id', '1', '10')
             public function between($val, $x, $y); // Returns Expr\Func
    -    
    -    
    +
    +
             /** Function objects **/
    -    
    +
             // Example - $qb->expr()->trim('u.firstname')
             public function trim($x); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat(' ', 'u.lastname'))
             public function concat($x, $y); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->substr('u.firstname', 0, 1)
             public function substr($x, $from, $len); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->lower('u.firstname')
             public function lower($x); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->upper('u.firstname')
             public function upper($x); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->length('u.firstname')
             public function length($x); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->avg('u.age')
             public function avg($x); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->max('u.age')
             public function max($x); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->min('u.age')
             public function min($x); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->abs('u.currentBalance')
             public function abs($x); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->sqrt('u.currentBalance')
             public function sqrt($x); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->count('u.firstname')
             public function count($x); // Returns Expr\Func
    -    
    +
             // Example - $qb->expr()->countDistinct('u.surname')
             public function countDistinct($x); // Returns Expr\Func
         }
    @@ -410,7 +410,7 @@ re-written using ``QueryBuilder`` helper methods:
     
         select('u')
            ->from('User', 'u')
    @@ -427,7 +427,7 @@ suggested standard way to build queries:
     
         select(array('u')) // string 'u' is converted to array internally
            ->from('User', 'u')
    @@ -449,67 +449,67 @@ Here is a complete list of helper methods available in
             // Example - $qb->select(array('u', 'p'))
             // Example - $qb->select($qb->expr()->select('u', 'p'))
             public function select($select = null);
    -    
    +
             // Example - $qb->delete('User', 'u')
             public function delete($delete = null, $alias = null);
    -    
    +
             // Example - $qb->update('Group', 'g')
             public function update($update = null, $alias = null);
    -    
    +
             // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold'))
             // Example - $qb->set('u.numChilds', 'u.numChilds + ?1')
             // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1'))
             public function set($key, $value);
    -    
    +
             // Example - $qb->from('Phonenumber', 'p')
             public function from($from, $alias = null);
    -    
    -        // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::ON, $qb->expr()->and($qb->expr()->eq('u.group_id', 'g.id'), 'g.name = ?1'))
    -        // Example - $qb->innerJoin('u.Group', 'g', 'ON', 'u.group_id = g.id AND g.name = ?1')
    +
    +        // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1'))
    +        // Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1')
             public function innerJoin($join, $alias = null, $conditionType = null, $condition = null);
    -    
    +
             // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55))
    -        // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55')    
    +        // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55')
             public function leftJoin($join, $alias = null, $conditionType = null, $condition = null);
    -    
    +
             // NOTE: ->where() overrides all previously set conditions
             //
             // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2'))
             // Example - $qb->where($qb->expr()->andX($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2')))
    -        // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2')    
    +        // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2')
             public function where($where);
    -    
    +
             // Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0'))
             public function andWhere($where);
    -    
    +
             // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10));
             public function orWhere($where);
    -    
    +
             // NOTE: -> groupBy() overrides all previously set grouping conditions
             //
    -        // Example - $qb->groupBy('u.id')   
    +        // Example - $qb->groupBy('u.id')
             public function groupBy($groupBy);
    -    
    +
             // Example - $qb->addGroupBy('g.name')
             public function addGroupBy($groupBy);
    -    
    +
             // NOTE: -> having() overrides all previously set having conditions
             //
             // Example - $qb->having('u.salary >= ?1')
             // Example - $qb->having($qb->expr()->gte('u.salary', '?1'))
             public function having($having);
    -    
    +
             // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0))
             public function andHaving($having);
    -    
    -        // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100'))    
    +
    +        // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100'))
             public function orHaving($having);
    -    
    +
             // NOTE: -> orderBy() overrides all previously set ordering conditions
             //
             // Example - $qb->orderBy('u.surname', 'DESC')
             public function orderBy($sort, $order = null);
    -    
    +
             // Example - $qb->addOrderBy('u.firstName')
             public function addOrderBy($sort, $order = null); // Default $order = 'ASC'
         }
    
    From 2200c1f7e1afd63d86c0a912ced131e80cfdba66 Mon Sep 17 00:00:00 2001
    From: Marco Pivetta 
    Date: Wed, 16 Nov 2011 20:00:21 +0100
    Subject: [PATCH 285/430] Fixing invalid XML mapping samples
    
    ---
     en/reference/association-mapping.rst | 8 ++++----
     1 file changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst
    index 6a9cb9d74..aed143815 100644
    --- a/en/reference/association-mapping.rst
    +++ b/en/reference/association-mapping.rst
    @@ -960,10 +960,10 @@ entities:
                     
                         
                             
    -                            
    +                            
                             
                             
    -                            
    +                            
                             
                         
                     
    @@ -1067,10 +1067,10 @@ one is bidirectional.
                     
                         
                             
    -                            
    +                            
                             
                             
    -                            
    +                            
                             
                         
                     
    
    From b9b05fc3eb65f54179aa58c17c2e40312bbe9026 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Fri, 18 Nov 2011 23:10:40 +0100
    Subject: [PATCH 286/430] Fix for DDC-1293
    
    ---
     en/reference/dql-doctrine-query-language.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index b8540e17b..2e7282e67 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -557,8 +557,8 @@ clauses:
     -  TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim
        the string by the given trim char, defaults to whitespaces.
     -  UPPER(str) - Return the upper-case of the given string.
    --  DATE_ADD(date, days) - Add the number of days to a given date.
    --  DATE_SUB(date, days) - Substract the number of days from a given date.
    +-  DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH)
    +-  DATE_SUB(date, days, unit) - Substract the number of days from a given date. (Supported units are DAY, MONTH)
     -  DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2.
     
     Arithmetic operators
    
    From 129b9d0945247690c72917ba5711e2ad8f083237 Mon Sep 17 00:00:00 2001
    From: jsor 
    Date: Mon, 28 Nov 2011 11:52:17 +0100
    Subject: [PATCH 287/430] Setup article
    
    ---
     en/cookbook/custom-mapping-type-value-sql.rst | 8 ++++++++
     en/reference/basic-mapping.rst                | 2 ++
     2 files changed, 10 insertions(+)
     create mode 100644 en/cookbook/custom-mapping-type-value-sql.rst
    
    diff --git a/en/cookbook/custom-mapping-type-value-sql.rst b/en/cookbook/custom-mapping-type-value-sql.rst
    new file mode 100644
    index 000000000..83d19e968
    --- /dev/null
    +++ b/en/cookbook/custom-mapping-type-value-sql.rst
    @@ -0,0 +1,8 @@
    +Database-level field value conversion using custom mapping types
    +================================================================
    +
    +.. sectionauthor:: Jan Sorgalla 
    +
    +When creating entities, you sometimes have the need to transform field values
    +before they are saved to the database. In Doctrine you can use Custom Mapping 
    +Types to solve this (see: :ref:`my-reference-label`). 
    diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst
    index 0e1ace490..146c71d6f 100644
    --- a/en/reference/basic-mapping.rst
    +++ b/en/reference/basic-mapping.rst
    @@ -300,6 +300,8 @@ list:
     -  ``scale``: (optional, default 0) The scale for a decimal (exact
        numeric) column. (Applies only if a decimal column is used.)
     
    +.. _reference-basic_mapping-custom_mapping_types:
    +
     Custom Mapping Types
     --------------------
     
    
    From 1300758499d950bbb5e827f036f4734b9c7dfd78 Mon Sep 17 00:00:00 2001
    From: jsor 
    Date: Mon, 28 Nov 2011 15:19:49 +0100
    Subject: [PATCH 288/430] Rename article and add more text
    
    ---
     ...-conversion-using-custom-mapping-types.rst | 91 +++++++++++++++++++
     en/cookbook/custom-mapping-type-value-sql.rst |  8 --
     en/index.rst                                  |  3 +-
     3 files changed, 93 insertions(+), 9 deletions(-)
     create mode 100644 en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
     delete mode 100644 en/cookbook/custom-mapping-type-value-sql.rst
    
    diff --git a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    new file mode 100644
    index 000000000..73f1b10a8
    --- /dev/null
    +++ b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    @@ -0,0 +1,91 @@
    +Advanced field value conversion using custom mapping types
    +==========================================================
    +
    +.. sectionauthor:: Jan Sorgalla 
    +
    +When creating entities, you sometimes have the need to transform field values
    +before they are saved to the database. In Doctrine you can use Custom Mapping 
    +Types to solve this (see: :ref:`reference-basic_mapping-custom_mapping_types`).
    +
    +There are several ways to achieve this: converting the value inside the Type
    +class, converting the value on the database-level or a combination of both.
    +
    +This article describes the third way by implementing the MySQL specific column
    +type `Point `_.
    +
    +The `Point` type is part of the `Spatial extension `_
    +of MySQL and enables you to store a single location in a coordinate space by
    +using x and y coordinates.
    +
    +As you might have already guessed, you can use the Point type to store a 
    +longitude/latitude pair to represent a geographic location.
    +
    +The entity
    +----------
    +
    +We create a simple entity which contains a field ``$point`` which should hold
    +a value object ``Point`` representing the latitude and longitude of the position.
    +
    +The entity class:
    +
    +.. code-block:: php
    +
    +    point = $point;
    +        }
    +
    +        /**
    +         * @return \Geo\Point
    +         */
    +        public function getPoint()
    +        {
    +            return $this->point;
    +        }
    +    }
    +
    +The point class:
    +
    +.. code-block:: php
    +
    +    latitude  = $latitude;
    +            $this->longitude = $longitude;
    +        }
    +
    +        public function getLatitude()
    +        {
    +            return $this->latitude;
    +        }
    +
    +        public function getLongitude()
    +        {
    +            return $this->longitude;
    +        }
    +    }
    diff --git a/en/cookbook/custom-mapping-type-value-sql.rst b/en/cookbook/custom-mapping-type-value-sql.rst
    deleted file mode 100644
    index 83d19e968..000000000
    --- a/en/cookbook/custom-mapping-type-value-sql.rst
    +++ /dev/null
    @@ -1,8 +0,0 @@
    -Database-level field value conversion using custom mapping types
    -================================================================
    -
    -.. sectionauthor:: Jan Sorgalla 
    -
    -When creating entities, you sometimes have the need to transform field values
    -before they are saved to the database. In Doctrine you can use Custom Mapping 
    -Types to solve this (see: :ref:`my-reference-label`). 
    diff --git a/en/index.rst b/en/index.rst
    index ef2638705..b4816c73c 100644
    --- a/en/index.rst
    +++ b/en/index.rst
    @@ -66,4 +66,5 @@ Cookbook
        cookbook/strategy-cookbook-introduction
        cookbook/validation-of-entities
        cookbook/working-with-datetime
    -   cookbook/mysql-enums
    \ No newline at end of file
    +   cookbook/mysql-enums
    +   cookbook/advanced-field-value-conversion-using-custom-mapping-types
    \ No newline at end of file
    
    From bece5f0f915dcd03cb0ce79dcce856e750e161e3 Mon Sep 17 00:00:00 2001
    From: jsor 
    Date: Mon, 28 Nov 2011 15:22:55 +0100
    Subject: [PATCH 289/430] Change ref name
    
    ---
     ...vanced-field-value-conversion-using-custom-mapping-types.rst | 2 +-
     en/reference/basic-mapping.rst                                  | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    index 73f1b10a8..5b79d2c4d 100644
    --- a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    +++ b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    @@ -5,7 +5,7 @@ Advanced field value conversion using custom mapping types
     
     When creating entities, you sometimes have the need to transform field values
     before they are saved to the database. In Doctrine you can use Custom Mapping 
    -Types to solve this (see: :ref:`reference-basic_mapping-custom_mapping_types`).
    +Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`).
     
     There are several ways to achieve this: converting the value inside the Type
     class, converting the value on the database-level or a combination of both.
    diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst
    index 146c71d6f..90970de2e 100644
    --- a/en/reference/basic-mapping.rst
    +++ b/en/reference/basic-mapping.rst
    @@ -300,7 +300,7 @@ list:
     -  ``scale``: (optional, default 0) The scale for a decimal (exact
        numeric) column. (Applies only if a decimal column is used.)
     
    -.. _reference-basic_mapping-custom_mapping_types:
    +.. _reference-basic-mapping-custom-mapping-types:
     
     Custom Mapping Types
     --------------------
    
    From 228d8517c75d89a10fb12640e5f190a34aec9be0 Mon Sep 17 00:00:00 2001
    From: Jan Sorgalla 
    Date: Mon, 28 Nov 2011 21:06:01 +0100
    Subject: [PATCH 290/430] Add type
    
    ---
     ...-conversion-using-custom-mapping-types.rst | 72 +++++++++++++++++--
     1 file changed, 68 insertions(+), 4 deletions(-)
    
    diff --git a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    index 5b79d2c4d..addf2751d 100644
    --- a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    +++ b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    @@ -23,8 +23,8 @@ longitude/latitude pair to represent a geographic location.
     The entity
     ----------
     
    -We create a simple entity which contains a field ``$point`` which should hold
    -a value object ``Point`` representing the latitude and longitude of the position.
    +We create a simple entity whith a field ``$point`` which holds a value object 
    +``Point`` representing the latitude and longitude of the position.
     
     The entity class:
     
    @@ -32,7 +32,7 @@ The entity class:
     
         longitude;
             }
         }
    +
    +The mapping type
    +----------------
    +
    +As you may have noticed, we used the custom type ``point`` in the ``@Column`` 
    +docblock annotation of the ``$point`` field.
    +
    +Now we're going to create this type and implement all required methods.
    +
    +.. code-block:: php
    +
    +    getLongitude(), $value->getLatitude());
    +            }
    +
    +            return $value;
    +        }
    +
    +        public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform)
    +        {
    +            return sprintf('AsText(%s)', $sqlExpr);
    +        }
    +
    +        public function convertToDatabaseValue($sqlExpr, AbstractPlatform $platform)
    +        {
    +            return sprintf('GeomFromText(%s)', $sqlExpr);
    +        }
    +    }
    +
    +A few notes about the implementation:
    +
    +  * 
    \ No newline at end of file
    
    From cbefb7c543f6f53db8a61bba4af5759a3706c0f5 Mon Sep 17 00:00:00 2001
    From: jsor 
    Date: Tue, 29 Nov 2011 10:31:07 +0100
    Subject: [PATCH 291/430] Finalize first version
    
    ---
     ...-conversion-using-custom-mapping-types.rst | 128 +++++++++++++++---
     1 file changed, 112 insertions(+), 16 deletions(-)
    
    diff --git a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    index addf2751d..4aad58919 100644
    --- a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    +++ b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    @@ -13,11 +13,9 @@ class, converting the value on the database-level or a combination of both.
     This article describes the third way by implementing the MySQL specific column
     type `Point `_.
     
    -The `Point` type is part of the `Spatial extension `_
    +The ``Point`` type is part of the `Spatial extension `_
     of MySQL and enables you to store a single location in a coordinate space by
    -using x and y coordinates.
    -
    -As you might have already guessed, you can use the Point type to store a 
    +using x and y coordinates. You can use the Point type to store a 
     longitude/latitude pair to represent a geographic location.
     
     The entity
    @@ -42,27 +40,53 @@ The entity class:
             /**
              * @Column(type="point")
              *
    -         * @var \Geo\Point
    +         * @var \Geo\ValueObject\Point
              */
             private $point;
     
             /**
    -         * @param \Geo\Point $point
    +         * @Column(type="string")
    +         *
    +         * @var string
              */
    -        public function setPoint(\Geo\Point $point)
    +        private $address;
    +
    +        /**
    +         * @param \Geo\ValueObject\Point $point
    +         */
    +        public function setPoint(\Geo\ValueObject\Point $point)
             {
                 $this->point = $point;
             }
     
             /**
    -         * @return \Geo\Point
    +         * @return \Geo\ValueObject\Point
              */
             public function getPoint()
             {
                 return $this->point;
             }
    +
    +        /**
    +         * @param string $address
    +         */
    +        public function setAddress($address)
    +        {
    +            $this->address = $address;
    +        }
    +
    +        /**
    +         * @return string
    +         */
    +        public function getAddress()
    +        {
    +            return $this->address;
    +        }
         }
     
    +We use the custom type ``point`` in the ``@Column``  docblock annotation of the 
    +``$point`` field. We will create this custom mapping type in the next chapter.
    +
     The point class:
     
     .. code-block:: php
    @@ -73,17 +97,28 @@ The point class:
     
         class Point
         {
    +
    +        /**
    +         * @param float $latitude
    +         * @param float $longitude
    +         */
             public function __construct($latitude, $longitude)
             {
                 $this->latitude  = $latitude;
                 $this->longitude = $longitude;
             }
     
    +        /**
    +         * @return float
    +         */
             public function getLatitude()
             {
                 return $this->latitude;
             }
     
    +        /**
    +         * @return float
    +         */
             public function getLongitude()
             {
                 return $this->longitude;
    @@ -93,10 +128,7 @@ The point class:
     The mapping type
     ----------------
     
    -As you may have noticed, we used the custom type ``point`` in the ``@Column`` 
    -docblock annotation of the ``$point`` field.
    -
    -Now we're going to create this type and implement all required methods.
    +Now we're going to create the ``point`` type and implement all required methods.
     
     .. code-block:: php
     
    @@ -144,12 +176,76 @@ Now we're going to create this type and implement all required methods.
                 return sprintf('AsText(%s)', $sqlExpr);
             }
     
    -        public function convertToDatabaseValue($sqlExpr, AbstractPlatform $platform)
    +        public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
             {
    -            return sprintf('GeomFromText(%s)', $sqlExpr);
    +            return sprintf('PointFromText(%s)', $sqlExpr);
             }
         }
     
    -A few notes about the implementation:
    +We do a 2-step conversion here. In the first step, we convert the ``Point``
    +object into a string representation before saving to the database (in the
    +``convertToDatabaseValue`` method) and back into an object after fetching the
    +value from the database (in the ``convertToPHPValue`` method).
     
    -  * 
    \ No newline at end of file
    +The format of the string representation format is called `Well-known text (WKT)
    +`_. The advantage of this format
    +is, that it is both human readable and parsable by MySQL.
    +
    +Internally, MySQL stores geometry values in a binary format that is not 
    +identical to the WKT format. So, we need to let MySQL transform the WKT
    +representation into its internal format.
    +
    +This is where the ``convertToPHPValueSQL`` and  ``convertToDatabaseValueSQL``
    +methods come into play.
    +
    +This methods wrap a sql expression (the WKT representation of the Point) into
    +MySQL functions `PointFromText `_
    +and `AsText `_
    +which convert WKT strings to and from the internal format of MySQL.
    +
    +.. note::
    +
    +    When using DQL queries, the ``convertToPHPValueSQL`` and  
    +    ``convertToDatabaseValueSQL`` methods only apply to identification variables
    +    and path expressions in SELECT clauses. Expressions in  WHERE clauses are 
    +    **not** wrapped!
    +
    +    If you want to use Point values in WHERE clauses, you have to implement a
    +    :doc:`user defined function ` for 
    +    ``PointFromText``.
    +
    +Example usage
    +-------------
    +
    +.. code-block:: php
    +
    +    getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('point', 'point');
    +
    +    // Store a Location object
    +    use Geo\Entity\Location;
    +    use Geo\ValueObject\Point;
    +
    +    $location = new Location();
    +
    +    $location->setAddress('1600 Amphitheatre Parkway, Mountain View, CA');
    +    $location->setPoint(new Point(37.4220761, -122.0845187));
    +
    +    $em->persist($location);
    +    $em->flush();
    +    $em->clear();
    +
    +    // Fetch the Location object
    +    $query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
    +    $location = $query->getSingleResult();
    +
    +    /* @var Geo\ValueObject\Point */
    +    $point = $location->getPoint();
    
    From 69295ba07696c6e194edc9b5df60a6ad9e0eda69 Mon Sep 17 00:00:00 2001
    From: Jan Sorgalla 
    Date: Fri, 2 Dec 2011 20:59:17 +0100
    Subject: [PATCH 292/430] Add missing canRequireSQLConversion() in example type
     code
    
    ---
     ...ced-field-value-conversion-using-custom-mapping-types.rst | 5 +++++
     1 file changed, 5 insertions(+)
    
    diff --git a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    index 4aad58919..b20582f53 100644
    --- a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    +++ b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
    @@ -171,6 +171,11 @@ Now we're going to create the ``point`` type and implement all required methods.
                 return $value;
             }
     
    +        public function canRequireSQLConversion()
    +        {
    +            return true;
    +        }
    +
             public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform)
             {
                 return sprintf('AsText(%s)', $sqlExpr);
    
    From 220c6c4e0e0c50b95490a78a4422d23bdb66e8c0 Mon Sep 17 00:00:00 2001
    From: Marco Pivetta 
    Date: Tue, 13 Dec 2011 11:30:09 +0100
    Subject: [PATCH 293/430] Fixing minor issues reported by BostjanWrk on IRC
     about the RSM Builder example
    
    ---
     en/reference/native-sql.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/reference/native-sql.rst b/en/reference/native-sql.rst
    index 01b837d4a..4afe2e2f2 100644
    --- a/en/reference/native-sql.rst
    +++ b/en/reference/native-sql.rst
    @@ -380,9 +380,9 @@ in your sQL statement:
         $sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " . 
                "FROM users u INNER JOIN address a ON u.address_id = a.id";
     
    -    $rsm = new ResultSetMappingBuilder;
    +    $rsm = new ResultSetMappingBuilder($em);
         $rsm->addRootEntityFromClassMetadata('MyProject\User', 'u');
    -    $rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', array('id' => 'address_id'));
    +    $rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' => 'address_id'));
     
     For entites with more columns the builder is very convenient to use. It extends the ``ResultSetMapping`` class
     and as such has all the functionality of it as well. Currently the ``ResultSetMappingBuilder`` does not support
    
    From 5ea8861bf3ccdd10cfbbe0352b533ad537bb2b09 Mon Sep 17 00:00:00 2001
    From: ebernhardson 
    Date: Tue, 13 Dec 2011 10:36:14 -0800
    Subject: [PATCH 294/430] replace non-existant constant Lexer::T_ABS
    
    ---
     en/reference/dql-doctrine-query-language.rst | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index 2e7282e67..9886aed29 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -626,7 +626,8 @@ classes have to implement the base class :
         namespace MyProject\Query\AST;
         
         use \Doctrine\ORM\Query\AST\Functions\FunctionNode;
    -    
    +    use \Doctrine\ORM\Query\Lexer;
    +
         class MysqlFloor extends FunctionNode
         {
             public $simpleArithmeticExpression;
    @@ -642,7 +643,7 @@ classes have to implement the base class :
             {
                 $lexer = $parser->getLexer();
         
    -            $parser->match(Lexer::T_ABS);
    +            $parser->match(Lexer::T_IDENTIFIER);
                 $parser->match(Lexer::T_OPEN_PARENTHESIS);
         
                 $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
    
    From 22ac3a3099069432575962540810e1f8982b82b5 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 17 Dec 2011 16:28:31 +0100
    Subject: [PATCH 295/430] Add cookbook entry on saving entities in the session.
    
    ---
     en/cookbook/entities-in-session.rst | 68 +++++++++++++++++++++++++++++
     1 file changed, 68 insertions(+)
     create mode 100644 en/cookbook/entities-in-session.rst
    
    diff --git a/en/cookbook/entities-in-session.rst b/en/cookbook/entities-in-session.rst
    new file mode 100644
    index 000000000..48ea12249
    --- /dev/null
    +++ b/en/cookbook/entities-in-session.rst
    @@ -0,0 +1,68 @@
    +Entities in the Session
    +=======================
    +
    +There are several use-cases to save entities in the session, for example:
    +
    +1.  User object
    +2.  Multi-step forms
    +
    +To achieve this with Doctrine you have to pay attention to some details to get
    +this working.
    +
    +Merging entity into an EntityManager
    +------------------------------------
    +
    +In Doctrine an entity objects has to be "managed" by an EntityManager to be
    +updateable. Entities saved into the session are not managed in the next request
    +anymore. This means that you have to register these entities with an
    +EntityManager again if you want to change them or use them as part of
    +references between other entities. You can achieve this by calling
    +``EntityManager#merge()``.
    +
    +For a representative User object the code to get turn an instance from
    +the session into a managed Doctrine object looks like this:
    +
    +.. code-block:: php
    +
    +    merge($user);
    +    }
    +
    +.. note::
    +
    +    A frequent mistake is not to get the merged user object from the return
    +    value of ``EntityManager#merge()``. The entity object passed to merge is
    +    not necessarily the same object that is returned from the method.
    +
    +Serializing entity into the session
    +-----------------------------------
    +
    +Entities that are serialized into the session normally contain references to
    +other entities as well. Think of the user entity has a reference to his
    +articles, groups, photos or many other different entities. If you serialize
    +this object into the session then you don't want to serialize the related
    +entities aswell. This is why you should call ``EntityManager#detach()`` on this
    +object or implement the __sleep() magic method on your entity.
    +
    +.. code-block:: php
    +
    +    find("User", 1);
    +    $em->detach($user);
    +    $_SESSION['user'] = $user;
    +
    +.. note::
    +
    +    When you called detach on your objects they get "unmanaged" with that
    +    entity manager. This means you cannot use them as part of write operations
    +    during ``EntityManagr#flush()`` anymore in this request.
    +
    
    From 48acbf75cdfa1179838f93c7a31952ada3de2779 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 17 Dec 2011 16:28:46 +0100
    Subject: [PATCH 296/430] Reference new cookbook entry
    
    ---
     en/index.rst | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/en/index.rst b/en/index.rst
    index b4816c73c..36697b429 100644
    --- a/en/index.rst
    +++ b/en/index.rst
    @@ -67,4 +67,6 @@ Cookbook
        cookbook/validation-of-entities
        cookbook/working-with-datetime
        cookbook/mysql-enums
    -   cookbook/advanced-field-value-conversion-using-custom-mapping-types
    \ No newline at end of file
    +   cookbook/advanced-field-value-conversion-using-custom-mapping-types
    +   cookbook/entities-in-session
    +
    
    From e42d70a2b02cd2c69da63e72fc51e101a57db0e6 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sat, 17 Dec 2011 23:56:10 +0100
    Subject: [PATCH 297/430] Clarify how the CLI is setup
    
    ---
     en/reference/tools.rst | 18 ++++++++++++++++++
     1 file changed, 18 insertions(+)
    
    diff --git a/en/reference/tools.rst b/en/reference/tools.rst
    index 2e26ba0e4..ad40a7899 100644
    --- a/en/reference/tools.rst
    +++ b/en/reference/tools.rst
    @@ -34,6 +34,24 @@ about the use of generate entities for example, you can call:
     
         doctrine orm:generate-entities --help
     
    +Setting up the Console
    +----------------------
    +
    +Doctrine uses the Symfony Console component for generating the command
    +line interface. You can take a look at the ``bin/doctrine.php``
    +script and the ``Doctrine\ORM\Tools\Console\ConsoleRunner`` command
    +for inspiration how to setup the cli.
    +
    +In general the required code looks like this:
    +
    +.. code-block:: php
    +
    +        $cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
    +        $cli->setCatchExceptions(true);
    +        $cli->setHelperSet($helperSet);
    +        Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli);
    +        $cli->run();
    +
     Configuration
     ~~~~~~~~~~~~~
     
    
    From 43e4e1c38992e18f0c3258f4062ca0a2110d4663 Mon Sep 17 00:00:00 2001
    From: "Fabio B. Silva" 
    Date: Mon, 19 Dec 2011 18:35:01 -0200
    Subject: [PATCH 298/430] add doc for MappedSuperclass repositoryClass
    
    ---
     en/reference/annotations-reference.rst | 25 ++++++++++++++++++++++++-
     1 file changed, 24 insertions(+), 1 deletion(-)
    
    diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst
    index f82704778..1760ca731 100644
    --- a/en/reference/annotations-reference.rst
    +++ b/en/reference/annotations-reference.rst
    @@ -199,7 +199,7 @@ Optional attributes:
     
     
     -  **repositoryClass**: Specifies the FQCN of a subclass of the
    -   Doctrine. Use of repositories for entities is encouraged to keep
    +   EntityRepository. Use of repositories for entities is encouraged to keep
        specialized DQL and SQL operations separated from the Model/Domain
        Layer.
     -  **readOnly**: (>= 2.1) Specifies that this entity is marked as read only and not
    @@ -574,6 +574,29 @@ The @MappedSuperclass annotation cannot be used in conjunction with
     @Entity. See the Inheritance Mapping section for
     :doc:`more details on the restrictions of mapped superclasses `.
     
    +Optional attributes:
    +
    +
    +-  **repositoryClass**: (>= 2.2) Specifies the FQCN of a subclass of the EntityRepository.
    +   That will be inherited for all subclasses of that Mapped Superclass.
    +
    +Example:
    +
    +.. code-block:: php
    +
    +    
    Date: Tue, 20 Dec 2011 09:15:37 -0200
    Subject: [PATCH 299/430] add doc for default repository class
    
    ---
     en/reference/configuration.rst | 14 ++++++++++++++
     1 file changed, 14 insertions(+)
    
    diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst
    index 178ff9bb4..57534ab32 100644
    --- a/en/reference/configuration.rst
    +++ b/en/reference/configuration.rst
    @@ -495,3 +495,17 @@ correctly if sub-namespaces use different metadata driver
     implementations.
     
     
    +Default Repository (***OPTIONAL***)
    +-------------------------
    +
    +Specifies the FQCN of a subclass of the EntityRepository.
    +That will be available for all entities without a custom repository class.
    +
    +.. code-block:: php
    +
    +    setDefaultRepositoryClassName($fqcn);
    +    $config->getDefaultRepositoryClassName();
    +
    +The default value is ``Doctrine\ORM\EntityRepository``.
    +Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
    \ No newline at end of file
    
    From 0b819ca3b024fa00fd4efdcb3bbc1a1aa89e2940 Mon Sep 17 00:00:00 2001
    From: keymaster 
    Date: Wed, 21 Dec 2011 12:24:37 +0200
    Subject: [PATCH 300/430] Clarify the performance warning.
    
    ---
     en/reference/inheritance-mapping.rst | 16 ++++++++--------
     1 file changed, 8 insertions(+), 8 deletions(-)
    
    diff --git a/en/reference/inheritance-mapping.rst b/en/reference/inheritance-mapping.rst
    index c4e76c531..48ff855d8 100644
    --- a/en/reference/inheritance-mapping.rst
    +++ b/en/reference/inheritance-mapping.rst
    @@ -140,10 +140,10 @@ relationships involving types that employ this mapping strategy are
     very performant.
     
     There is a general performance consideration with Single Table
    -Inheritance: If you use a STI entity as a many-to-one or one-to-one
    -entity you should never use one of the classes at the upper levels
    -of the inheritance hierachy as "targetEntity", only those that have
    -no subclasses. Otherwise Doctrine *CANNOT* create proxy instances
    +Inheritance: If the target-entity of a many-to-one or one-to-one 
    +association is an STI entity, it is preferable for performance reasons that it 
    +be a leaf entity in the inheritance heirarchy, (ie. have no subclasses). 
    +Otherwise Doctrine *CANNOT* create proxy instances
     of this entity and will *ALWAYS* load the entity eagerly.
     
     SQL Schema considerations
    @@ -243,10 +243,10 @@ themselves on access of any subtype fields, so accessing fields of
     subtypes after such a query is not safe.
     
     There is a general performance consideration with Class Table
    -Inheritance: If you use a CTI entity as a many-to-one or one-to-one
    -entity you should never use one of the classes at the upper levels
    -of the inheritance hierachy as "targetEntity", only those that have
    -no subclasses. Otherwise Doctrine *CANNOT* create proxy instances
    +Inheritance: If the target-entity of a many-to-one or one-to-one 
    +association is a CTI entity, it is preferable for performance reasons that it 
    +be a leaf entity in the inheritance heirarchy, (ie. have no subclasses). 
    +Otherwise Doctrine *CANNOT* create proxy instances
     of this entity and will *ALWAYS* load the entity eagerly.
     
     SQL Schema considerations
    
    From 894dfd1a6b227f8023d417f6cb193a610c18faf0 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Thu, 22 Dec 2011 23:06:59 +0100
    Subject: [PATCH 301/430] Update en/reference/yaml-mapping.rst
    
    ---
     en/reference/yaml-mapping.rst | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/en/reference/yaml-mapping.rst b/en/reference/yaml-mapping.rst
    index e173b2340..73048d0c4 100644
    --- a/en/reference/yaml-mapping.rst
    +++ b/en/reference/yaml-mapping.rst
    @@ -35,6 +35,8 @@ of the constructor, like this:
     .. code-block:: php
     
         setMetadataDriverImpl($driver);
    
    From 1c31603e177f47c6f8ed6226d9dc7fde7ad03d9d Mon Sep 17 00:00:00 2001
    From: Peter Kokot 
    Date: Sat, 31 Dec 2011 04:33:46 +0100
    Subject: [PATCH 302/430] type varchar replaced with type string
    
    ---
     en/cookbook/mysql-enums.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/cookbook/mysql-enums.rst b/en/cookbook/mysql-enums.rst
    index b8cef7b21..69f0a351b 100644
    --- a/en/cookbook/mysql-enums.rst
    +++ b/en/cookbook/mysql-enums.rst
    @@ -49,7 +49,7 @@ entities:
             const STATUS_VISIBLE = 'visible';
             const STATUS_INVISIBLE = 'invisible';
     
    -        /** @Column(type="varchar") */
    +        /** @Column(type="string") */
             private $status;
     
             public function setStatus($status)
    @@ -70,7 +70,7 @@ the **columnDefinition** attribute.
         /** @Entity */
         class Article
         {
    -        /** @Column(type="varchar", columnDefinition="ENUM('visible', 'invisible')") */
    +        /** @Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
             private $status;
         }
     
    
    From 65c64f52c886c3a24f3524ac55e0bd0fef08ff85 Mon Sep 17 00:00:00 2001
    From: Peter Kokot 
    Date: Sat, 31 Dec 2011 04:37:25 +0100
    Subject: [PATCH 303/430] typo in reference/tools
    
    ---
     en/reference/tools.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/tools.rst b/en/reference/tools.rst
    index ad40a7899..4f7fdb1b3 100644
    --- a/en/reference/tools.rst
    +++ b/en/reference/tools.rst
    @@ -348,7 +348,7 @@ This is an **execute one-time** command. It should not be necessary for
     you to call this method multiple times, escpecially when using the ``--from-database``
     flag.
     
    -Converting an existing databsae schema into mapping files only solves about 70-80%
    +Converting an existing database schema into mapping files only solves about 70-80%
     of the necessary mapping information. Additionally the detection from an existing
     database cannot detect inverse associations, inheritance types,
     entities with foreign keys as primary keys and many of the
    
    From 442227fc8968e1d9dc1910d489dd15eea99d15f6 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Thu, 5 Jan 2012 10:28:46 +0100
    Subject: [PATCH 304/430] Update en/reference/architecture.rst
    
    ---
     en/reference/architecture.rst | 3 ++-
     1 file changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/en/reference/architecture.rst b/en/reference/architecture.rst
    index 9a1d5ee44..c3508bf7b 100644
    --- a/en/reference/architecture.rst
    +++ b/en/reference/architecture.rst
    @@ -15,7 +15,8 @@ be any regular PHP class observing the following restrictions:
     -  An entity class must not be final or contain final methods.
     -  All persistent properties/field of any entity class should
        always be private or protected, otherwise lazy-loading might not
    -   work as expected.
    +   work as expected. In case you serialize entities (for example Session)
    +   properties should be protected (See Serialize section below).
     -  An entity class must not implement ``__clone`` or
        :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
     -  An entity class must not implement ``__wakeup`` or
    
    From 6d1f716f8b978c86dad198e6f427bdb252bcbaf1 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Thu, 5 Jan 2012 21:40:15 +0100
    Subject: [PATCH 305/430] Update en/tutorials/getting-started-xml-edition.rst
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 61da8010c..44679c9d1 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -1009,7 +1009,7 @@ the first read-only use-case:
         $bugs = $query->getResult();
         
         foreach($bugs AS $bug) {
    -        echo $bug->description." - ".$bug->created->format('d.m.Y')."\n";
    +        echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n";
             echo "    Reported by: ".$bug->getReporter()->name."\n";
             echo "    Assigned to: ".$bug->getEngineer()->name."\n";
             foreach($bug->getProducts() AS $product) {
    
    From bc91e5c0fde68558183ae55e1bf523f2b3757237 Mon Sep 17 00:00:00 2001
    From: AmirBehzad Eslami 
    Date: Fri, 6 Jan 2012 01:03:15 +0330
    Subject: [PATCH 306/430] Separated listings of User.php, Bug.php, and
     Product.php. Added missed statement to use ArrayCollection in User.php .
    
    ---
     en/tutorials/getting-started-xml-edition.rst | 10 ++++++++++
     1 file changed, 10 insertions(+)
    
    diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst
    index 44679c9d1..ae2c97d28 100644
    --- a/en/tutorials/getting-started-xml-edition.rst
    +++ b/en/tutorials/getting-started-xml-edition.rst
    @@ -100,6 +100,7 @@ following set of classes. Put them into `entities/Bug.php`,
     .. code-block:: php
     
         name = $name;
             }
         }
    +
    +.. code-block:: php
    +    // User.php
         class User
         {
             protected $id;
    @@ -210,6 +219,7 @@ with the assumptions about related collections:
     
         
    Date: Mon, 9 Jan 2012 08:37:12 +0100
    Subject: [PATCH 307/430] Fix TIP and show new cache instropection API (2.2)
    
    ---
     en/reference/dql-doctrine-query-language.rst | 10 +++++++++-
     1 file changed, 9 insertions(+), 1 deletion(-)
    
    diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst
    index 9886aed29..a9e8e03d6 100644
    --- a/en/reference/dql-doctrine-query-language.rst
    +++ b/en/reference/dql-doctrine-query-language.rst
    @@ -1187,7 +1187,15 @@ Result Cache API:
         $query->useResultCache(true, $seconds = 3600, 'my_query_result');
         $result = $query->getResult(); // cache hit!
     
    -    **TIP!** You can set the Result Cache Driver globally on the
    +    // Introspection
    +    $queryCacheProfile = $query->getQueryCacheProfile();
    +    $cacheDriver = $query->getResultCacheDriver();
    +    $lifetime = $query->getLifetime();
    +    $key = $query->getCacheKey();
    +
    +.. note::
    +
    +    You can set the Result Cache Driver globally on the
         ``Doctrine\ORM\Configuration`` instance so that it is passed to
         every ``Query`` and ``NativeQuery`` instance.
     
    
    From 7b4349a9ce85a5f2132c154fa6555ca2fa2aa9ac Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Mon, 9 Jan 2012 10:36:56 +0100
    Subject: [PATCH 308/430] Rewrite installation and configuration of Tools
     chapter.
    
    ---
     en/reference/tools.rst | 138 +++++++++++++++++++++++++++++------------
     1 file changed, 98 insertions(+), 40 deletions(-)
    
    diff --git a/en/reference/tools.rst b/en/reference/tools.rst
    index 4f7fdb1b3..25d591df7 100644
    --- a/en/reference/tools.rst
    +++ b/en/reference/tools.rst
    @@ -12,15 +12,11 @@ Installation
     ~~~~~~~~~~~~
     
     If you installed Doctrine 2 through PEAR, the ``doctrine`` command
    -line tool should already be available to you.
    +line tool should already be available to you. Y
     
    -If you use Doctrine through SVN or a release package you need to
    -copy the ``doctrine`` and ``doctrine.php`` files from the
    -``tools/sandbox`` or ``bin`` folder, respectively, to a location of
    -your choice, for example a ``tools`` folder of your project. You
    -probably need to edit ``doctrine.php`` to adjust some paths to the
    -new environment, most importantly the first line that includes the
    -``Doctrine\Common\ClassLoader``.
    +In any other case you should create a project specific doctrine command
    +on your own. This is a combination of the PEAR ``doctrine`` commands
    +code and some of your own bootstrap code.
     
     Getting Help
     ~~~~~~~~~~~~
    @@ -52,21 +48,23 @@ In general the required code looks like this:
             Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli);
             $cli->run();
     
    -Configuration
    -~~~~~~~~~~~~~
    +Configuration (PEAR)
    +~~~~~~~~~~~~~~~~~~~~
     
     Whenever the ``doctrine`` command line tool is invoked, it can
     access alls Commands that were registered by developer. There is no
    -auto-detection mechanism at work. The ``bin\doctrine.php`` file
    +auto-detection mechanism at work. The Doctrine binary
     already registers all the commands that currently ship with
     Doctrine DBAL and ORM. If you want to use additional commands you
     have to register them yourself.
     
    -All the commands of the Doctrine Console require either the ``db``
    +All the commands of the Doctrine Console require access to the EntityManager
    +or DBAL Connection. You have to inject them into the console application
    +using so called Helper-Sets. This requires either the ``db``
     or the ``em`` helpers to be defined in order to work correctly.
    -Doctrine Console requires the definition of a HelperSet that is the
    -DI tool to be injected in the Console. In case of a project that is
    -dealing exclusively with DBAL, the ConnectionHelper is required:
    +
    +Whenever you invoke the Doctrine binary the current folder is searched for a
    +``cli-config.php`` file. This file contains the project specific configuration:
     
     .. code-block:: php
     
    @@ -90,49 +88,108 @@ required:
     The HelperSet instance has to be generated in a separate file (i.e.
     ``cli-config.php``) that contains typical Doctrine bootstrap code
     and predefines the needed HelperSet attributes mentioned above. A
    -typical ``cli-config.php`` file looks as follows:
    +sample ``cli-config.php`` file looks as follows:
     
     .. code-block:: php
     
         register();
    -    
    -    $classLoader = new \Doctrine\Common\ClassLoader('Proxies', __DIR__);
    -    $classLoader->register();
    -    
    -    $config = new \Doctrine\ORM\Configuration();
    -    $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
    -    $config->setProxyDir(__DIR__ . '/Proxies');
    -    $config->setProxyNamespace('Proxies');
    -    
    -    $connectionOptions = array(
    -        'driver' => 'pdo_sqlite',
    -        'path' => 'database.sqlite'
    -    );
    -    
    -    $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
    +    // cli-config.php
    +    use Doctrine\ORM\Tools\Setup;
    +    use Doctrine\ORM\EntityManager;
    +
    +    require 'Doctrine/ORM/Tools/Setup.php';
    +
    +    Doctrine\ORM\Tools\Setup::registerAutoloadPEAR();
    +
    +    $paths = array("/path/to/entities-or-mapping-files");
    +    $isDevMode = false;
    +
    +    $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
    +    $em = EntityManager::create($dbParams, $config);
         
         $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
             'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
             'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
         ));
     
    -It is important to define a correct HelperSet that doctrine.php
    +It is important to define a correct HelperSet that Doctrine binary
     script will ultimately use. The Doctrine Binary will automatically
     find the first instance of HelperSet in the global variable
     namespace and use this.
     
    -You can also add your own commands on-top of the Doctrine supported
    -tools. To include a new command on Doctrine Console, you need to
    -do:
    +
    +.. note:: 
    +
    +    You have to adjust this snippet for your specific application or framework
    +    and use their facilities to access the Doctrine EntityManager and
    +    Connection Resources.
    +
    +Configuration (Non-PEAR)
    +~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +If you do not use a PEAR installation of Doctrine you have to define your own
    +Doctrine binary. Put this file into the application root and invoke it from
    +there whenever you want to access the Doctrine console.
     
     .. code-block:: php
     
         addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand());
    +    // doctrine.php - Put in your application root
    +
    +    use Doctrine\ORM\Tools\Setup;
    +    use Doctrine\ORM\EntityManager;
    +    use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
    +    use Doctrine\DBAL\Tools\Console\Helper\EntityManagerHelper;
    +    use Doctrine\ORM\Tools\Console\ConsoleRunner;
    +    use Symfony\Component\Console\Helper\HelperSet;
    +
    +    $lib = "/path/to/doctrine2-orm/lib";
    +    require $lib . '/Doctrine/ORM/Tools/Setup.php';
    +    Setup::registerAutoloadDirectory($lib);
    +
    +    $paths = array("/path/to/entities-or-mapping-files");
    +    $isDevMode = false;
    +
    +    $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
    +    $em = EntityManager::create($dbParams, $config);
    +    
    +    $helperSet = new HelperSet(array(
    +        'db' => new ConnectionHelper($em->getConnection()),
    +        'em' => new EntityManagerHelper($em)
    +    ));
    +
    +    ConsoleRunner::run($helperSet);
    + 
    +Adding own commands
    +~~~~~~~~~~~~~~~~~~~
    +
    +You can also add your own commands on-top of the Doctrine supported
    +tools if you are using a manually built (Non-PEAR) binary.
    +
    +To include a new command on Doctrine Console, you need to do modify the
    +``doctrine.php`` file a little:
    +
    +.. code-block:: php
    +
    +    setCatchExceptions(true);
    +    $cli->setHelperSet($helperSet);
    +
    +    // Register All Doctrine Commands
    +    ConsoleRunner::addCommands($cli);
    +
    +    // Register your own command
    +    $cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand);
    +
    +    // Runs console application
    +    $cli->run();
     
     Additionally, include multiple commands (and overriding previously
     defined ones) is possible through the command:
    @@ -140,6 +197,7 @@ defined ones) is possible through the command:
     .. code-block:: php
     
         addCommands(array(
             new \MyProject\Tools\Console\Commands\MyCustomCommand(),
             new \MyProject\Tools\Console\Commands\SomethingCommand(),
    
    From 649d29414f733e0f3f8c6db6b6de7b16a7783d41 Mon Sep 17 00:00:00 2001
    From: Guilherme Blanco 
    Date: Fri, 13 Jan 2012 01:09:28 -0500
    Subject: [PATCH 309/430] Fixed one-to-many unidirectional with join table
     chapter.
    
    ---
     en/reference/association-mapping.rst | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst
    index aed143815..fdd9259f7 100644
    --- a/en/reference/association-mapping.rst
    +++ b/en/reference/association-mapping.rst
    @@ -606,7 +606,7 @@ the join columns enforces the one-to-many cardinality. The
     following example sets up such a unidirectional one-to-many
     association:
     
    -.. configuration-block
    +.. configuration-block::
     
         .. code-block:: php
     
    
    From d44d82b694fc4fbb9f376bdba490e679f1263718 Mon Sep 17 00:00:00 2001
    From: Mikko Hirvonen 
    Date: Mon, 16 Jan 2012 22:08:49 +0200
    Subject: [PATCH 310/430] Added missing docblock endings.
    
    ---
     en/tutorials/composite-primary-keys.rst | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/en/tutorials/composite-primary-keys.rst b/en/tutorials/composite-primary-keys.rst
    index a091f573b..0dadb245e 100644
    --- a/en/tutorials/composite-primary-keys.rst
    +++ b/en/tutorials/composite-primary-keys.rst
    @@ -304,10 +304,10 @@ of products purchased and maybe even the current price.
             /** @Id @Column(type="integer") @GeneratedValue */
             private $id;
     
    -        /** @Column(type="string")
    +        /** @Column(type="string") */
             private $name;
     
    -        /** @Column(type="decimal")
    +        /** @Column(type="decimal") */
             private $currentPrice;
     
             public function getCurrentPrice()
    
    From 17d91d173bcab1e3d4f644044e6d4ccd8b0f0e51 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 22 Jan 2012 22:23:19 +0100
    Subject: [PATCH 311/430] Change _templates override into a theme "doctrine".
    
    ---
     .gitignore                                                 | 3 ++-
     en/{_templates => _theme/doctrine}/layout.html             | 0
     .../doctrine/static}/configurationblock.css                | 0
     .../doctrine/static}/configurationblock.js                 | 0
     en/{_static => _theme/doctrine/static}/default.css         | 0
     en/{_static => _theme/doctrine/static}/pygments.css        | 0
     en/_theme/doctrine/theme.conf                              | 7 +++++++
     en/conf.py                                                 | 4 ++--
     8 files changed, 11 insertions(+), 3 deletions(-)
     rename en/{_templates => _theme/doctrine}/layout.html (100%)
     rename en/{_static => _theme/doctrine/static}/configurationblock.css (100%)
     rename en/{_static => _theme/doctrine/static}/configurationblock.js (100%)
     rename en/{_static => _theme/doctrine/static}/default.css (100%)
     rename en/{_static => _theme/doctrine/static}/pygments.css (100%)
     create mode 100644 en/_theme/doctrine/theme.conf
    
    diff --git a/.gitignore b/.gitignore
    index 75ec69b23..2091122d0 100644
    --- a/.gitignore
    +++ b/.gitignore
    @@ -1,2 +1,3 @@
     en/_exts/configurationblock.pyc
    -build
    \ No newline at end of file
    +build
    +en/_build
    diff --git a/en/_templates/layout.html b/en/_theme/doctrine/layout.html
    similarity index 100%
    rename from en/_templates/layout.html
    rename to en/_theme/doctrine/layout.html
    diff --git a/en/_static/configurationblock.css b/en/_theme/doctrine/static/configurationblock.css
    similarity index 100%
    rename from en/_static/configurationblock.css
    rename to en/_theme/doctrine/static/configurationblock.css
    diff --git a/en/_static/configurationblock.js b/en/_theme/doctrine/static/configurationblock.js
    similarity index 100%
    rename from en/_static/configurationblock.js
    rename to en/_theme/doctrine/static/configurationblock.js
    diff --git a/en/_static/default.css b/en/_theme/doctrine/static/default.css
    similarity index 100%
    rename from en/_static/default.css
    rename to en/_theme/doctrine/static/default.css
    diff --git a/en/_static/pygments.css b/en/_theme/doctrine/static/pygments.css
    similarity index 100%
    rename from en/_static/pygments.css
    rename to en/_theme/doctrine/static/pygments.css
    diff --git a/en/_theme/doctrine/theme.conf b/en/_theme/doctrine/theme.conf
    new file mode 100644
    index 000000000..c2ee566c2
    --- /dev/null
    +++ b/en/_theme/doctrine/theme.conf
    @@ -0,0 +1,7 @@
    +[theme]
    +inherit = basic
    +stylesheet = default.css
    +pygments_style = sphinx
    +
    +[options]
    +nosidebar = false
    diff --git a/en/conf.py b/en/conf.py
    index 09d8c262f..fbb344b04 100644
    --- a/en/conf.py
    +++ b/en/conf.py
    @@ -91,7 +91,7 @@ pygments_style = 'sphinx'
     
     # The theme to use for HTML and HTML Help pages.  Major themes that come with
     # Sphinx are currently 'default' and 'sphinxdoc'.
    -html_theme = 'default'
    +html_theme = 'doctrine'
     
     # Theme options are theme-specific and customize the look and feel of a theme
     # further.  For a list of options available for each theme, see the
    @@ -99,7 +99,7 @@ html_theme = 'default'
     #html_theme_options = {}
     
     # Add any paths that contain custom themes here, relative to this directory.
    -#html_theme_path = []
    +html_theme_path = ['_theme']
     
     # The name for this set of Sphinx documents.  If None, it defaults to
     # " v documentation".
    
    From cad694e469ed294ca767be3d76a42e5c5eb32a68 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 22 Jan 2012 22:49:14 +0100
    Subject: [PATCH 312/430] Explicit Javascripts
    
    ---
     en/_theme/doctrine/layout.html | 28 ++++++++++++++++------------
     1 file changed, 16 insertions(+), 12 deletions(-)
    
    diff --git a/en/_theme/doctrine/layout.html b/en/_theme/doctrine/layout.html
    index 5a5d2f448..97b589126 100644
    --- a/en/_theme/doctrine/layout.html
    +++ b/en/_theme/doctrine/layout.html
    @@ -116,10 +116,14 @@
             HAS_SOURCE:  {{ has_source|lower }}
           };
         
    -    {%- for scriptfile in script_files %}
    -    
    -    
    -    {%- endfor %}
    +
    +    
    +    
    +    
    +    
    +    
    +    
    +
         {%- if use_opensearch %}
         
                 
               
    @@ -246,4 +250,4 @@
       
     
       
    -
    \ No newline at end of file
    +
    
    From 34a696c3d651cf88b136a9701d5ede32ef6f5c34 Mon Sep 17 00:00:00 2001
    From: Benjamin Eberlei 
    Date: Sun, 22 Jan 2012 23:09:21 +0100
    Subject: [PATCH 313/430] Add all static dependencies instead of loading them
     from doctrine project site.
    
    ---
     en/_theme/doctrine/layout.html             |   2 +-
     en/_theme/doctrine/static/arrows.jpg       | Bin 0 -> 5947 bytes
     en/_theme/doctrine/static/bg-gradient.jpg  | Bin 0 -> 3704 bytes
     en/_theme/doctrine/static/bl-corner.gif    | Bin 0 -> 108 bytes
     en/_theme/doctrine/static/br-corner.gif    | Bin 0 -> 105 bytes
     en/_theme/doctrine/static/bullet_white.gif | Bin 0 -> 180 bytes
     en/_theme/doctrine/static/caution.png      | Bin 0 -> 2132 bytes
     en/_theme/doctrine/static/default.css      |  18 +-
     en/_theme/doctrine/static/disk.gif         | Bin 0 -> 426 bytes
     en/_theme/doctrine/static/layout.css       | 485 +++++++++++++++++++++
     en/_theme/doctrine/static/logo.jpg         | Bin 0 -> 9772 bytes
     en/_theme/doctrine/static/note.png         | Bin 0 -> 1371 bytes
     en/_theme/doctrine/static/servergrove.jpg  | Bin 0 -> 3941 bytes
     en/_theme/doctrine/static/sidebox-foot.jpg | Bin 0 -> 437 bytes
     en/_theme/doctrine/static/sidebox.jpg      | Bin 0 -> 3172 bytes
     en/_theme/doctrine/static/tip.png          | Bin 0 -> 1997 bytes
     en/_theme/doctrine/static/ul-corner.jpg    | Bin 0 -> 589 bytes
     en/_theme/doctrine/static/ur-corner.gif    | Bin 0 -> 116 bytes
     18 files changed, 495 insertions(+), 10 deletions(-)
     create mode 100644 en/_theme/doctrine/static/arrows.jpg
     create mode 100644 en/_theme/doctrine/static/bg-gradient.jpg
     create mode 100644 en/_theme/doctrine/static/bl-corner.gif
     create mode 100644 en/_theme/doctrine/static/br-corner.gif
     create mode 100644 en/_theme/doctrine/static/bullet_white.gif
     create mode 100644 en/_theme/doctrine/static/caution.png
     create mode 100644 en/_theme/doctrine/static/disk.gif
     create mode 100644 en/_theme/doctrine/static/layout.css
     create mode 100644 en/_theme/doctrine/static/logo.jpg
     create mode 100644 en/_theme/doctrine/static/note.png
     create mode 100644 en/_theme/doctrine/static/servergrove.jpg
     create mode 100644 en/_theme/doctrine/static/sidebox-foot.jpg
     create mode 100644 en/_theme/doctrine/static/sidebox.jpg
     create mode 100644 en/_theme/doctrine/static/tip.png
     create mode 100644 en/_theme/doctrine/static/ul-corner.jpg
     create mode 100644 en/_theme/doctrine/static/ur-corner.gif
    
    diff --git a/en/_theme/doctrine/layout.html b/en/_theme/doctrine/layout.html
    index 97b589126..f3ca001cd 100644
    --- a/en/_theme/doctrine/layout.html
    +++ b/en/_theme/doctrine/layout.html
    @@ -104,7 +104,7 @@
         {{ title|striptags }}{{ titlesuffix }}
         
         
    -    
    +    
         
         {%- if not embedded %}
         
    -          {%- endif %}
    -          {%- endblock %}
    -        
    -      
    -      {%- endif %}{% endif %}
    -{%- endmacro %}
    -
    -
    -  
    -    
    -    {{ metatags }}
    -    {%- if not embedded and docstitle %}
    -      {%- set titlesuffix = " — "|safe + docstitle|e %}
    -    {%- else %}
    -      {%- set titlesuffix = "" %}
    -    {%- endif %}
    -    {{ title|striptags }}{{ titlesuffix }}
    -    
    -    
    -    
    -    
    -    {%- if not embedded %}
    -    
    -
    -    
    -    
    -    
    -    
    -    
    -    
    -
    -    {%- if use_opensearch %}
    -    
    -    {%- endif %}
    -    {%- if favicon %}
    -    
    -    {%- endif %}
    -    {%- endif %}
    -{%- block linktags %}
    -    {%- if hasdoc('about') %}
    -    
    -    {%- endif %}
    -    {%- if hasdoc('genindex') %}
    -    
    -    {%- endif %}
    -    {%- if hasdoc('search') %}
    -    
    -    {%- endif %}
    -    {%- if hasdoc('copyright') %}
    -    
    -    {%- endif %}
    -    
    -    {%- if parents %}
    -    
    -    {%- endif %}
    -    {%- if next %}
    -    
    -    {%- endif %}
    -    {%- if prev %}
    -    
    -    {%- endif %}
    -{%- endblock %}
    -{%- block extrahead %} {% endblock %}
    -  
    -  
    -    
    - - - -
    - {%- block header %}{% endblock %} - - {%- block relbar1 %}{{ relbar() }}{% endblock %} - - {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %} - -
    - {%- block document %} -
    - {%- if not embedded %}{% if not theme_nosidebar|tobool %} -
    - {%- endif %}{% endif %} -
    - {% block body %} {% endblock %} -
    - {%- if not embedded %}{% if not theme_nosidebar|tobool %} -
    - {%- endif %}{% endif %} -
    - {%- endblock %} - - {%- block sidebar2 %}{{ sidebar() }}{% endblock %} -
    -
    - - {%- block footer %} - - {%- endblock %} -
    - -
    -
    -
    - - -
    - - - - - - diff --git a/en/_theme/doctrine/static/arrows.jpg b/en/_theme/doctrine/static/arrows.jpg deleted file mode 100644 index 9ea7fae818057a4445e6ff9d9e64fab2900db9b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5947 zcmbVO2Ut_vvR-?qkq}BkK!}tGp$jOzL+BBZ4hliV5a|H|0TmmBqM(APpnw{R(iD#z zr3s2y5mZnF74%RJg5@Y4yC}RJJlEsB@4NTC@6P(Zwf{Y9X3eab*|XRDgZWV)y~2g* z0w5TGaAbh_mw@ENXUA*;5I_R}yb;zoaTU|iajmznmkZOw89)#KL^qEpZagdvKpZ!j z=j-Y~TeUivhPw@5fCpHx9Dp!(Qi7+Si$AglU^+X}k`N&g{)>=3J`5r@5W1)5=1rsh z#sB|4NbCe&G5`?bA215%B(V{kiQw4OLtpN}tM*M`=|Adn{8xcPNaEwpb#EXiEOr~kEwQ0*OEX-+c zoYYuOa}XD$4PpG)WSS_b?D*Jt zK981^5XR=v^!~Mo|7*pc-1^CnrM{dE93F>@mW^N4mJtUi)=^{y`jIyee4wZ^fFPBr?{$x0WPM@& zrwPtSUg4yu2%3oP=<82o^LZ&EjC>QK2?Gc~3Q&LoPzGv13+MquU`~rTD0w{5m9BK(l4`q&WM0uftQ5#S^)HYNust8qqYCyH2uA}auMo}+OpV3&f z44Q`4LtCNU&;e*RIuX4My&GMEu0c1Uub}Ut$IvtAIgA8G5u=N-#4s_dFcFwkOg5$n za{_Y?a}{$RGl}_t#bV{KnpiWe8+J7|8k>gQjXi=rjqSwtV<)ihaX6d;P8Vl`^TLJU zl5yF%!?=^Ui@3YEN!%wfaWNG!LopYzU@@*(hFFnUo!AAjyJAyfvv_H|I^GiRg%8JX z!sp|w@NM`z_(}XMfkeS zkBNUKl8HJ*MJC+;Oy6FZ3yiEkw&Bs3)KC4wcACH6|xN?ewBBJoL5Mp9prDH$%g zU9wcNN%F4bD=EB`x)eidjnrnTBB@`c?nu3q#!G8RJ4%O1Z<8*SZj~OAeovAiEhl-A z;z+wmb);_6G#N+MAUl)8$(iIz@@4Xb3`#~##!-eXBao?*xhgYB!BR9SOiDB*k8+A~ zn=&IymNk|Ql-(#>D%&ADDuhWS){axv52#1*P@0+cNcw>Uo3Ag z&ymlSKP`V>{)+-l!C4_jVV^>?!ec6$szddmrc#emuTozwrYyEv%wC+kxN-3#MOaZs z(N{4|u~M;D@x79=lB-gJ(qW~`N-ve=l61o1v@FJ?UHM_4G&T zcy)92NcCd%>*^mhG&O=Wax|JXrZwd?nVOq5>orHTB(&_b616I{2DGu-=GrmZN3{EN zU>y^kNS#uhJG!v0scw|+5#9c!n57m=xl1dS4(So~7&zE|YJjW~MyT2GeI|bhCA4WoAR>6mxI$-R3teFcuCL+b!BHK3kevCR;XI zzP8e{im|G*nzq)o=2%x*PuQs1gxQqajM~y{*VzhfN9~r_h1pfujoYi)hufd9pJHe; zq8TR{uN;;+@Ep!Ld~~#Q-0IlrgmQ9q%5}Q!Om+@%E^&V1qUsXiQtvY3YT}ya+UbUI zb9dY4HpEn7vYEBanH6R$wywD1PIUKkFL59D(DC4Tw0XjwD?Immj;vH)$z9p(1-zJE z`@J4}YkDVopZCG~c>9$4JoR1fyT$jqpN!vHzdFAU{to{6{=)%U0sMfjK&e1hU~S;X zAg7?hpwU$ZtG2G{SxsFXwYnu37aSOTBKUoXbI8GvNtOvKn>84!8M-mFdku9>?3#|X zQftH3{<;pcE@)ldy02kgVHIKT*E83bt)F2#vWwX-!tKKkgg@iha0)q78*DZdZkUd+ ziP#_UEYdErDDq{LV^m4hn`pP_W6>XDyke?j=3|3mPsfSHt&3~rlDRS5EAh(l8{_*E zbQ7`@MiVU)4<^p=+<7OGprp{GmShTYnd;?h@w512DYhx4DW6gUQ-9q^+Q{A5yGdu$ zu1!;$T{a(2L#MIRy0$FYlD=hZD`RWSw>;T zJ3)}3EmJ9TN9IJ9YgT=>WHv8*D90-2*iQ7$sGYZW8SOf}Yc4lD_h#O*yrR6XyV<*M z?peO)(4P6d8}{DLH_bm+qySxQ&$(+k=f|@op%``_gkG8CC z>2F=p+SO*&cJ93X`Rev1?WG;E9r+gs7cwu*U)+50eP=@F%S#cLCc4&k4PRb$dEkoA zmD^X{uim)ka_#bU#`TLgY;Lr7TXwhJG{4#0W7^ZyYtnn}mdUMiw@q(1-7&k<(r3}v z)^FW^;jZ1?OZS}aT^nEy^xR*0zke`b@WBvkX!Jq&gXxE{58pgWc{Dq`V+1pj`&jyM z(G$fd6~Af!c6!uwv}4R^tasdRe0YLA@#6QS-@i^~K9zV{G^H|CJ8d}K{>&U)#U#omn+A`G)sq{?FZS72a0AGk(|g-uwNN4{;x6KjwZ? z_*C=R{PXoML0_h3Q@`TA9{Q&Jt!>VIZe%`get!NHkYt3%ujhd9PUJ2C%@2c20RJEq zf>3B=Mxili5n-^H1;S!+I58X!i^mfPcs%i+2|}Sz7&Hct#o{Hz@!}Fvk_bsjEeQSf zAbx-QXT2Y9^PPYs23A2aD2N1L5`-c_^H%^Lx!$0VtHe*29K z=;?IwaNJ$TL)so?0%ijry`wmo_EN+qg*NF+C>m-Rg$5Q55lDv^-QSf?hcC7cQSOsU zm~;G+!r+Rw5ijG~wQGFU+kH+)g0#MqB3C+*D`t==Kk8Y&Xln<7Sm8z|IV@Y^b5j@q z*N_>02iJwlM9wk3d|>QZb7R2`SHz%b2<~y^sQJpopM^o-I= zVdXvLZq`iNKgNsbacaLW509B;CG;tN4Xw5>SnXytUO@G9puZr}7q3%AsG_v8wkM?RR=HtBtuxg1 zkVNak%P9R*jt$; z>89N-M^$X2C?mG;wCVbtC^ApXBz9P?wEj+wu*8kb*l=rqGV|KfXC60m8#fNtW-$h9 zv_sjaEHejG+|My62OSU@i5Kf%BqzLU#ro7UqdIqYNiuOK;EyH(Gb&oezEghiOslas z?LA@uNvml~Svv1Qm~9GyR5R^A!U`$OMYvjjw#-d(L*D26@#n$0L+2P@ytm9=VwYVm^(06k-iw9TFmX-r+XoxGS(HejJW>&9Y}Q-GxWCM-{N^)^(EDNK z^1`?34sPa=V-7mfejAq>PxG{_5kto2Cxsuyt{|ns6P1QUUU*vU2_MR8G830`u9+G< zmd-es*l>3GyutBDb!vs8=vCk3xVB*R-p2}T-3$_(8Z4$Xh-O+QRicO?J6aAE6+A5z zyCHlu!OMEuHZFx1K2a7w+(JIhsv zjPaE+fd0_?L=jjQ;<4xOxYa=&r)1w!>5FMQfUK~Su2l344ynR(3qmITEO(FBuya4_ zD}hhdVLap(xF()zdC=CeH+T2>J^W_p`!D(au6lRT&2HwN4D4BpD*2Dr`ALbLRn#(~ z-XiwX)Nb1Og=Km(ck-T#amj|wTm0qhBw8n5plTLsBWP<&=Vx@R-gSd)k0v?ml+otAd@cjIAS(&xhnuh{wT31W}rf z-?((?^7C7`3mRtlSqBMiq^SspS^`eeW1N(-?wt7zEykKvnqeg6r%Xh}{gJ$_*pHs^ z`vE-djVK)FJEsLY6QOZe8)hu)M0H}cX(LN=Hm4h#Bk|kB@SD#E zeinYgf@|4leJW`c9Rc=r7;l|)e}brc$TAM!Zd&Yqi&9%T4<=bIu?O+A`rgz}AFXY} zuuGAGXg^<`G8c^mrQcVBKb%+J&R14o;Gd$X|-+Dg^g>)uAN9@;*&_hO@XyV>^qc%^L|%;8i<#I6LheX)gFOcj#T z{do7b8}4oB^TPi%5m>W3R_#el%CeTdl&Al^Tz^_ONomfeH~Dj>e4)rPt>|XnhtRK{ zJ*RJ>8y{kK7z2X0>Uq5a^+t4)ebEi_LpX$XdP}a!gTS+n0mmCvb#KpUa_&79+`)eu zk!O27#(a*=m?BS0CgI`L8#VUeV=KBsmf&ME)f=UYZa6H!P5jP2);LfQUgLLL+`cRm z;L{zOkz?{{Sf2l_VD%{LRJ9r5+;7HaC>6TJ+Ss1&njgZCrXAVpLO1E(q&`0ge;D88vj{7H(^3P#TMg4x&me?i@SEGNu% zhMh6WTWjADg0%zRQRzz67jbOX2><5hJ!*)opiLwsoV)OnTcp@PG!ruo7ZfrcBHw_HpplW5ckPHiOWEsqTWy=mslY&42U!QT^P7rR zv0g(12NTwZUYNbDpDNf4&hGquLG9{12ulfa#0FloIzS0er%N+Q5&22vM^ih$|4}6~ zSdq)sF~hM$YRgj}-vFF=jgBYi6;|`!-GVAzpJ!;RBLC5SyeiYy_1-}-J#Go+0&fAQ z81-lfCCKvLENX)pL&|qrT`%_cMd#kFktc#1S`FQ}Vpa^4v{x$2Xu?zBs<^ZBJQ>5(swkbwX5>^+V&DEMA!#`wKr` zA2CQeI+3@>D|g{XQ&;n1O|WBj7&dQ>V&B+4#v@?>=7|l8Gf-Qxxu%?tSqV0H`<&m{ z-nXv*OWn@sngE>Q%wVzdPNT9v$CAP2ztJ?>35ru>5L|KmY^eN+5ekp3U*_hz<@7^5z710SE$s>>D5yrNGJnB#R{C zP#<^Z>^V_Pd=G#D5#WF|09;;rYTzvIF!UXOqF;2R3n92OE z|GOQPmnxP300pBCQ~82)9?H2WCuK@f<@ynn8L^3ShUM#Eh|vI1R+DqwH=gp<<{P`p znV&2|*9*&IPUR=_<=lz#+6<`xWh4OQybPfr6XokD8zf1Sg(#1q%t#h+(*eMc<$8&L z7mu=%S4lGXYF8T$Fi|N)Bi*Ma`1=MUxR#azgfHq z`MPJ2YWGjwPv4)qlxhIjqiAk6{M5yk0noY{0JS?mbp|^DP|5|MFN@ z{qG_E$Bq-inh?j7P{BfhSRg{Dia<}9P!xx5w}>y42vbB%q3F+c_#e7Wh(V6u&NT}3 znlFG6Z2^=oGXV0U8(`=P0GYQP9f5xATL3Wv$iKWuouO~%9%Xd=Yx`dfT!j9G(}i(N zx!NN%jLDOVGvthZ6Y>TNNI(fNfI83y`oILRfDNz*PQVp-0Y4A~!oVD`0PsLONCsk% z39>;B$O8pn1K0w}!46OZ>Odnn1X{sSa0;9S7eOz$0q%l+FbJN35%3O-LlA_AC=eY| zhjbuA$Q-hP*w75f3krZjp*c_t6bFeQDU=PZf(oEws2r+->Y#(r5$F`u1zmw|Lj%w- z^cwmIBQP0ez*?{oYzebrH`pHzgXhC>a2lKi=fdmZQn(s!fLq{G@J09r+z&s4M-hN1 zAgYKi!a~@H2NHxtAqx>PvJA;bN|0)#5ot%xA=i+8&IgTv4<9=;6UfIor1iXX&} z5flhI1RH`kA&MX(tR$2W_7RQ~t`dd_<3wemA<>Z-MC20}6N`v7#CGCk;vjLHL?s!M zoJnD%1X2#El+;K%L%K(LqoAOmr{JIvq99aQp-`%DK;gW?fWikdjci8tAkQO9$?M5` z$tTIT$!{nWiV?+?5=D_v)>G;zrzsC8V~TV|3q_71Uvar&g<^~1RmGP|L?uHdH>Cwi zSxO~Jhm|fXJy#|w8!3AzbCs7VS12D*zNtJ)rBkh`LDXbwA$1?Mi~59yry0?_Xna~O zZ5Qn{?GYWJ>(f2xJo+kn4gCy#kbz?uGdPR{Mge0#qlYn~LQ|Qh5}~q4r9$PHO1~m8 zt?KtRFdAkWAsU$)+cnN;JfF;%>@=A-Ie+q@$#*niO*73<%_W+F^jjhVzOWp*-0bTxH-b)~vhx;?t% zdd7O;dMovs^zQ4E^x66g^*8Hx=#Loa7z7$*8`K-zF(ewY4dV^B8lE$JZ)9u~X|%?u z)#!<_nlZ;X%edb7o(aXo#U#z7+T^+^&XjGMXu8d`*9U2B@P zpY=-XcI($RW;Q&V3Y+UwDO0_tE}z;q_0=@ePIZ7N4IsW0qa!PdC=k#Q{@pQrTn(2?6 z^_;oRRnGl0m@{H#?3~f>qU*wSsdjnjs_)8o-QzmsX6lyU*5LNi-O63;-t7L~!_i}@ z$4O7b)5kO4v&W0(74B8;^}t)tJI=e_d&I}qXNk{AU#zdc??&I794!uyQ^y(cv-ex- z*Xd98pXFcf|1iKTKpfB>2nYHFZVJ3N({QF}W@``#;sk99x*u#DoEH3B2reWzq$1>T zsC8&|=($<+S<$oh&3YH+9##}~H{2v#8r~716frNNF5-QpXJm0?|7^?IOJ`r4qcKM~ zr!5K}6%n;3YILsG+%0nl=h@E7o7Xqrc>bdKT?;f8BrP}=trX3TJ{*IMiHzA7^NAb8 zt>%u#a$+lEM|d8*t-NP^SN>-Hu)tYREErnoys&uTlQ`$NO>x8VF7YMt&xIbsa^b54 z--I0r?-PR(_a@4cB9oeu3CS_ZM?^GHqNp=PJ7rPIl~nW8ywv_Q$FwbJBVvE?o^&XE zetMgPffiHUQWNQF=|IMejEao0%!tgxi>Ql4i@Fz^Enc^HD9bx*_Y%w!-ja@N-Rzv~ zfu(LstCm5_VwZI+*I%Byd~k)&irO4fPGZibl~yZ@SH4{pxvD)^D|bciqt!mE>+=-z z#CbQ@IIY>S7PD5kwrAbcb!F?mgJ+6tUIk%g= zyKaxho`OA}_loxR*M`=1*4fw9?bF;>R1eon>xUcWHT3TH+~3y7YOHEfZ7Mhb4oD9? zJs5Ma?~wnYj>GK3jlUZHTG>o*&ToNQ7PpMF#<%vj&1t)G#P3K)yHk7fZx+ApJ*s=O z;+V>@qT{6FxyNNEvQCViOg;JhRNSdY9nl?kPtQJmy)&e<=ZycE^Jl%!o<8Sx?!{_=*F3MCz0SFQ z@y5&>SNg*HZrz-Bv;P+V)|1;ww_n}KxHEot#Xao3{QJuHOCD%GsQ%sb_ojZk{$m54 z1Kkg2J-qvf_vqQb(*N~oF!wR#amkR*P~8)oCr5|9hkKvSeLDCo_1XCIycg6Ll`l21ZEo=>YN{mAr=`1cz6K0E8fz2?G$9 zMyG~Ujcgo%({c_{R~EE1ukIh?(rhO+7~w+;!fZReVpE~Ixz<3o(ZDL!(ya0M{%R?E%|c$C4omc0(GIAm>S;WFYPMx7D;~JWZvPvc8*-!o diff --git a/en/_theme/doctrine/static/bl-corner.gif b/en/_theme/doctrine/static/bl-corner.gif deleted file mode 100644 index f40f7fcc424589a119e6c132820677f0a7cbae23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108 zcmZ?wbhEHb_Yvj70iN&o;eB>(^#;ND_i zmjD0<+DSw~RCwCNmrIOgRTYN+v(Mwc-}lzLy1O2?y8HGE(MipWPDG?}!N@`;-okTF5Z2D8fj&WoilQLY=mm7@oK1hcGKwsCZ3Dqvz2N!m41bT&vrr_CeFUUd?Jz9Tw0hddtr$F&_>MZ;OQ#{mM^4%vF2Vc zqMtn4Gk);1fzfD3jIl)Zys4kQ+z;YTi%0GO#N1S|av~PrL=YV6#D4*3A;5l6TTUjo zs*7{gPzLA^O=OcTJpUU5YZuZ`4GClvy;k@6rI$nFrI(fX=Zy%+D&_A>K6QrG^~d+! zY;vDQUJ|bFVPU#dU5Y2R5QH@?#NhyRAilEbIGZQtXDWe=&>aZGV;wy6go)+zNvK9d z45^_aPB@y2d4Xb1ARgBY6*3^%Z)#)hEUBxP-kT(y294Y#T;In`xlmqooJ|D53Vi}X z001$8{h)R{=Df0cY_1x~2;G5!T)K-Fo)lO+n_#sc5VIuah!91T%P2~x45X4kKBF|E zgFE7gCQXIYH6^b<_VzHF+S4O92`}_8SI$>YII#mKm_7zD4RPPBZEY^i&s0JcVK6q3 zNc3>^*8Ojg)aW`JHcyb94#9@5hQ^nF-anp z#G#~0PO5AQoKAtuxkz&;HIpQ!i0Jlr!v=8XbP)adiB~(N?7x-NG5CRxsba30&ljKn zXdD{=hzJG@Fc~0bFcgD9OcD&ufM#YzfZ{0BoGdHKWleTMLa7oef2^5VGoS%h1lG*Z z3=TCQ3oI=8m9RGOmo+6Ht}}7{_#5?h+xFyyL-eNd+lCZFi^}%j3#;G^3_~? z=cY80l|a#f&77I`)psw>7QXY)d@*14H6V$!ye)3om#^Pf9!x4CpK)Os0-fQc-8761 zcAQ;7bofgsh=7<=|A1)ygHC@3Y@m>pX!VNX;!{(pTQ{}HR07Eyk=J8f{84lC$dC7j z-atd^h>GPPtk-S(=hwb6-Re(S`IHOO5NHqGorZyRa-6#P3GqW`fyoSxIlB$;MzcF? z3u2V=5eB1z_~o_v%&nVj=E}YdCWHsR)^We}^|tFxG#5)!Zr82YrRx_egHcK3(=G&n z?$~R$gxFxm**$DuJaihG%wWf{vBpS^#&*{N8$lncQEor%BGG{ibPc3phNvih0(z=uw>3}d4`oV3A=Vm?HF zSP++fzgFDdw(Me2%2G+I?KiC0qklMG=?;scko8~y=(&EoV_FA2zr&_{$BjrZgB`ox z6vldI=p6WM>iqSOY^}?sP3{ot(Q@e&k#rm_d%U z+qJBXkr%bWK-`f?I10=LMuBYiYL diff --git a/en/_theme/doctrine/static/configurationblock.css b/en/_theme/doctrine/static/configurationblock.css deleted file mode 100644 index c1f987be7..000000000 --- a/en/_theme/doctrine/static/configurationblock.css +++ /dev/null @@ -1,97 +0,0 @@ -div.configuration-block ul.simple -{ - margin: 0; - padding: 0; - margin-left: 30px; -} - -div.configuration-block ul.simple li -{ - margin: 0 !important; - margin-right: 5px !important; - display: inline; - margin-left: 10px; - padding: 10px; -} - -div.configuration-block em -{ - margin-bottom: 10px; -} - -div.configuration-block li -{ - padding: 5px; -} - -div.configuration-block em -{ - font-style: normal; - font-size: 90%; -} - -div.jsactive -{ - position: relative; -} - -div.jsactive ul -{ - list-style: none; -} - -div.jsactive li -{ - float: left; - list-style: none; - margin-left: 0; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; - background-color: #ddd; - margin-right: 5px; -} - -div.jsactive .selected -{ - background-color: #000; -} - -div.jsactive .selected a -{ - color: #fff; - text-decoration: none; -} - -div.jsactive .selected a:hover -{ - color: #fff; - text-decoration: underline; -} - -div.jsactive a -{ - color: #000; - text-decoration: none; -} - -div.jsactive a:hover -{ - color: #000; - text-decoration: underline; -} - -div.jsactive div -{ - position: absolute; - top: 30px; - left: 0; -} - -div.jsactive div div -{ - position: static; -} - -div.jsactive pre -{ - margin: 0; -} diff --git a/en/_theme/doctrine/static/configurationblock.js b/en/_theme/doctrine/static/configurationblock.js deleted file mode 100644 index 0f708a1a8..000000000 --- a/en/_theme/doctrine/static/configurationblock.js +++ /dev/null @@ -1,34 +0,0 @@ -$(document).ready(function(){ - $('div.configuration-block [class^=highlight-]').hide(); - $('div.configuration-block [class^=highlight-]').width($('div.configuration-block').width()); - - $('div.configuration-block').addClass('jsactive'); - $('div.configuration-block').addClass('clearfix'); - - $('div.configuration-block').each(function (){ - var el = $('[class^=highlight-]:first', $(this)); - el.show(); - el.parents('ul').height(el.height() + 40); - }); - - // Global - $('div.configuration-block li').each(function(){ - var str = $(':first', $(this)).html(); - $(':first ', $(this)).html(''); - $(':first ', $(this)).append('' + str + '') - $(':first', $(this)).bind('click', function(){ - $('[class^=highlight-]', $(this).parents('ul')).hide(); - $('li', $(this).parents('ul')).removeClass('selected'); - $(this).parent().addClass('selected'); - - var block = $('[class^=highlight-]', $(this).parent('li')); - block.show(); - block.parents('ul').height(block.height() + 40); - return false; - }); - }); - - $('div.configuration-block').each(function (){ - $('li:first', $(this)).addClass('selected'); - }); -}); diff --git a/en/_theme/doctrine/static/default.css b/en/_theme/doctrine/static/default.css deleted file mode 100644 index a6d6bd3fd..000000000 --- a/en/_theme/doctrine/static/default.css +++ /dev/null @@ -1,974 +0,0 @@ -/* - * default.css_t - * ~~~~~~~~~~~~~ - * - * Sphinx stylesheet -- default theme. - * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -div.sphinxsidebar { - width: 300px; -} - -body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td,button { margin:0; padding:0; } -ul,li { list-style-type:none; } -select { min-width: 1.5em;} -select > option { padding: 0 2px 0 3px; } -form { margin: 0; padding: 0; } -img { border: 0; } -hr { clear:both; display: none; } -label { display: none; } -fieldset { border: 0; } -q:before,q:after { content:''; } -abbr,acronym { border:0; } - -.reset div, -.reset dl, -.reset dt, -.reset dd, -.reset ul, -.reset ol, -.reset li, -.reset h1, -.reset h2, -.reset h3, -.reset h4, -.reset h5, -.reset h6, -.reset pre, -.reset code, -.reset form, -.reset fieldset, -.reset legend, -.reset input, -.reset textarea, -.reset p, -.reset blockquote, -.reset th, -.reset td{margin:0 !important;padding:0 !important;} -.reset table{border-collapse:collapse !important;border-spacing:0 !important;} -.reset fieldset,.reset img{border:0 !important;} -.reset address, -.reset caption, -.reset cite, -.reset code, -.reset dfn, -.reset em, -.reset strong, -.reset th, -.reset var{font-style:normal !important;font-weight:normal !important;} -.reset li{list-style:none !important;} -.reset caption,.reset th{text-align:left !important;} -.reset h1,.reset h2,.reset h3,.reset h4,.reset h5,.reset h6{font-size:100% !important;font-weight:normal !important;} -.reset q:before,.reset q:after{content:'' !important;} -.reset abbr,.reset acronym {border:0 !important;font-variant:normal !important;} -/* to preserve line-height and selector appearance */ -.reset sup {vertical-align:text-top !important;} -.reset sub {vertical-align:text-bottom !important;} -.reset input,.reset textarea,.reset select{font-family:inherit !important;font-size:inherit !important;font-weight:inherit !important;} -/*to enable resizing for IE*/ -.reset input,.reset textarea,.reset select{*font-size:100% !important;} -/*because legend doesn't inherit in IE */ -.reset legend{color:#000 !important;} - -/* -- page layout ----------------------------------------------------------- */ - -div.document { - margin-right: 30px; - font-size: 12pt; - text-align: left !important; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 300px; -} - -div.body { - background-color: #ffffff; - color: #000000; -} - -div.body ul { - margin-left: 30px -} - -div.body ul, div.body ul li { - list-style-type: square; -} - -div.footer { - color: #ffffff; - width: 100%; - padding: 9px 0 9px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #ffffff; - text-decoration: underline; -} - -div.related { - background-color: #ffc; - width: 100%; - font-size: 90%; - margin-top: -40px; - height: 35px; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - float: left; - font-weight: bold; -} - -div.related li.right { - float: right; - margin-right: 5px !important; - margin-left: 5px !important; -} -div.sphinxsidebar { - background: #f2f2f2; -} - -div.sphinxsidebar h3 { - font-family: 'Trebuchet MS', sans-serif; - color: #000000; - font-size: 1.4em; - font-weight: normal; - margin: 0; - padding: 0; - margin-left: 0 !important; -} - -div.sphinxsidebar h3 a { - color: #000000; -} - -div.sphinxsidebar h4 { - font-family: 'Trebuchet MS', sans-serif; - color: #000000; - font-size: 1.3em; - font-weight: normal; - margin: 5px 0 0 0; - padding: 0; - margin-left: 0 !important; -} - -div.sphinxsidebar p { - color: #000000; -} - -div.sphinxsidebar p.topless { - margin: 5px 10px 10px 10px; -} - -div.sphinxsidebar ul { - margin: 0; - padding: 0; - color: #000000; -} - -div.sphinxsidebar ul li { - margin-left: 5px !important; -} - -div.sphinxsidebar a { - color: #000000; - font-weight: bold; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - - - -/* -- hyperlink styles ------------------------------------------------------ */ - -a { - color: #355f7c; - text-decoration: none; -} - -a:visited { - color: #355f7c; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - - - -/* -- body styles ----------------------------------------------------------- */ - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Trebuchet MS', sans-serif; - background-color: #f2f2f2; - font-weight: normal; - color: #20435c; - border-bottom: 1px solid #ccc; - margin: 20px -20px 10px -20px; - padding: 3px 0 3px 10px; -} - -div.body h1 { margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 160%; } -div.body h3 { font-size: 140%; } -div.body h4 { font-size: 120%; } -div.body h5 { font-size: 110%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - /*text-align: justify;*/ -} -#content div.body p { - font-size: 100%; - line-height: 120%; -} - -div.highlight { - margin-left: 30px; -} - -div.highlight pre { - margin-left: 0; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.admonition p { - margin-bottom: 5px; -} - -div.admonition pre { - margin-bottom: 5px; -} - -div.admonition ul, div.admonition ol { - margin-bottom: 5px; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 5px; - background-color: #eeffcc; - color: #333333; - line-height: 120%; - border-left: none; - border-right: none; - margin-left: 30px; -} - -tt { - background-color: #ecf0f3; - padding: 0 1px 0 1px; - /*font-size: 1.4em;*/ - font-weight: bold; -} - -th { - background-color: #ede; -} - -.warning tt { - background: #efc2c2; -} - -.note tt { - background: #d6d6d6; -} - -.viewcode-back { - font-family: sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} - -img -{ - vertical-align: middle; -} - -.js_active -{ - cursor: pointer; - display: inline-block; - background: url(/images/btn-open.png) no-repeat 550px center; -} - -ul.error_list -{ - margin: 0; - list-style: none; - color: #f22; -} - -ul.error_list li -{ - list-style: none; -} - -pre -{ - padding: 0.7em; - background-color: #000; - line-height: 1.3em !important; - font-size: 100%; - color: #fff; -} - -pre code -{ - background-color: #000; -} - -pre.command-line -{ - background-color: #333; - color: #eee; - padding-bottom: 10px; -} - -pre.command-line code -{ - background-color: #333; -} - -blockquote -{ - padding: 2px 20px 5px 45px; - margin: 15px 0; - background-color: #fff; -} - -div.admonition -{ - padding: 2px 20px 5px 45px; - background-color: #fff; - margin-left: 30px; -} - -div.note -{ - background: #fff url(note.png) no-repeat -5px -5px; -} - -div.caution -{ - background: #fff url(caution.png) no-repeat -5px -5px; -} - -div.warning -{ - background: #fff url(caution.png) no-repeat -5px -5px; -} - -div.tip -{ - background: #fff url(tip.png) no-repeat -5px -5px; -} - -div.seealso -{ - background: #fff url(tip.png) no-repeat -5px -5px; -} - -/* Note/Caution/Tip Boxes */ - -/* Note */ - -div.note { - padding: 5px 20px 5px 45px; - margin: 10px 0; - background: #ffd url(note.png) no-repeat 5px 10px; -} - -/* Caution */ - -div.caution, div.warning { - padding: 5px 20px 5px 45px; - margin: 10px 0; - background: #ffd url(caution.png) no-repeat 5px 10px; -} - -/* Tip */ - -div.tip { - padding: 5px 20px 5px 45px; - margin: 10px 0; - background: #ffd url(tip.png) no-repeat 5px 10px; -} - -/* Sidebar */ - -div.seealso { - margin: 10px 0; - padding: 10px; - background: #ccc; -} - -div.seealso ul { - margin: 10px; -} - -div.seealso p.title { - margin: -20px; - margin-bottom: 10px !important; - padding: 10px !important; - background: #aaa; - color: #fff; - font-weight: bold; -} - -/* Shared Styles */ -div.note, div.caution, div.tip, div.seealso, div.warning { - overflow/**/: auto; - margin-left: 30px; - font-weight: normal; - min-height: 40px; - border: 1px solid #ddd; -} - -div.note p, div.caution p, div.tip p, div.seealso p, div.warning p { - margin: 0 !important; - padding: 0 !important; - padding-top: 10px; -} - -.admonition-title -{ - display: none !important; -} - -blockquote.quote -{ - background: #D7CABA; -} - -blockquote.sidebar -{ - padding: 10px; - background: #eee; -} - -blockquote.sidebar p.title -{ - margin: -10px; - margin-bottom: 10px; - padding: 10px; - background: #ddd; - font-style: italic; -} - -.navigation -{ - font-family: Arial, sans-serif; - padding: 10px 15px; - font-size: 0.9em; - text-align:center; - background-color: #e3e3e3; - border: 1px solid #e3e3e3; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; -} - -.navigation a -{ - text-decoration: none; -} - -.navigation a:hover -{ - text-decoration: underline; -} - -.navigation .separator -{ - padding: 0 10px; - color: #ccc; -} - -.feedback p -{ - font-family: Arial, sans-serif; - color: #858585; - font-size: 0.8em; -} - -.feedback h3 -{ - border-top: 1px solid #ddd; - font-family: Georgia, "Times New Roman", serif; - margin-top: 10px; - padding-top: 20px; - color: #858585; -} - -.toc -{ - margin-top: 10px; - padding: 10px; - background-color: #f1f1f1; - border: 1px solid #e3e3e3; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; - font-family: Arial, sans-serif; -} - -.toc h2 -{ - margin: 0; - padding: 0; -} - -.pages -{ - padding: 10px 0 0 0; -} - -.pages ul.inline -{ - display: inline; - padding: 5px 0 0 0; -} - -.pages .inline li -{ - display: inline; - margin: 0 5px; -} - -#quick-tour ul.simple -{ - padding: 5px 0 0 0; -} - -#quick-tour ul.simple li -{ - margin: 0; - margin-right: 5px; - display: inline; -} - -.toc a -{ - text-decoration: none; - color: #777; -} - -.toc a:hover -{ - text-decoration: underline; -} - -.bd .content .toc li -{ - padding: 2px; - list-style: square; - margin-left: 15px; -} - -.bd .content .toc li.current -{ - font-weight: bold; - background-color: #e3e3e3; -} - -.bd .content .toc ul.inline -{ - padding: 0; - margin: 0; - margin-left: 3px; -} - -.bd .content .toc .inline li -{ - margin: 0; - padding: 0; -} - -.bd .content .toc li.separator -{ - color: #ccc; -} - -#release_info -{ - background-color: #e3e3e3; - border: 1px solid #e3e3e3; - margin-bottom: 15px; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; - width: 290px; -} - -#license -{ - margin-top: 40px; - line-height: 1.2em; - font-size: 80%; -} - -#license img -{ - margin-right: 5px; -} - -table.docutils -{ - margin-bottom: 10px; -} - -table.docutils th -{ - font-weight:bold; - background-color: #efefef; -} - -table.docutils td, table.docutils th -{ - padding: 4px 6px; - border: 0; - border-bottom: 1px solid #ddd; - text-align: left; - vertical-align: top; -} - -.menu -{ - float: right; - width: 300px; - margin: 15px; - font-size: 0.7em; - background-color: #fff; - position: relative; - z-index: 999; -} - -#searchform -{ - display: inline; -} - -#search -{ - -webkit-appearance: searchfield; -} - -span.pre -{ - font-size: 85%; -} - -.bd .content .navigation li -{ - margin-left: 0; -} - -a.headerlink -{ - padding: 2px; - color: #ddd; - text-decoration: none; - font-size: 80%; -} - -a.reference em, a.internal em -{ - font-style: normal; -} - -#guides ul ul, #contributing ul ul -{ - display: inline; - padding: 5px 0 0 0; -} - -#guides ul ul li, #contributing ul ul li -{ - display: inline; - margin: 0; -} - -.sidebarbox -{ - margin-top: 10px; - padding: 10px; - background-color: #f1f1f1; - border: 1px solid #e3e3e3; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; - font-family: Arial, sans-serif; -} - -.sidebarbox h2 -{ - margin: 0; - padding: 0; -} - -.sidebarbox h3 -{ - margin: 0; - padding: 0; - margin-top: 5px; -} - -#searchbox h3 a.nolink -{ - padding: 0; - text-decoration: none; -} - -#searchbox h3 a.nolink:hover -{ - text-decoration: underline; -} - -div.breadcrumb h3 -{ - display: none; -} - -.bd .content div.breadcrumb ul -{ - margin: 0; - padding: 0; - list-style: none; - margin-top: 5px; -} - -.bd .content div.breadcrumb li -{ - display: inline; - margin: 0; - padding: 0; - line-height: 0.9em; -} - -.bd .content div.breadcrumb li a -{ - color: #777; - text-decoration: none; -} - -.bd .content div.breadcrumb li a:hover -{ - text-decoration: underline; -} - -.p-Indicator -{ - color: #FF8400; -} - -.bd .content ul.search li -{ - margin-left: 0; - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -div.genindex-jumpbox -{ - font-size: 85%; - border: 0; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox -{ - color: #999; -} - -div.genindex-jumpbox strong -{ - font-weight: normal; -} - -div.genindex-jumpbox a -{ - padding: 0 4px; -} - -h2#A, h2#B, h2#C, h2#D, h2#E, h2#F, h2#G, h2#H, h2#I, h2#J, h2#K, h2#L, h2#M, h2#N, h2#O, -h2#P, h2#Q, h2#R, h2#S, h2#T, h2#U, h2#V, h2#W, h2#X, h2#Y, h2#Z -{ - background-color: #eee; - border-bottom: 1px solid #aaa; - font-size: 120%; - font-weight: bold; - margin: 20px 0; - padding: 5px; -} - -.indextable a, div.genindex-jumpbox a -{ - text-decoration: none; -} - -.indextable a:hover, div.genindex-jumpbox a:hover -{ - text-decoration: underline; -} - -div.configuration-block em -{ - margin-bottom: 10px; -} - -.bd .content div.configuration-block li -{ - padding: 5px; -} - -.bd .content div.configuration-block em -{ - font-style: normal; - font-size: 90%; -} - -div.jsactive -{ - position: relative; -} - -.bd .content div.jsactive ul -{ - list-style: none; -} - -.bd .content div.jsactive li -{ - float: left; - list-style: none; - margin-left: 0; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; - background-color: #ddd; - margin-right: 5px; -} - -.bd .content div.jsactive .selected -{ - background-color: #000; -} - -.bd .content div.jsactive .selected a -{ - color: #fff; - text-decoration: none; -} - -.bd .content div.jsactive .selected a:hover -{ - color: #fff; - text-decoration: underline; -} - -.bd .content div.jsactive a -{ - color: #000; - text-decoration: none; -} - -.bd .content div.jsactive a:hover -{ - color: #000; - text-decoration: underline; -} - -div.jsactive div -{ - position: absolute; - top: 30px; - left: 0; -} - -div.jsactive div div -{ - position: static; -} - -div.jsactive pre -{ - margin: 0; -} - -.highlight -{ - overflow: auto; - margin-bottom: 10px; -} diff --git a/en/_theme/doctrine/static/disk.gif b/en/_theme/doctrine/static/disk.gif deleted file mode 100644 index 1f31274f72c98adc66ebbdd0d4268937ca62a17c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 426 zcmV;b0agA-Nk%w1VGsZi0M!5hw#@G7@cP;1_jR7#YLU!kjL72b`K-k0@%H>VbjO&p z+%#sgdZXg_`~Eg+ySvcsIc&R(snjxIse78pa+=wCqTt@?_{-h(_WJ(D+4DVcy?dnN z_xk-dY`!*b!+4_LHE6c%^7}SxyzlhGGnYWWUq+8V#3_N_WAvI zq2MuKr+%pDeyHc$=l5-v)5F#8q`u^zy5WPN&A!s`?)3X`nbvZg+jN}TfU4>9`2E7w z@>__>A^8LV00000EC2ui01yBW000KZK#!->5g8p0hz6oeBs?ZlqrpN1LM%?m^7th> zCmse&Bn6(uDDIs*!&J`A?06)mp=I}{2j4mY>9x&s3g6gd&V z4-8Y!&?v+bjhWE*%F4(a{qV5K1Z>XoLp{)XdKmdTJuI=jO4JHBrPp<$!Lp4RlyXF>*m_q;< zzy>e?K>)Ca`}^n^sT!m20cfZwG5VuB-Qa%-`W`2f=sD0b#;dN+$oMb)|8GSM_wfq= z0FXXy1~wWVV*yI{=)J%*wNDq zO#%4EW*D9{`|uH~Ihs z+y#vV&=}9p*iZ?Lr2zm7&-owN;U72v5rVc80F=CaLj7EwT>=<6;GB$t5)$H!>WE+u zL_h$qzCGOC-p`Ry$=lP%-YXRCtv_qN83mBsaLb4$SxituOpI5E4^98y^1n9z-Rpk` zH@^L=$DzR=pMfX_{;m7>*njK13jjd;7|qSIf9o7F0ifwI08q{RTgQ?I0EE#1&^Y{$ z^-$dG7ngtlA4z`x;NW0BR|K5zhS0y||JmX1p8qrW$9#M@^Zm^oqcXzDKF}k8@rG2m zw}*G2AEUpIJsiQv``Z8vvlBxB$c|2i*et+i%*~rohdUXU4Gc z*S<$%bo-z3zZBqP^d;Ee)tT`|tz>A-2oLlNy20pY;--KBH~=9)22ca^05iY=@B%`> z9Y6|@2UGw}Ko2km%mEt!4!8iGfFBSH+yfo~F+e=<3`hgAfLx#mCJ00+Pca0vo|FhTerQV=zW0mKU81_^=0L9!qfkT%E=WDc?eIfJ}F zfuMV!C{R2o1(XHK2bF`~f|@}cpkB}jXd1K(+5-IqU4S8AJTMuU4$KDT2aAIhz*=Bq zur=5j>_X0=7*J9uBa{~^1yzTdK;ckdXaqC?nguO`HbT3gW6)*j z0rU!n52J-~!z5uEu)8p4STHOGmJTa{HNd)I6R>sI2?hoR1qM6D9Sn60a|}0(aEt_u zR~R)I?HD5%s~E?an3&X<+?X<$dYJZ@0hlqEnV7FJKVptzu4A5I;bJjh31O*XSzvi# zMPj96RbYL<8pYbcy2K{JX2q7o*28wh4#iH!F2ZiX9>!kBzQiHMVaJidF~)Jjd4%%< zry8dlXAb8O7Z;ZaR|3}n*A@36?hD*ExSw&CanJEc@VM}l@T~9x@sjXL@!IjG@ec9v z@!9a@@h$KJ@RRV%@jLMs@XrWH3HS&!2pkC>5WFO4Bp4;wC&VUXB~&1^Aq*o-C#)kJ zCfp^$CSoH}BC;onAbLsEOf*GwOiW5FNUTTfNt{4jLEKBcNrFkjMxsLEND@s_NYY8N zN(v!mCRHMZlSYvik#>`=lVOmtlWCB-k;Ri$kqwa@kdu&$keiZ+kiR5vBVVF`P_R*G zPkb*s|#xeD~k1$jh)SmEtai`ZIhjf zU4uQ0y^?*71D`{V!I4w-~o8_bcuZ9vF`V zj~7n?&lE2nuL5rnZzb-#$MJzXktueiZ+efT)0nK#{<#Ac>%+V5DG+ z;7=hgA-K>>p>bh+VO8M=!taECh;WM_L~=yFijs=zh{lS368$Z9N6b>5RhM%N@Hr z*>|SI$;9==6U0ADz$6qU9!RuGoJ)#H21wRQewX5va+j)*+LUIOMo1S)ugWmV*vsV0 zEXgv;+R5h0F3H`NvzIH7Tajm$ca$%Y-%#LGa8r1#u&*ef=%?7Ac&a3+bYH1m8KSJF z9H%^_LZV`#lBKes%B1S7TB&-VCaM;$)~*gy*HVA3KBYmY0oN$kIMBSKd0(?j3s1{P zD@$udn@ihIyF~}Aqp6dsGpEa{>!sVI2k2?&rRvS=v+Minzc;`z&^LH#@Xb)r@SY*c zh}6i|sKV&PSkXAyc-Dl&B+%rODWR#AX}RgCnX*}m+0tG9yZ7(*o70%Pn!mHav@o?O zu{gF=u}rgEw-U9Av6{4Iw+^xHwV|=`uxYa;w6(XbvxC{0*;Uy6ve&aOus?RtaL9Hz zfGfc>;5&};j!4HXgbX4DvEd}+l;X7MEaRN&yyYV2lJ2tWs^psG`om4#E!XYTUC+JL z{o2FKqs9}<)6TQmi^R*_tIM0-JH&g$hubH{XUSL6H_i9JPs^{wALMWC|1N+G9ZU@b zas)mOTn>^AdKq*UY#Lk_LLA~1G7!oY`ZRPSOf{@H92yP}|8(#6y+`+!?krZOB76ek$9ctlr)qqmYnkp(y<%TkC_ z!ctaKby6FVjL10TQJQU9Z@Nf&ZU#<9V8+r5?HBJdSu&qx{>pO6ns}-BvL>50`)T&+ zE5}!(Ir2HxxpcYlxfgk^d0+F@@*4}-3epQP3WEy26`2;Hip7h|OK3{sOMaJnl`fST zmUWfiDKD>}tw^o}R|ZyYy|#M&rAoQ#T{UlYeho!U{2Sm+;G6BYc5f$ZwQ4`riPu%v zv({%fkT%3Mf*L~`51L$>mfo4a8)?>T{?sDX((s<|eQE3M)|YMMZOI>SK16@G{uuW0 zxZS6Hx5K$(^^@(V+0MJ2<6Q<_L)}{4pLulE501xIfO1mM_? zjs-y1vp_Te0pAF^guoyW2n+^=K%g)T*o|OdVW1B%Obje6Y#baMY%IJ#LU1EQe+vi# zg`y8ZJS;3c5&}E|60(~XGU7k&#D7&p|Em5T^IyPqJ3x#D+yxawK*Ru;7z7~(U3USM z=r{%pLB|p|QOh3zLjVxkBus25S_=ZujsMw#UJBZJC3Tl;hU$Czsud((Jdo2%>ERTRw(=zUnt~3(IcMVxA z{?jnh%Gl865g$jfbh&~q7mTEs(~AQ@7GrY(tS~19Va8H2NG5b3@&;x;vkXDzeS3q% zBg&R0SLV45i!6g{8d8l`VTx3$yW{Bl%R zQBdJU!>k)0IkBN(*H%ep$8+U&tBqz90MN6fp>jf&6V{RAY97r3Q|Y0q;@tyBPhu9! zf=Vs6EU5PH>}VtFM~OznT-IFp4hXiIr!}q`am$y_hjG)gmrETH$l$=_v@Y9@dH>e< z+|H?6ceb;AbFNYk%d^fJXyVv~dxLc!pXGcSt>7$Lsi&D@z5SYNly-`XW{O+g-O40y zve`n(Vg(r^x^98*eqPMj8JE{NT%phSvkFY&uc?5UYn*RCH~Kg>uXWu(7)d^^pt3Om zbLKKEYrQB2@pH;+4Obw6HV)n)v?sIg8|O8Nm!p|NlC2mQv#z0i=!<4+om0b?%MgjC zQM}+$w><1~-asD78abMshZ=tzsr?x$VQ|T5`4mfr(!HiWOUumS82_HCTbKrRW}d^+ z)SEZWdFM@Hw*x--b`Ga?PM2br_etLSez+U_L2alrc4+FOc&l&DN2-U;>rbQ9Y zWZI-2*Dh1CtFd^~WBz&zi|~1|*uA(I6Qb9{dXdGRJg|D|TqAy_9kIyHzrX)wVl%AC z?2G8wG#6bo=~)XV1J;_{_WR-a`%_~Jd7ZhjQL--`BvBV_KQ2)Ihabz^MCb85G^<+vDL3S5qB*uPHndvj;Othp(W4blAlY%2*?Hi#Vr;)B zuRoV(!YCPOKeIWK3gITNa24`<$4f=Vo$BOFyCr%Owb|rcXbID$J<4V+_rIrvec*Dy zcMS*~@#|k?(Bu8CGNAVO_4e2OtBsNS!du#{pJY$AvIEtI`8%iSuYuG$vDBtPm3wdW zDCTCEW2TgUPJJ-H@J#XhqGhp#%@;%kUmv{po`C&DXrsB;MP>1z+9w9f*>Ch_YwuPI zH$Qx!dfNYcw0*Ju8lc$|E$4XSueDXV>2|A&xV@qM8c?{p9dLKODYG?9 zk0h#Tk>Pc)oaDvJG4u1FVS2higbe#fhV|Uh@C6xj3^nroH%DsA?BFKt#3c3!3pM2+ z+>-kJn*C0tt$AzL;x>zl?#1n@3@(?7a>SMeH>*=fiTjJ)8W*1~A4Uf9j?bBi{<=E8 zSIz|y`XajVUv3?j?p{!Ic#k1TT`M#*%c?|t^xMzpeQWHWkBq%d1vEPo)7gzZZZSOMskd6A!fGpl&ApXseLl7j7d?@!92mhgQMh= z*jY}wF8;jD-*U6Ir?zdY*8utPD{0A)Uw4mvwyuHT*q_yPdwZ8t^B2;?6!e%Rn7KS= z&U*OvL(D|jSls3NRk-fi5&C+y*yGHPx;f0u<~ejrTlA|-TMXQ`{W(=C%lC_M_`ezF z@ukjl$~&HF>Qi2pD|kICz&?<`FN zqBdOcX)e;Te`WL4?3*dNW^MRk)_sBz3#Khwa=!GD^ybP}YPgnkqQb%y-BkDky8@88 z^O^x0dbK+dMhUv1>%zi%lXbb2{by&ZIR-IQU2w{=lMKC}9kERp>Q{xwvZ+-`C~M)8 zxK^bfhI))Z#>0@FT@3bfJ=P-zUBuwZ@bqjce-mwgL59UGRtE6xO2n3qYW9x<(@CtV8+J!ZY-!I3o|s= zlzhDg-p|VwwGe7wWbk1;aQC(rmOIjGlv()t+e)SCD8F@@Z^rOgdy_4kCGsw-m-+2i zxtxq=6z{+v%sSRQmVdq-evODePF^HExL=vuGcmNhUcJiWk19f~3;yt7?JguS+orWm zaiWq)BCz_>xy`Uz*J%BE>?PaPwKZP`2;?68jgy)AJ zE~z_Ny>Qr_cV>1RvY`sK_z~CaEyb{~|0x(W#~QC}9<;5A+vW96O2e&rrDz8=nqPKr zI@(T_q8E5(M}MF}l`NQW=ePS1(Q1jXOnW*;VT)1HC3O%arR_<)m>Xi1@Aajw}NzV7+_X5rJy8<0X ztRk@2_jM2f&jy*ZHy9o_tFuVqJ%n}C;GJp@^oJsnh`*xtbNW1j!+tlt4?pSa#*a1r zO|FAzDaoPQ+6c6!@D;^}+k;YKB_3CWQpbM!mR(doCx)n$!#iQS+Y>T7^pS49bnn%$ zXYu~6VBVwC^kG)BX^v_IKra{j)S4*eia9-rS)n?fD;-=?3cIhiKoT}PIA(+y%((>j>^h2}TbsrfVB7{Ww zzRU+EeJB{U@N@H2kxmO{4$D?#jvHZxTk5Fj-4gEseEfT))oIn;ef)k5G4X}bIMdhn z23yX5>)f2TuMx|+1{yt^w@nRu75CJR(oa)mtZ}W$n%BI|mPqa-i61PLhS+z&Q;|K> z?#615O5J2P-ZRf8Hcf=X3U=SlUG;iY6*on$udRMxoK~sL zo)P}&&TG6hJVA-}8*g0(eu4)!qj)_L@A+G)P5cTSR(E-jB4 zaB{mn{BmMOHT`|?vdeVaZHDQ9{mRH&>j+V10R+Aex0o+J zlB4dz!~TKW-pSDHpKfy2nqAsCdV5{wba)+g@BTP-(iyUXn?~5 zFgNk|C1-0(G4RdPgoZWhIZ3R_@f6q^o!koXyfpHEH|(2t|33ZVqo^XCCx4xb3I;+<<7sWi4Ry0p4vq9tv~I-#>JuL;`^^HtDe)-E7X<%!8*{9>bq*Im`! zzihRjeAt|tBR;+%NX&q|XIs?z@o94U0ke&{Z6uphM5tcJq3SB*ilA+&PlSFtMsVbJ z@?iq@scQf=**IdkGkt8@WyO7qZfHU!aBS)VLt3nG?^I21tSs@}DiS5(sH%tyP;D{-QvI%BqVQ>dol{L*>aH%gKT7 zi3?PCi$d%3y$J^u&iF=2roVnKdsUSp9x%PsdUyIurFyose7RYD@|&QMnaO-vF;c`= zf?6;L^0dQRT-UJvdg+yH7}dN1>WOSLqo|IjbRD(69MFeJDt*` zJyZ4R8B#MCTCro>MEk9*sS&`yJszm-J1oMiUFmr8O5J1-|{ZAvOHr zj3w~)@D~Gv1VIekTA%50Ny8hD+)JneT^AGgM!w9xntiP1-K5#05Lzd+qh{nW#GF$w z9guE5aB!73Wh|4_8SexA)k&{wLz)!9w*}WV5FhLCdhpcxz^OPrX{>@rcpC5HoYgi< zWlThjEPtt|^g}tqbwXa$c}529F86DlccsZui*OPsmG7M&V|M2WGgo?|R38Bv?)NP_CD!(NqY@7DAtq>R$Txs>Bc1z0a%y4x6#b>iX zN4#*Au{UPEqq`JLJ7W0~#>!$LucKii6JE)EyEH|@79#2C4u3v;Mt@5CK`HJqaJ?i* z**-psV@M#g&kA=!w(?yF9ckqt?qllEin#4!u9oa^GRJRm^aV(ZgzV9H?9?n|BZOTP z7nGV6Zx@g~Zpl0RtzcUBfha|v|Dk%TFM(Be}8`Nff}0~s6@0}r)+ zaIhi%WEp&Vk&o-0c^g7$#b1xM?AV`{OoucWZsBVg&V;9NTjJv@S9hcd-iflXx<^0K znLHp-^E9k*?4_t@|ML=lyq0z1U&4zAxxJ^u>KBDJdv||?Qk#Yx^*Emuw#Ze;-3bml zZnL2Ds&D&*D$h^PDL*ExKv)rFe#ozeTTtz`DtOsY9jvtK>4h#*#uoC1J2-glGku>w z@VlrSYaM#Pj-TbkPIE9m7!r2!E5ciDKJjboe&X)rWU7{Hk^hHj&%uMPVS=I@RW(A= z^(D%h(59@t4bSGSg!|%G9~0UNd!dvns#G3%$uozS`P-zwHsJz5^uKww4U_^3p+eF|T8 zV&BgxiVOnQR>2Mj5qr8p-GS1!%I)9nQ@3LMP`=nT-CoUgVHDPR>Rs}w&9v3}xB+rm+u2^>$L~#TR`Qk$l zAdw4_KYAopgeA03-cG)s7)9CQPxhPh!aRlR#wLw4&zurF2b1jyFuzs}2?`nx=*Wnb zzNcSz=Y8N*KDQ@-VU?fcJ?|Es-vb))P4FlD*W zNj9EhwJqr{IM`>&K}aXBw%zRuM@^CX#``x(e7Ar7e&YR%zs{>$rN?dC({}!BPbW2{ zsvOBw(x91^Uy@8wQT<8wvE5I*y#khn{fRwPLzPYHuWD-7?VBGYhJU;?JK_HxKDGBD zc{F12vdVe(D))EBSYTMFTxA9fb>j2BtnpWR5_Dj&6SY zls$YNNlAhs?#l&_hLaO3uM5vi2k7~{pKqeL2I@CG{e$cD@%(1D+)F%#HN;cCYQ@uN z974!ZIfN@%8rNRQYKmB9*Fu|B2oOL9Q(2n|0XmoS zCl5;T5fV2ehmcXQr{YK$r0X5%Os-AES8m#&IPqeQ%&ZUUKUTVJ*YRB4T*^X*hRywe zzJDrIHh0OpzdNVwW3b-5p5w|z^1!>Zv%vknwp8;6>-HbNQ?!lOe@poho)bcM*gpF8 z#rHii#xD<^SY_KXFa7xW!+nZac47M09%|t58W31pU7l^?Om=p>SCD0*(*B-A^ZSqP zov~CpQC$Z!5v4c{LYCFYHAnj$<0wKCJi@j5D8jWW#;9{{b)M~luIw}@eHqV|`dd!Z z=cv@eVdp%&~l((rv=Ok<2px^HW{YYSgV;tJ-WK4}Dvnq&g z2EM6i_uV9DA1b9!Q$=9KqVz4lRS|yao4PZ|iFrIE>u&r|ly$+~0Ix6l#aY!Tehp5o zzp;j`6)FtWVhSZf+vHjMa0V($&_`5W<}-b6j~a4?SS~CHQ&rjq zEU{)+7KMKO&oe9aQ3TxTjO#`Du@y+3?P4tHia~zK5k*49j8`~!Lrpk^3k;pP;)nE$ zhRQWw;b?CfIxD71l4X7=8hXqYK#E4=oWe*>^S;Mye(?(Y9ES0I54FZkC^_=^xVgY9 vXbRD%CN~$!diq1FI$qF_xeIKL3=-ED-K>Ja#pR?=z)>C-=@97_Yvj70iN&o;eB>(^#;ND_i zmjD0+;Ymb6RCwCtm&tEiRTRd5=iK+6ow1G6bP8#rhN`85+6oF3AvPdbfXW6T5Nr_Q zA3zLJB{skUkdPRbus|#-F)Uahv0w#AlroE<0@{LVM4OhR&1C!8@4b6iJSS;l14#gN z!L_V6UEllp`@ZuXTg;5hIh4yhg1M8oMKB-+5*P-qk^Ir;0h}%ZDbRBOHa!jL`Bi>n z5e(tl`?AFVEZ-On!^d~udFNC8+qZ{VwaV{bewm#5;DaYpcvb+7nv?%+2Bx6Y;oL*l zA3prd$bkdc!~|I!Gqh`0*gZJ-%vVQ_7y-{+aS!;}{{TR5SJ$J%x8C~D$gW+)M~{-m zF)4)+hbEZPP|J$$Kwgsm|yv3W@uO-4vW-U0CW!w+}2kr_4{%eKQn`R zkB?)LN(FxYg`Ps#-+lWvpAFq`bv-^ktH{dzu1ZfldGwc=3ny;|PA>wG)oK=>JV|wY zoHR|*LV;vzih8*WNlKa(*t~hTb7**9Cz~#;g6ssMy<9Bs9zFj4vY8ne-G}whQo8bEsC-T~xe6Cr6}RRlvc^tCh+8D6W!dxP6DKYd zSogxv)~nE|^8})FY}vvO7pCdEze?Y=!(?MAxK$8fG3Xkww7m(xh3O)^*wKA#i>%%A zu&WIa#|h<1H)A*5!Twvup#CW$Js^fHS?KSFG|M+@58%27AzT9+4*HKq2Csiu%HQCt zWsY6Emv@%l zWK80A5D+2G{SD;{&&<+%e+hCX+DTwdj84N2RRL3kB8~bPYSU+l!ie79jC$pJ>J?jt z=FN3|gQ#bg{Qo{~b>eu_Oewv^AGhfRj}vrSh?(}W3Tan6F-qU2a$Kok*= zXxND;NC;{)YCn-C=Mf2((QZ@JbrW^$$ZLMyFA7q~8IYXq767UaRZrleZV`u>1ENdb z!LnLzK6g%YzvNjk=eJf5;3WTya~+E0FA<{7ofKr>GJl5&Sh^sA=#nVTgQ%?p<{fwf z)u0CjGT*#k#i*{re$sj^29_?Wqxthl69R}--GBscg*m_cc95nCrM?Aibb*Q#NeMCS z!39kK@-G80)|Wgl9cd%MxoIHd;Ksg?#-6TH;eky% zwhAtWwWZe7vn9v*bX?+5yX1O~L=8YI{QUhVU!0xbSPOu)eZi^Os=fa5Q?K^M4UuBO z`B2Q%fmaa~EDS&mamsv4bF@AxcOX>mb`;Mp2f=mZj{w zF(?coLJt*1DD!%rbAHb`@9+1|@1OU-_qxx0&V8=yd_LE8-{;)td>HQ-3jnW$nYkGN zVgdlSqXjVj1o%wuxcYVm=DD01WEva2@C>(j&lAFz|1VHAU38W?tcj& zroSX!00d?NvoN!TnwV*7VuD{CiM+ zK&4UfU64@hzTebr$w9VHrfY!xi&Z7I@AT@%3%h5h6uUpx-WYY@S!;F)NZGtmWuM&V z@wTu|Vm!EYSz`9BQ>|#)&;vs+{j!aSe*MPyMh{JC0{1`u(15}JynWvPi5vEBafgG% z|LuCU!c+$&c~2YyPN#)GzS>`#l|J5bh;gdx8~$zCT#K7ezSHEo_CtM2Nozl{yUkEJ zSJXOXtHI^xYWJ00RfU32-;R;gUuBP5Ne(P`RTxfhjW&_GqFab{EQ-dl_AdS^H8v8zkp#0LJt zVDA;=2%6~Je^(n7{N??loLJ31jtsf__!xWRb-Bw*TcHY&3+E%qY~%Y0+NS*a8ovO% zv!C@epHfz~auw2YMKNvesuR*t`&L5+?^UP1+bIJtChzHXf6O=j&?#AF)*0tEys-Rf z_)$yTxTa?fHU2Fn(gvycPi1H;12~h}8*(bpwh{sMlC~#Y zd3e>gA{hOK(9zEOL#C11gq77CNI#2TsKk-U{sz8eM*~#vk8zz(L$G zlDMM1-(dd}(A9w*LQr%|Je~bAn5Ke=wM*AUJ(e8{R54bK8`WCiN&DF@_9+iE?BC9& z3y6*1J3BkFzRv(im51-k)W7dVShYzk9SFl=?P_KPGcOz^r3%~ZX>ZwISVk0o57OXgdSePR&TYoNSoyx>Mu?#XRWv$U=&PCrjOSj!(Er?} zO9#aYZD5~d(Zfy{(LSHpz|k~Hk2jO6n3K@vU2220WkFh&jGfD`j+Wy_n*=JRj$W0@ zo285p56OU^b*2$5=7@alk-AvB4%D+}IC<~%Vm-w<_YtSW`~n8x{q?meF21q%@o{by zY5b|Y4K23v8=_HQ^WfhY=6>qOak9tl&?)lDJDr#@;@(a+E`#pYbZA1nzVoEZ0QH7N zRY7j4*4-aRbkM*ufC9O(R*ysHT8c_>7P-eyBwj`m} zBZ-VVyrL4W4SNeNw<5V8l;}S}o3n-40<%%jj08gzDOJHDw|M>0-K%-<_?>c=a_jA5 zls<=Bh{>;9wa4pi@N)L54K^G#$upW${2S7VN(vlWHJbR~i<+xj^^-;J0$+r&+}Xsh zVn{BH;%S>{w2elL!YX*mzF9t^g0MBd^Ndf`X>CY4)K+^3nel3%;`x`N6=mr2(llNx zn-?*8^HIBbCZ{H~U=(d-D~*IE%8;DKc|(o?@pqM#yX@gVV^j%F;T1$#ru)pnfv~CT zoo00O#khQn`l1le)_Hn;m$1)W|63`Z%0{^MS2G6o5L6~dJZ)7hwh>Z9C4c-%3lD z99`1wdY&VcTmG00N-SOL^E@ObmtWIXaHI#-HIR}Te2XpRK!fvV``6Gm(H0(Jm&C&0 zS01H>=L{&=K6;W!$>DPR5jcrPq_fV0u8P*TKYMIp8c=xR)C!86pRtsHStncKFzV}9 z)K~PgRZkGULuKRMA+j8C?;^5XL|w|Fjek4Te|e{9 zR?2jZ0ia*WjO?>_o!)d;zWcPZh~mR}XB#!IR!|xXqZI1?O5{g0oVtMaT0q9+)GVLk zu~uHBTETVwR8R?t+n21dR$9NymWU`qFv)f@T}aI|b;yDYwPzm1#9jv`;~S_n0{IV7 zyF4QLjMtF9S7YzCgk8H?VfxLyKTG?u@u)GxF=uya<7{XEOY&TvNiMa~Hlfcek$9oa z=iXd(o;XdQI%@z|;G*^XRVY+#33*9riq8m!UV)Usmg8V%<>k!j9#8uprV{rq`NZri zz->dOBXn8a8a^l39CUOkx3%;i5W7dT6J&fVP0q+m_`9feE&H#);RGMH71gN_uY!^H z$<8|a$GHhy-}<_F^*fiU$uc8KtY6YF+}2Sd&JnLF3c9SFVS8;?vY-2yA1@^F%d)>@@;Vx)fd zX|2UcqRifcOAr3zUaM&*jOQ?;#MX?4{g4&%Q&1yH41M6zS+avXyiok$ttOiE*5CjO zt@tW8mlwXCTmPwVB(0H!_)8^)Hu*SGXZ$C{F%^ z($6haw|ST1%t!YRGx#d`FrC_wAFG(BZPJz?E3symc8O3~jk@M- z^HMZmSGH;mu3edy>-@aV^IELVx8>oQiDQDKu;|0UlleugQhmKotviV>w|m^Nr)uD7 zd9JCUt`ua5d1s4eRoR7CC*_5T1tg>-*^d#<=dYGI+^h*-GuB3KmKC@&fDFEkmui9H zA|JDE=zXq6GJmQTea?H`8|!S?5OQ(jhxvse}b%%I^?hR+!V=i^39pUzm%v{^TX3xNj+tTh@ZLRX7RCf zy)UzObR&2N_f?e3@H+1Rgslc5ue%KA*AO_!lUSrP%(=1=!zV` zW2Imc?POhUA@H195K}Ln^I~0#lzb;i9OMJ1t1_hOwT$9G6*!(xHW#Z$2AXZn`>#(G! zR_SsIo$%>WoK@8xMOj|6_u4f5R&#ssyuUl*>X@bkrym|f&T2}TV5$l&wys7 z>Y&PAS2kmcu1h)OfCG?(xp*->NGi!9V{}%uKV5;J}oE6%6q@ZtA p?t0c9zOB%KGG~nM#7t&R&CcumhACwEz`9S-+MY+-Y!u`DzW^4%L;wH) diff --git a/en/_theme/doctrine/static/sidebox-foot.jpg b/en/_theme/doctrine/static/sidebox-foot.jpg deleted file mode 100644 index 34084e04c01ba87250e955c4eac3fd1c959ca94c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 437 zcmex=iF;o{=v;^GnD0RsUZK7IjyJ|1CV5fNcw z8EI*08F@HhWM^mR<>8eO5Ri}(6%>_%OAyQWe}F-dgMpQijhRu9fk}{&S&;Gn5r#wt zMn*=UBar|b3s8g^NdU-XVrCOyU;+YWB*lzO%z`XJY=%LsB7#62sG1lBg$x~;S^nQ* z-~q}o2{H>Z*fTV}-}pLvTdn$aoo)4oZz5iYI~fGl+)Iw`S$=B2+og(6z3u1sxcxf+ zd-KZk!cSi{pt=Ex$F?`(&-SG|byw?0uM+qpdY!{+sd-A}?zAUMMOHs;mDsbVLeXl+ Mx=Jst`TuVM06M-_jQ{`u diff --git a/en/_theme/doctrine/static/sidebox.jpg b/en/_theme/doctrine/static/sidebox.jpg deleted file mode 100644 index 4c6c4a420c83fae25400f798196b2070f3b2fad0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3172 zcma)82~bnl8ot>vgqW11N=bCYK@hAw%{)_y5W^F+jEz7Ei>S2}1)o(m*&`{Y)R^Zk zi!2hWq7ad_b%9L4!gC=61~7`EM2s<2!LSO!K;F&GBD8%oy_qm4=bZolzyJHbb1sG+ z!*hV(@Asu20D}WS6!-xQ0|0SD(sz5)02qJ-0AL+B^$x(ZqGBU=0Z~oh49w6A{0N|t zNF)l0MxoI2(BRkB2936{#bL3wSS*f!gB}77Kc6rkkF&QYk?id$j*gC$#nuOg!C>rg zb_)rFg`bg$=vpQ%gjf5sbTj7Y0DU4f} zAXc%Eh9>|H0p=khhyW9q(#{kafGaco)1%V{AYb-sWW)d{+9M1=)r@jYT>o!V>0E7J zV}!2JV{s^@t%I39xk#I`GDFipsdy9bO2VSxS?kXSKm-24%wN&t>hV&1@r52BC9=}W_uk5p639UmR;^ogHh-GJRn>4k->)Y6$F zA@1wAnaQPLl7cgY3<#HQ}lcT@@kmD$%@6mjWCb3bbO{+E)V| zK~AT3uqBx_UDe7hmu3@TW#mxZiO(n9?r2VEgHDAvALlxURc*kcN7^O=8ETd2G?}5E z`tOmirR<22YCqlTwsD2vb#ux=Z|`u$-4Kqt@@USVveIdJADlVSCo+?ro$3x#^tq9> z(Y1DE3%nn&b*a(!vFAn)Mz)kIT|U=^1uJ}JSQN?A#9qa>+a~VI@=Is_^GW@X>Jf9r zyGk$0>Uh1j!SPyT*}7jZ`_ED_iv>!73W-xbzqCJ2HTot;*rqD!5k5hoW6vDto@DE! zHIc#E{5*YyTSg!jg-F|-QKpf}MX$&874D*y+#5=INcu#ol9A$;GKJg4{0TI7lWnQn3s7*6ru!U*UgD-Jrd;6tubR@UXODpy=w^!Enyxs77xeN_BTr$<8V7Hixy+ z^L3Lij-C(Lzgv6TH`-43KwP1^tMjKX%Rt1?(q3i!?!uL1dTk1c&Cm4ul+5TzQF0tC zhC7s&_Qmjo0f3V!l3r=wu?=NnFq12Z+{}nosp%Xgoz|JVKYal6{nI;~MeIbE$NY19 zD7@(DcBP$jN;@Spxn`@d{nh|K#!*I7vOt4@3Z=*!k^bj2vdrLJAI~XRDODp^2sAR8 zjJL45T$~N9#Zvkr2r2gqR^#$y6@Q36k#!hNAzQ;5HTPKl#){6}F-ocI-{oPu|mF|_ilM&yh zI-?I0i``SFw9lz~-83&XZx`4nQEN6bJ($}%F4z3CUF`XAD|D||juSFApVs@}NFaD# z-d%M2*+Lw@3Rj?uPAUy>^&B3%6Rd9Eg>ma5QRul(x}7op&A!&g5oueJh|<^#&VHky z%6=TH53%^-)#=&>&ztqafYA9FV==EElRHF=#_X1_kh-;OX6uW+xqGnq+&m%ZKx0`N zXnMi1(Ss}8Qa}S@O;%KsL-@W>AH#SzmO=B;;Q9iHwz2P z0uEG^L->s*yX~k}W*=g>&AChu-P|>( z(j`~Yg76?d{r*{&<4bj8wxH0;shM&+Spy+S-gaMbWhVpHL;YeZ^4~YoHkVayH(nVS zZ*GfzCHH3g46r=U@K5B_u&Z8`-G=WkZgthi*EF!Ock1o5u{- zq|P^sw``v<#UZNQf6^Cv)Vi09)|a(6B_pcWYTd<0p-Aut^@jnBu`#g1>>uDeN+>l= z$r}g<N65YWj()Az{5ar<5@we5U>f<%3Of09a?E z_l{}@$aAdOT^bD)Eym}PjAz}k=)i)W-~`Q(@utD-OzaDV*a>i5`+hb+3)=nrP_Np2 zV~q@f%UR)dKwlz^ngeyzzHJ&t&9!u9V}3=OiL#ZO6a|;j!^)H2L=k1@6b>dHT=G2v z9fBbf9kN= z)c4FKvSyQE*WzeC)Tm@~(LHD@3aRz5o>V8R(ELONx--JG8y!PWgJ+m!q*X)mLg&;M z`U}=W$E*>WoC1Q{M=ZJ*ViKb0o{uFX#?#~;*ERf@Qd1lmwOb&6>29)gu*0mY+DM0S zPsM{EbG)4#&S%&_HPFduD_|1na^s|3nN@RYws{|%Pd`ciWBn(XC<_Phtezl&8MPbq zDlio-Gy|oc+-Jf`0+ll>VI;v60^xG9bO+|LSiu}^#bX@I722HDRPC>`nPvKmorhxP sWc~_Yvj70iN&o;eB>(^#;ND_i zmjD07 zRjZ)xDz*I!xP+n~m3WQ*jKy1f29?y6l_i-M-E@te^ zWZaY;iyrCd%v_zv_xJn$zQ6N3N<{cFo90Vy;pUGcKm9!@kGL{O1wqg%qof0@Xwuwb zj5#(oRz7lYW^!&giDpWno)jQ2+*&g=iwjHDmp2;O+fh_HSuO>H%2RlUmof?ks*MUV z5Y5}a`$<25?VC_<<>(-M>W+iEp1$+&p4o}f3SnGAP@n(-=Vb!?p>L-7gVkD#vmdX# z`Qe2t&x*4r;&SlCJkZ;e0_TKA+Z{N#Z|vFc+;{8T^wc;wV?D-t0HRP+ ziZo-DnDNOSJi2#c_U^;A*;8+Q{KUn}&ErW~7Q#RwUj%?sU<#p`rQdzz-UH9i-FIko zu~zWpm1Jz|6-M$ zV+r?vWB*tX$_uZ(v+}4Ol2-zx3wb~V8br~`^22lY-uU7}vxmZ0-&^HMJ!7~WqSPRI z>je?v=BXiuV@+N-eD_-tX{sA3|D3OK6g8&NSqTBaR{&H$+jE|Q#sKjB7kQ|!Zqjwb)_(-8z&g)gUSHxL=c}yM3!1qli8Rjpt@(84J0~=;tZ=k*%UkEyskRErVMhU*Yqr+; zPUN>TOF1b~E=Ttdc;Lp}6UV|RqE>BoTrIxK|5dBK@3?;FXo(+xcm8sg!Q9it=1fM53kNaA3|dBGGGtvXptM7Bl*jCDkzX6~+OjvShx zq@fgtAi}>EYFOtfg+U)x^nftl;$27_s+mC<26|EijPZE!Tanz`0}a-CPW$<&HH63qsjIqX%8@D(B;PP7SWIN4C;sCeBo%D?&LMyl2e!~buNd#!-ms@`nFXD59NIeqDg?27bg52mJZa&!q~~Q& zf}7H8whO%Vr?*i->5U7E)iVJeMdw=Xx5CrTEPHk&?B6v62<_DH#-g^q; zm>y3E!V=b4rY919^W%G{wQ~M^`U2%Heb@4@6=Zoqt=2rH#XqlcgSVm8DM6A`Yc`I5 zFu$6PBoTp9Sm(DmjU&xROLc0ECSepaHD2NK^^`xodI7CEGP_niohr+5Wi3sO%a0Sb zu{zzZB=dkL3gdI1T-taH<^Fop%v9KD7o@pCE5%3>GQYgRum12Z2lo!Mw3hPki#4K1 zQwlV$d+BUaptZ(3$9lbG+iiJF2l|{S-&>LO0#JZLDJVtZD;JmQj~l}uXLgT`RmvsO z%plUa+ZQf3c<;hGrARZ9MBUrdU(Nr3(u%@Z>h*TLoth`Lj$Y}Mb*kI1Q{a!UC?T`UM{R0S1MlEuBN>1 z!$c{FB3UT%;*rmmt4}3K{Pb`oo(;p0AkZiUs&gXFb%M>NCNTpS!+d;+|@e7w8@f;>Qyk4KPSNLWNzh+jfNN=iaP zSxHGrSse}-IXO5uxj4BwIl1|`dAa!n`G7!B7;J#Bgdjf<2ug^Fi-?Gbi-ELDNhvBS zC@RABtN%a1AjrX>##qVBsKme|$jB_n`2Pq)0s|um0G$a3j7+R-7=l0#vk3szF*36- z!&Ndc3Mw+OIx-8h1STp8qMHNMBKZFn0}nGJ&^l&827882cfv(q7tY&vcJAKEXFH$1 zd;E2Gd2F5CT#3_xH_|Q7r4?H$&%d^A?Y3`Md)<-)16N#0GZ)A*mF#tMTO2s!iffjR z#|o__0ilj;Eho5AMMNB+@Du|R(}D)2MV*l$3(lr(ja+3Mxi&0ovFNP3Nn60oo{L!= zy)NC1D>f;QrgZ_V0&wWa=)&MAq7}%fZMy1g z;Hpb diff --git a/en/_theme/doctrine/theme.conf b/en/_theme/doctrine/theme.conf deleted file mode 100644 index c2ee566c2..000000000 --- a/en/_theme/doctrine/theme.conf +++ /dev/null @@ -1,7 +0,0 @@ -[theme] -inherit = basic -stylesheet = default.css -pygments_style = sphinx - -[options] -nosidebar = false From 7918a42a0ee053f3e1ff8063d25552575833e9cc Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 22 Jan 2012 23:25:48 +0100 Subject: [PATCH 316/430] Theme submodule --- .gitmodules | 3 +++ en/_theme | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 en/_theme diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..e38d44b0a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "en/_theme"] + path = en/_theme + url = https://github.com/doctrine/doctrine-sphinx-theme.git diff --git a/en/_theme b/en/_theme new file mode 160000 index 000000000..e0bbe47f4 --- /dev/null +++ b/en/_theme @@ -0,0 +1 @@ +Subproject commit e0bbe47f444a68e2a37a442dd1e2a07c9d31dd5b From 9602e6785e86fa03cf768107084a509948e9e8bd Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 23 Jan 2012 00:44:19 +0100 Subject: [PATCH 317/430] Updated theme --- en/_theme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/_theme b/en/_theme index e0bbe47f4..a32137e07 160000 --- a/en/_theme +++ b/en/_theme @@ -1 +1 @@ -Subproject commit e0bbe47f444a68e2a37a442dd1e2a07c9d31dd5b +Subproject commit a32137e076d0cfbcec79c4f7fd04ab55fbbdc8e6 From 3880ec68396c89620ffb3e9c833562f69546c3a6 Mon Sep 17 00:00:00 2001 From: Juti Noppornpitak Date: Thu, 26 Jan 2012 11:29:32 -0500 Subject: [PATCH 318/430] Changed from "@var string" to "@var int" for the complete version of entities/User.php. --- en/tutorials/getting-started-xml-edition.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst index ae2c97d28..b308afc5e 100644 --- a/en/tutorials/getting-started-xml-edition.rst +++ b/en/tutorials/getting-started-xml-edition.rst @@ -642,7 +642,7 @@ The last missing definition is that of the User entity: { /** * @Id @GeneratedValue @Column(type="integer") - * @var string + * @var int */ protected $id; From d5a97c0c5991abf6eb4d3ba54414d8d39d43ac33 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 27 Jan 2012 21:36:36 +0100 Subject: [PATCH 319/430] Add fancy new index page, moving toc to toc.rst --- en/index.rst | 155 ++++++++++++++++++++++++++++++++------------------- en/toc.rst | 73 ++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 56 deletions(-) create mode 100644 en/toc.rst diff --git a/en/index.rst b/en/index.rst index 36697b429..8f0124466 100644 --- a/en/index.rst +++ b/en/index.rst @@ -1,72 +1,115 @@ Welcome to Doctrine 2 ORM's documentation! ========================================== -Reference Guide +The Doctrine documentation is comprised of tutorials, a reference section and +cookbook articles that explain different parts of the Object Relational mapper. + +Doctrine DBAL and Doctrine Common both have their own documentation. + +Getting Help +------------ + +If this documentation is not helping to answer questions you have about +Doctrine ORM don't panic. You can get help from different sources: + +- There is a :doc:`FAQ ` with answers to frequent questions. +- The `Doctrine Mailing List `_ +- Internet Relay Chat (IRC) in `#doctrine on Freenode `_ +- Report a bug on `JIRA `_. +- On `Twitter `_ with ``#doctrine2`` +- On `StackOverflow `_ + +If you need more structure over the different topics you can browse the :doc:`table +of contents `. + +Getting Started --------------- -.. toctree:: - :maxdepth: 1 - :numbered: +* **Tutorial**: + :doc:`Getting Started ` - reference/introduction - reference/architecture - reference/configuration - reference/faq - reference/basic-mapping - reference/association-mapping - reference/inheritance-mapping - reference/working-with-objects - reference/working-with-associations - reference/transactions-and-concurrency - reference/events - reference/batch-processing - reference/dql-doctrine-query-language - reference/query-builder - reference/native-sql - reference/change-tracking-policies - reference/partial-objects - reference/xml-mapping - reference/yaml-mapping - reference/annotations-reference - reference/php-mapping - reference/caching - reference/improving-performance - reference/tools - reference/metadata-drivers - reference/best-practices - reference/limitations-and-known-issues +* **Reference**: + :doc:`Introduction ` | + :doc:`Architecture ` | + :doc:`Configuration ` | + :doc:`Tools ` | + :doc:`Limitations and knowns issues ` -Tutorials ---------- +Mapping Objects onto a Database +------------------------------- -.. toctree:: - :maxdepth: 1 +* **Basic Reference**: + :doc:`Mapping Objects and Fields ` | + :doc:`Mapping Associations ` | + :doc:`Inheritance Mapping ` - tutorials/getting-started-xml-edition - tutorials/working-with-indexed-associations - tutorials/extra-lazy-associations - tutorials/composite-primary-keys +* **Mapping Driver References**: + :doc:`XML ` | + :doc:`YAML ` | + :doc:`Docblock Annotations ` | + :doc:`PHP Mapping ` | + :doc:`Metadata Drivers ` +Working with Objects +-------------------- + +* **Basic Reference**: + :doc:`Working with Objects ` | + :doc:`Working with Assocations ` | + :doc:`Events ` + +* **Querying Objects**: + :doc:`DQL - Doctrine Query Language ` | + :doc:`QueryBuilder ` | + :doc:`Native SQL Queries ` + +* **Tutorials**: + :doc:`Working with indexed associations ` | + :doc:`Extra Lazy Assocations ` | + :doc:`Composite Primary Keys ` + +Advanced Topics +--------------- + +* **Database Integration**: + :doc:`Transactions and Concurrency ` + +* **Performance**: + :doc:`Improving Performance ` | + :doc:`Caching ` | + :doc:`Partial Objects ` | + :doc:`Change Tracking Policies ` + +* **Best Practices**: + :doc:`Best Practices ` Cookbook -------- -.. toctree:: - :maxdepth: 1 +* **Patterns**: + :doc:`Aggregate Fields ` | + :doc:`Decorator Pattern ` | + :doc:`Strategy Pattern ` | - cookbook/aggregate-fields - cookbook/decorator-pattern - cookbook/dql-custom-walkers - cookbook/dql-user-defined-functions - cookbook/implementing-arrayaccess-for-domain-objects - cookbook/implementing-the-notify-changetracking-policy - cookbook/implementing-wakeup-or-clone - cookbook/integrating-with-codeigniter - cookbook/sql-table-prefixes - cookbook/strategy-cookbook-introduction - cookbook/validation-of-entities - cookbook/working-with-datetime - cookbook/mysql-enums - cookbook/advanced-field-value-conversion-using-custom-mapping-types - cookbook/entities-in-session +* **DQL Extension Points**: + :doc:`DQL Custom Walkers ` | + :doc:`DQL User-Defined-Functions ` + +* **Implementation**: + :doc:`Array Access ` | + :doc:`Notify ChangeTracking Example ` | + :doc:`Using Wakeup Or Clone ` | + :doc:`Working with DateTime ` | + :doc:`Validation ` | + :doc:`Entities in the Session ` + +* **Integration into Frameworks/Libraries** + :doc:`CodeIgniter ` + +* **Hidden Gems** + :doc:`Prefixing Table Name ` + +* **Custom Datatypes** + :doc:`MySQL Enums ` + :doc:`Advanced Field Value Conversion ` diff --git a/en/toc.rst b/en/toc.rst new file mode 100644 index 000000000..2c078eabd --- /dev/null +++ b/en/toc.rst @@ -0,0 +1,73 @@ +Welcome to Doctrine 2 ORM's documentation! +========================================== + +Tutorials +--------- + +.. toctree:: + :maxdepth: 1 + + tutorials/getting-started-xml-edition + tutorials/working-with-indexed-associations + tutorials/extra-lazy-associations + tutorials/composite-primary-keys + +Reference Guide +--------------- + +.. toctree:: + :maxdepth: 1 + :numbered: + + reference/introduction + reference/architecture + reference/configuration + reference/faq + reference/basic-mapping + reference/association-mapping + reference/inheritance-mapping + reference/working-with-objects + reference/working-with-associations + reference/transactions-and-concurrency + reference/events + reference/batch-processing + reference/dql-doctrine-query-language + reference/query-builder + reference/native-sql + reference/change-tracking-policies + reference/partial-objects + reference/xml-mapping + reference/yaml-mapping + reference/annotations-reference + reference/php-mapping + reference/caching + reference/improving-performance + reference/tools + reference/metadata-drivers + reference/best-practices + reference/limitations-and-known-issues + + + +Cookbook +-------- + +.. toctree:: + :maxdepth: 1 + + cookbook/aggregate-fields + cookbook/decorator-pattern + cookbook/dql-custom-walkers + cookbook/dql-user-defined-functions + cookbook/implementing-arrayaccess-for-domain-objects + cookbook/implementing-the-notify-changetracking-policy + cookbook/implementing-wakeup-or-clone + cookbook/integrating-with-codeigniter + cookbook/sql-table-prefixes + cookbook/strategy-cookbook-introduction + cookbook/validation-of-entities + cookbook/working-with-datetime + cookbook/mysql-enums + cookbook/advanced-field-value-conversion-using-custom-mapping-types + cookbook/entities-in-session + From 19b7d4d0d427ba70673325c9da133a6fa50974f1 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 27 Jan 2012 21:43:04 +0100 Subject: [PATCH 320/430] Simplify section names even more --- en/index.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/en/index.rst b/en/index.rst index 8f0124466..fe7189c29 100644 --- a/en/index.rst +++ b/en/index.rst @@ -39,9 +39,9 @@ Mapping Objects onto a Database ------------------------------- * **Basic Reference**: - :doc:`Mapping Objects and Fields ` | - :doc:`Mapping Associations ` | - :doc:`Inheritance Mapping ` + :doc:`Objects and Fields ` | + :doc:`Associations ` | + :doc:`Inheritance ` * **Mapping Driver References**: :doc:`XML ` | @@ -54,17 +54,17 @@ Working with Objects -------------------- * **Basic Reference**: - :doc:`Working with Objects ` | - :doc:`Working with Assocations ` | + :doc:`Entities ` | + :doc:`Assocations ` | :doc:`Events ` * **Querying Objects**: - :doc:`DQL - Doctrine Query Language ` | + :doc:`Doctrine Query Language (DQL) ` | :doc:`QueryBuilder ` | :doc:`Native SQL Queries ` * **Tutorials**: - :doc:`Working with indexed associations ` | + :doc:`Indexed associations ` | :doc:`Extra Lazy Assocations ` | :doc:`Composite Primary Keys ` From b0ec3dfb47c77f3425f1cb4dc360f7a317ed6e2f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 28 Jan 2012 00:49:37 +0100 Subject: [PATCH 321/430] Started refactoring of the documentation towards smaller chapters, grouped into logical units with better explanations (hopefully). --- en/index.rst | 7 +- en/reference/association-mapping.rst | 843 ++++++++---------- en/reference/limitations-and-known-issues.rst | 55 +- en/reference/unitofwork-associations.rst | 60 ++ en/reference/unitofwork.rst | 135 +++ en/reference/working-with-associations.rst | 2 +- en/toc.rst | 6 +- en/tutorials/ordered-associations.rst | 79 ++ 8 files changed, 649 insertions(+), 538 deletions(-) create mode 100644 en/reference/unitofwork-associations.rst create mode 100644 en/reference/unitofwork.rst create mode 100644 en/tutorials/ordered-associations.rst diff --git a/en/index.rst b/en/index.rst index fe7189c29..62ded5461 100644 --- a/en/index.rst +++ b/en/index.rst @@ -63,10 +63,15 @@ Working with Objects :doc:`QueryBuilder ` | :doc:`Native SQL Queries ` +* **UnitOfWork dissected**: + :doc:`Doctrine Internals explained ` | + :doc:`Owning and Inverse Side Associations ` + * **Tutorials**: :doc:`Indexed associations ` | :doc:`Extra Lazy Assocations ` | - :doc:`Composite Primary Keys ` + :doc:`Composite Primary Keys ` | + :doc:`Ordered associations ` Advanced Topics --------------- diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst index fdd9259f7..1622be013 100644 --- a/en/reference/association-mapping.rst +++ b/en/reference/association-mapping.rst @@ -1,402 +1,23 @@ Association Mapping =================== -This chapter explains how associations between entities are mapped -with Doctrine. We start out with an explanation of the concept of -owning and inverse sides which is important to understand when -working with bidirectional associations. Please read these -explanations carefully. +This chapter introduces association mappings which are used to explain +references between objects and are mapped to a relational database using +foreign keys. -.. _association-mapping-owning-inverse: +Instead of working with the foreign keys directly you will always work with +references to objects: -Owning Side and Inverse Side ----------------------------- +- A reference to a single object is represented by a foreign key. +- A collection of objects is represented by many foreign keys pointing to the object holding the collection -When mapping bidirectional associations it is important to -understand the concept of the owning and inverse sides. The -following general rules apply: +This chapter is split into three different sections. +- A list of all the possible association mapping use-cases is given. +- :ref:`association_mapping_defaults` are explained that simplify the use-case examples. +- :ref:`collections` are introduced that contain entities in assoactions. -- Relationships may be bidirectional or unidirectional. -- A bidirectional relationship has both an owning side and an - inverse side. -- A unidirectional relationship only has an owning side. -- The owning side of a relationship determines the updates to the - relationship in the database. - -The following rules apply to *bidirectional* associations: - - -- The inverse side of a bidirectional relationship must refer to - its owning side by use of the mappedBy attribute of the OneToOne, - OneToMany, or ManyToMany mapping declaration. The mappedBy - attribute designates the field in the entity that is the owner of - the relationship. -- The owning side of a bidirectional relationship must refer to - its inverse side by use of the inversedBy attribute of the - OneToOne, ManyToOne, or ManyToMany mapping declaration. The - inversedBy attribute designates the field in the entity that is the - inverse side of the relationship. -- The many side of OneToMany/ManyToOne bidirectional relationships - *must* be the owning side, hence the mappedBy element can not be - specified on the ManyToOne side. -- For OneToOne bidirectional relationships, the owning side - corresponds to the side that contains the corresponding foreign key - (@JoinColumn(s)). -- For ManyToMany bidirectional relationships either side may be - the owning side (the side that defines the @JoinTable and/or does - not make use of the mappedBy attribute, thus using a default join - table). - -Especially important is the following: - -**The owning side of a relationship determines the updates to the relationship in the database**. - -To fully understand this, remember how bidirectional associations -are maintained in the object world. There are 2 references on each -side of the association and these 2 references both represent the -same association but can change independently of one another. Of -course, in a correct application the semantics of the bidirectional -association are properly maintained by the application developer -(that's his responsibility). Doctrine needs to know which of these -two in-memory references is the one that should be persisted and -which not. This is what the owning/inverse concept is mainly used -for. - -**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)** - -The owning side of a bidirectional association is the side Doctrine -"looks at" when determining the state of the association, and -consequently whether there is anything to do to update the -association in the database. - -.. note:: - - "Owning side" and "inverse side" are technical concepts of - the ORM technology, not concepts of your domain model. What you - consider as the owning side in your domain model can be different - from what the owning side is for Doctrine. These are unrelated. - - -Collections ------------ - -In all the examples of many-valued associations in this manual we -will make use of a ``Collection`` interface and a corresponding -default implementation ``ArrayCollection`` that are defined in the -``Doctrine\Common\Collections`` namespace. Why do we need that? -Doesn't that couple my domain model to Doctrine? Unfortunately, PHP -arrays, while being great for many things, do not make up for good -collections of business objects, especially not in the context of -an ORM. The reason is that plain PHP arrays can not be -transparently extended / instrumented in PHP code, which is -necessary for a lot of advanced ORM features. The classes / -interfaces that come closest to an OO collection are ArrayAccess -and ArrayObject but until instances of these types can be used in -all places where a plain array can be used (something that may -happen in PHP6) their usability is fairly limited. You "can" -type-hint on ``ArrayAccess`` instead of ``Collection``, since the -Collection interface extends ``ArrayAccess``, but this will -severely limit you in the way you can work with the collection, -because the ``ArrayAccess`` API is (intentionally) very primitive -and more importantly because you can not pass this collection to -all the useful PHP array functions, which makes it very hard to -work with. - -.. warning:: - - The Collection interface and ArrayCollection class, - like everything else in the Doctrine namespace, are neither part of - the ORM, nor the DBAL, it is a plain PHP class that has no outside - dependencies apart from dependencies on PHP itself (and the SPL). - Therefore using this class in your domain classes and elsewhere - does not introduce a coupling to the persistence layer. The - Collection class, like everything else in the Common namespace, is - not part of the persistence layer. You could even copy that class - over to your project if you want to remove Doctrine from your - project and all your domain classes will work the same as before. - - -Mapping Defaults ----------------- - -Before we introduce all the association mappings in detail, you -should note that the @JoinColumn and @JoinTable definitions are -usually optional and have sensible default values. The defaults for -a join column in a one-to-one/many-to-one association is as -follows: - -:: - - name: "_id" - referencedColumnName: "id" - -As an example, consider this mapping: - -.. configuration-block:: - - .. code-block:: php - - - - - -
    - - .. code-block:: yaml - - Product: - type: entity - oneToOne: - shipping: - targetEntity: Shipping - -This is essentially the same as the following, more verbose, -mapping: - -.. configuration-block:: - - .. code-block:: php - - - - - - - - - - .. code-block:: yaml - - Product: - type: entity - oneToOne: - shipping: - targetEntity: Shipping - joinColumn: - name: shipping_id - referencedColumnName: id - -The @JoinTable definition used for many-to-many mappings has -similar defaults. As an example, consider this mapping: - -.. configuration-block:: - - .. code-block:: php - - - - - - - - .. code-block:: yaml - - User: - type: entity - manyToMany: - groups: - targetEntity: Group - -This is essentially the same as the following, more verbose, -mapping: - -.. configuration-block:: - - .. code-block:: php - - - - - - - - - - - - - - - - - .. code-block:: yaml - - User: - type: entity - manyToMany: - groups: - targetEntity: Group - joinTable: - name: User_Group - joinColumns: - User_id: - referencedColumnName: id - inverseJoinColumns: - Group_id: - referencedColumnName: id - -In that case, the name of the join table defaults to a combination -of the simple, unqualified class names of the participating -classes, separated by an underscore character. The names of the -join columns default to the simple, unqualified class name of the -targeted class followed by "\_id". The referencedColumnName always -defaults to "id", just as in one-to-one or many-to-one mappings. - -If you accept these defaults, you can reduce the mapping code to a -minimum. - -Initializing Collections ------------------------- - -You have to be careful when using entity fields that contain a -collection of related entities. Say we have a User entity that -contains a collection of groups: - -.. code-block:: php - - groups; - } - } - -With this code alone the ``$groups`` field only contains an -instance of ``Doctrine\Common\Collections\Collection`` if the user -is retrieved from Doctrine, however not after you instantiated a -fresh instance of the User. When your user entity is still new -``$groups`` will obviously be null. - -This is why we recommend to initialize all collection fields to an -empty ``ArrayCollection`` in your entities constructor: - -.. code-block:: php - - groups = new ArrayCollection(); - } - - public function getGroups() - { - return $this->groups; - } - } - -Now the following code will be working even if the Entity hasn't -been associated with an EntityManager yet: - -.. code-block:: php - - find('Group', $groupId); - $user = new User(); - $user->getGroups()->add($group); - -Runtime vs Development Mapping Validation ------------------------------------------ - -For performance reasons Doctrine 2 has to skip some of the -necessary validation of association mappings. You have to execute -this validation in your development workflow to verify the -associations are correctly defined. - -You can either use the Doctrine Command Line Tool: - -.. code-block:: php - - doctrine orm:validate-schema - -Or you can trigger the validation manually: - -.. code-block:: php - - validateMapping(); - - if (count($errors) > 0) { - // Lots of errors! - echo implode("\n\n", $errors); - } - -If the mapping is invalid the errors array contains a positive -number of elements with error messages. - -.. warning:: - - One mapping option that is not validated is the use of the referenced column name. - It has to point to the equivalent primary key otherwise Doctrine will not work. - -.. note:: - - One common error is to use a backlash in front of the - fully-qualified class-name. Whenever a FQCN is represented inside a - string (such as in your mapping definitions) you have to drop the - prefix backslash. PHP does this with ``get_class()`` or Reflection - methods for backwards compatibility reasons. - +To master assocations you should also learn about :doc:`owning and inverse sides of associations ` One-To-One, Unidirectional -------------------------- @@ -411,7 +32,7 @@ the ``Product`` so it is unidirectional. .. code-block:: php _id" + referencedColumnName: "id" + +As an example, consider this mapping: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + .. code-block:: yaml + + Product: + type: entity + oneToOne: + shipping: + targetEntity: Shipping + +This is essentially the same as the following, more verbose, +mapping: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + .. code-block:: yaml + + Product: + type: entity + oneToOne: + shipping: + targetEntity: Shipping + joinColumn: + name: shipping_id + referencedColumnName: id + +The @JoinTable definition used for many-to-many mappings has +similar defaults. As an example, consider this mapping: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + .. code-block:: yaml + + User: + type: entity + manyToMany: + groups: + targetEntity: Group + +This is essentially the same as the following, more verbose, +mapping: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + + + + + .. code-block:: yaml + + User: + type: entity + manyToMany: + groups: + targetEntity: Group + joinTable: + name: User_Group + joinColumns: + User_id: + referencedColumnName: id + inverseJoinColumns: + Group_id: + referencedColumnName: id + +In that case, the name of the join table defaults to a combination +of the simple, unqualified class names of the participating +classes, separated by an underscore character. The names of the +join columns default to the simple, unqualified class name of the +targeted class followed by "\_id". The referencedColumnName always +defaults to "id", just as in one-to-one or many-to-one mappings. + +If you accept these defaults, you can reduce the mapping code to a +minimum. + +.. _collections: + +Collections +----------- + +In all the examples of many-valued associations in this manual we +will make use of a ``Collection`` interface and a corresponding +default implementation ``ArrayCollection`` that are defined in the +``Doctrine\Common\Collections`` namespace. Why do we need that? +Doesn't that couple my domain model to Doctrine? Unfortunately, PHP +arrays, while being great for many things, do not make up for good +collections of business objects, especially not in the context of +an ORM. The reason is that plain PHP arrays can not be +transparently extended / instrumented in PHP code, which is +necessary for a lot of advanced ORM features. The classes / +interfaces that come closest to an OO collection are ArrayAccess +and ArrayObject but until instances of these types can be used in +all places where a plain array can be used (something that may +happen in PHP6) their usability is fairly limited. You "can" +type-hint on ``ArrayAccess`` instead of ``Collection``, since the +Collection interface extends ``ArrayAccess``, but this will +severely limit you in the way you can work with the collection, +because the ``ArrayAccess`` API is (intentionally) very primitive +and more importantly because you can not pass this collection to +all the useful PHP array functions, which makes it very hard to +work with. + +.. warning:: + + The Collection interface and ArrayCollection class, + like everything else in the Doctrine namespace, are neither part of + the ORM, nor the DBAL, it is a plain PHP class that has no outside + dependencies apart from dependencies on PHP itself (and the SPL). + Therefore using this class in your domain classes and elsewhere + does not introduce a coupling to the persistence layer. The + Collection class, like everything else in the Common namespace, is + not part of the persistence layer. You could even copy that class + over to your project if you want to remove Doctrine from your + project and all your domain classes will work the same as before. + + + +Initializing Collections +------------------------ + +You have to be careful when using entity fields that contain a +collection of related entities. Say we have a User entity that +contains a collection of groups: .. code-block:: php groups; + } } -The DQL Snippet in OrderBy is only allowed to consist of -unqualified, unquoted field names and of an optional ASC/DESC -positional statement. Multiple Fields are separated by a comma (,). -The referenced field names have to exist on the ``targetEntity`` -class of the ``@ManyToMany`` or ``@OneToMany`` annotation. +With this code alone the ``$groups`` field only contains an +instance of ``Doctrine\Common\Collections\Collection`` if the user +is retrieved from Doctrine, however not after you instantiated a +fresh instance of the User. When your user entity is still new +``$groups`` will obviously be null. -The semantics of this feature can be described as follows. - - -- ``@OrderBy`` acts as an implicit ORDER BY clause for the given - fields, that is appended to all the explicitly given ORDER BY - items. -- All collections of the ordered type are always retrieved in an - ordered fashion. -- To keep the database impact low, these implicit ORDER BY items - are only added to an DQL Query if the collection is fetch joined in - the DQL query. - -Given our previously defined example, the following would not add -ORDER BY, since g is not fetch joined: - -.. code-block:: sql - - SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10 - -However the following: - -.. code-block:: sql - - SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 - -...would internally be rewritten to: - -.. code-block:: sql - - SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC - -You can't reverse the order with an explicit DQL ORDER BY: - -.. code-block:: sql - - SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC - -...is internally rewritten to: - -.. code-block:: sql - - SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC +This is why we recommend to initialize all collection fields to an +empty ``ArrayCollection`` in your entities constructor: + +.. code-block:: php + + groups = new ArrayCollection(); + } + + public function getGroups() + { + return $this->groups; + } + } + +Now the following code will be working even if the Entity hasn't +been associated with an EntityManager yet: + +.. code-block:: php + + find('Group', $groupId); + $user = new User(); + $user->getGroups()->add($group); + +Runtime vs Development Mapping Validation +----------------------------------------- + +For performance reasons Doctrine 2 has to skip some of the +necessary validation of association mappings. You have to execute +this validation in your development workflow to verify the +associations are correctly defined. + +You can either use the Doctrine Command Line Tool: + +.. code-block:: php + + doctrine orm:validate-schema + +Or you can trigger the validation manually: + +.. code-block:: php + + validateMapping(); + + if (count($errors) > 0) { + // Lots of errors! + echo implode("\n\n", $errors); + } + +If the mapping is invalid the errors array contains a positive +number of elements with error messages. + +.. warning:: + + One mapping option that is not validated is the use of the referenced column name. + It has to point to the equivalent primary key otherwise Doctrine will not work. + +.. note:: + + One common error is to use a backlash in front of the + fully-qualified class-name. Whenever a FQCN is represented inside a + string (such as in your mapping definitions) you have to drop the + prefix backslash. PHP does this with ``get_class()`` or Reflection + methods for backwards compatibility reasons. diff --git a/en/reference/limitations-and-known-issues.rst b/en/reference/limitations-and-known-issues.rst index 74d87c25d..2d6cedd15 100644 --- a/en/reference/limitations-and-known-issues.rst +++ b/en/reference/limitations-and-known-issues.rst @@ -76,47 +76,6 @@ objects using ``serialize()/deserialize()`` which the DBAL Type The feature request for full value-object support `is described in the DDC-93 ticket `_. -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. - -Restricing Associations -~~~~~~~~~~~~~~~~~~~~~~~ - -There is currently no way to restrict associations to a subset of entities matching a given condition. -You should use a DQL query to retrieve a subset of associated entities. For example -if you need all visible articles in a certain category you could have the following code -in an entity repository: - -.. code-block:: php - - getEntityManager() - ->createQuery($dql) - ->setParameter(1, $category) - ->getResult(); - } - } Cascade Merge with Bi-directional Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -162,7 +121,7 @@ to the same entity. Behaviors ~~~~~~~~~ -Doctrine 2 *will never* include a behavior system like Doctrine 1 +Doctrine 2 will **never** include a behavior system like Doctrine 1 in the core library. We don't think behaviors add more value than they cost pain and debugging hell. Please see the many different blog posts we have written on this topics: @@ -173,7 +132,7 @@ blog posts we have written on this topics: - `Doctrine 2 Behavioral Extensions `_ - `Doctrator _ -Doctrine 2 has enough hooks and extension points so that *you* can +Doctrine 2 has enough hooks and extension points so that **you** can add whatever you want on top of it. None of this will ever become core functionality of Doctrine2 however, you will have to rely on third party extensions for magical behaviors. @@ -199,11 +158,15 @@ 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. +See the Open Bugs on Jira for more details on `bugs, improvement and feature +requests +`_. + Identifier Quoting and Legacy Databases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For compatibility reasons between all the supported vendors and -edge case problems Doctrine 2 does *NOT* do automatic identifier +edge case problems Doctrine 2 does **NOT** do automatic identifier quoting. This can lead to problems when trying to get legacy-databases to work with Doctrine 2. @@ -222,5 +185,5 @@ tables and column names to avoid the legacy quoting nightmare. Microsoft SQL Server and Doctrine "datetime" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Doctrine assumes that you use DateTime2 data-types. If your legacy database contains DateTime -datatypes then you have to add your own data-type (see Basic Mapping for an example). \ No newline at end of file +Doctrine assumes that you use ``DateTime2`` data-types. If your legacy database contains DateTime +datatypes then you have to add your own data-type (see Basic Mapping for an example). diff --git a/en/reference/unitofwork-associations.rst b/en/reference/unitofwork-associations.rst new file mode 100644 index 000000000..954567ec9 --- /dev/null +++ b/en/reference/unitofwork-associations.rst @@ -0,0 +1,60 @@ +Association Updates: Owning Side and Inverse Side +================================================= + +When mapping bidirectional associations it is important to +understand the concept of the owning and inverse sides. The +following general rules apply: + +- Relationships may be bidirectional or unidirectional. +- A bidirectional relationship has both an owning side and an inverse side +- A unidirectional relationship only has an owning side. +- Doctrine will **only** check the owning side of an assocation for changes. + +Bidirectional Associations +-------------------------- + +The following rules apply to **bidirectional** associations: + +- The inverse side has to use the ``mappedBy`` attribute of the OneToOne, + OneToMany, or ManyToMany mapping declaration. The mappedBy + attribute contains the name of the association-field on the owning side. +- The owning side has to use the ``inversedBy`` attribute of the + OneToOne, ManyToOne, or ManyToMany mapping declaration. + The inversedBy attribute contains the name of the association-field + on the inverse-side. +- ManyToOne is always the owning side of a bidirectional assocation. +- OneToMany is always the inverse side of a bidirectional assocation. +- The owning side of a OneToOne assocation is the entity with the table + containing the foreign key. +- You can pick the owning side of a many-to-many assocation yourself. + +Important concepts +------------------ + +**Doctrine will only check the owning side of an assocation for changes.** + +To fully understand this, remember how bidirectional associations +are maintained in the object world. There are 2 references on each +side of the association and these 2 references both represent the +same association but can change independently of one another. Of +course, in a correct application the semantics of the bidirectional +association are properly maintained by the application developer +(that's his responsibility). Doctrine needs to know which of these +two in-memory references is the one that should be persisted and +which not. This is what the owning/inverse concept is mainly used +for. + +**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)** + +The owning side of a bidirectional association is the side Doctrine +"looks at" when determining the state of the association, and +consequently whether there is anything to do to update the +association in the database. + +.. note:: + + "Owning side" and "inverse side" are technical concepts of + the ORM technology, not concepts of your domain model. What you + consider as the owning side in your domain model can be different + from what the owning side is for Doctrine. These are unrelated. + diff --git a/en/reference/unitofwork.rst b/en/reference/unitofwork.rst new file mode 100644 index 000000000..856f30a6e --- /dev/null +++ b/en/reference/unitofwork.rst @@ -0,0 +1,135 @@ +Doctrine Internals explained +============================ + +Object relational mapping is a complex topic and sufficiently understanding how Doctrine works internally helps you use its full power. + +How Doctrine keeps track of Objects +----------------------------------- + +Doctrine uses the Identity Map pattern to track objects. Whenever you fetch an +object from the database, Doctrine will keep a reference to this object inside +its UnitOfWork. The array holding all the entity references is two-levels deep +and has the keys "root entity name" and "id". Since Doctrine allows composite +keys the id is a sorted, serialized version of all the key columns. + +This allows Doctrine room for optimizations. If you call the EntiyManager and +ask for an entity with a specific ID twice, it will return the same instance: + +.. code-block:: php + + public function testIdentityMap() + { + $objectA = $this->entityManager->find('EntityName', 1); + $objectB = $this->entityManager->find('EntityName', 1); + + $this->assertSame($objectA, $objectB) + } + +Only one SELECT query will be fired against the database here. In the second +``EntityManager#find()`` call Doctrine will check the identity map first and +doesn't need to make that database roundtrip. + +Even if you get a proxy object first then fetch the object by the same id you +will still end up with the same reference: + +.. code-block:: php + + public function testIdentityMapReference() + { + $objectA = $this->entityManager->getReference('EntityName', 1); + // check for proxyinterface + $this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA); + + $objectB = $this->entityManager->find('EntityName', 1); + + $this->assertSame($objectA, $objectB) + } + +The identity map being indexed by primary keys only allows shortcuts when you +ask for objects by primary key. Assume you have the following ``persons`` +table: + +:: + + id | name + ------------- + 1 | Benjamin + 2 | Bud + +Take the following example where two +consecutive calls are made against a repository to fetch an entity by a set of +criteria: + +.. code-block:: php + + public function testIdentityMapRepositoryFindBy() + { + $repository = $this->entityManager->getRepository('Person'); + $objectA = $repository->findOneBy(array('name' => 'Benjamin')); + $objectB = $repository->findOneBy(array('name' => 'Benjamin')); + + $this->assertSame($objectA, $objectB); + } + +This query will still return the same references and `$objectA` and `$objectB` +are indeed referencing the same object. However when checking your SQL logs you +will realize that two queries have been executed against the database. Doctrine +only knows objects by id, so a query for different criteria has to go to the +database, even if it was executed just before. + +But instead of creating a second Person object Doctrine first gets the primary +key from the row and check if it already has an object inside the UnitOfWork +with that primary key. In our example it finds an object and decides to return +this instead of creating a new one. + +The identity map has a second use-case. When you call ``EntityManager#flush`` +Doctrine will ask the identity map for all objects that are currently managed. +This means you don't have to call ``EntityManager#persist`` over and over again +to pass known objects to the EntityManager. This is a NO-OP for known entities, +but leads to much code written that is confusing to other developers. + +The following code WILL update your database with the changes made to the +``Person`` object, even if you did not call ``EntityManager#persist``: + +.. code-block:: php + + find("Person", 1); + $user->setName("Guilherme"); + $entityManager->flush(); + +How Doctrine Detects Changes +---------------------------- + +Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI). +This means you map php objects into a relational database that don't +necessarily know about the database at all. A natural question would now be, +"how does Doctrine even detect objects have changed?". + +For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch +an object from the database Doctrine will keep a copy of all the properties and +associations inside the UnitOfWork. Because variables in the PHP language are +subject to "copy-on-write" the memory usage of a PHP request that only reads +objects from the database is the same as if Doctrine did not keep this variable +copy. Only if you start changing variables PHP will create new variables internally +that consume new memory. + +Now whenever you call ``EntityManager#flush`` Doctrine will iterate over the +Identity Map and for each object compares the original property and association +values with the values that are currently set on the object. If changes are +detected then the object is qeued for an SQL UPDATE operation. Only the fields +that actually changed are updated. + +This process has an obvious performance impact. The larger the size of the +UnitOfWork is, the longer this computation takes. There are several ways to +optimize the performance of the Flush Operation: + +- Mark entities as read only. These entities can only be inserted or removed, + but are never updated. They are omitted in the changeset calculation. +- Temporarily mark entities as read only. If you have a very large UnitOfWork + but know that a large set of entities has not changed, just mark them as read + only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``. +- Flush only a single entity with ``$entityManager->flush($entity)``. +- Use :doc:`Change Tracking Policies ` to use more + explicit strategies of notifying the UnitOfWork what objects/properties + changed. diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst index 3772b8085..55594123e 100644 --- a/en/reference/working-with-associations.rst +++ b/en/reference/working-with-associations.rst @@ -7,7 +7,7 @@ collections of objects. When it comes to persistence, it is important to understand three main things: -- The :ref:`concept of owning and inverse sides ` +- The :doc:`concept of owning and inverse sides ` in bidirectional associations. - If an entity is removed from a collection, the association is removed, not the entity itself. A collection of entities always diff --git a/en/toc.rst b/en/toc.rst index 2c078eabd..f4f407735 100644 --- a/en/toc.rst +++ b/en/toc.rst @@ -11,6 +11,7 @@ Tutorials tutorials/working-with-indexed-associations tutorials/extra-lazy-associations tutorials/composite-primary-keys + tutorials/ordered-associations Reference Guide --------------- @@ -28,8 +29,10 @@ Reference Guide reference/inheritance-mapping reference/working-with-objects reference/working-with-associations - reference/transactions-and-concurrency reference/events + reference/unitofwork + reference/unitofwork-associations + reference/transactions-and-concurrency reference/batch-processing reference/dql-doctrine-query-language reference/query-builder @@ -48,7 +51,6 @@ Reference Guide reference/limitations-and-known-issues - Cookbook -------- diff --git a/en/tutorials/ordered-associations.rst b/en/tutorials/ordered-associations.rst new file mode 100644 index 000000000..52b217308 --- /dev/null +++ b/en/tutorials/ordered-associations.rst @@ -0,0 +1,79 @@ +Ordering To-Many Assocations +---------------------------- + +There use-cases you will want to sort collections when they are +retrieved from the database. In userland you do this as long as you +haven't initially saved an entity with its associations into the +database. To retrieve a sorted collection from the database you can +use the ``@OrderBy`` annotation with an collection that specifies +an DQL snippet that is appended to all queries with this +collection. + +Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you +can specify the ``@OrderBy`` in the following way: + +.. code-block:: php + + 10 + +However the following: + +.. code-block:: sql + + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 + +...would internally be rewritten to: + +.. code-block:: sql + + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC + +You can't reverse the order with an explicit DQL ORDER BY: + +.. code-block:: sql + + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC + +...is internally rewritten to: + +.. code-block:: sql + + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC + + From f9523aa41993d39d4affd0882b329796121bea5f Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 29 Jan 2012 15:59:45 +0100 Subject: [PATCH 322/430] Making the correct usage of LIKE expressions and placeholders explicit --- en/reference/dql-doctrine-query-language.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index a9e8e03d6..8350c9a03 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -317,7 +317,8 @@ Restricting a JOIN clause by additional conditions: .. code-block:: php createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%'"); + $query = $em->createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE :foo"); + $query->setParameter('foo', '%foo%'); $users = $query->getResult(); Using several Fetch JOINs: From 5e3e48c8dd8edde8b2ae65af2fb95315d4b1ad80 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 29 Jan 2012 19:48:09 +0100 Subject: [PATCH 323/430] Rename getting-started tutorial page. --- en/index.rst | 2 +- ...ed-xml-edition.rst => getting-started.rst} | 54 ++++++++++++------- 2 files changed, 37 insertions(+), 19 deletions(-) rename en/tutorials/{getting-started-xml-edition.rst => getting-started.rst} (98%) diff --git a/en/index.rst b/en/index.rst index 62ded5461..034432add 100644 --- a/en/index.rst +++ b/en/index.rst @@ -26,7 +26,7 @@ Getting Started --------------- * **Tutorial**: - :doc:`Getting Started ` + :doc:`Getting Started ` * **Reference**: :doc:`Introduction ` | diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started.rst similarity index 98% rename from en/tutorials/getting-started-xml-edition.rst rename to en/tutorials/getting-started.rst index d46a0aa19..ab67aefe4 100644 --- a/en/tutorials/getting-started-xml-edition.rst +++ b/en/tutorials/getting-started.rst @@ -135,6 +135,7 @@ following set of classes. Put them into `entities/Bug.php`, } .. code-block:: php + // User.php class User { @@ -207,6 +208,8 @@ with the assumptions about related collections: class Bug { + // ... (previous code) + protected $products = null; public function __construct() @@ -222,6 +225,8 @@ with the assumptions about related collections: use Doctrine\Common\Collections\ArrayCollection; class User { + // ... (previous code) + protected $reportedBugs = null; protected $assignedBugs = null; @@ -298,6 +303,8 @@ the bi-directional reference: // entities/Bug.php class Bug { + // ... (previous code) + protected $engineer; protected $reporter; @@ -330,6 +337,8 @@ the bi-directional reference: // entities/User.php class User { + // ... (previous code) + protected $reportedBugs = null; protected $assignedBugs = null; @@ -383,6 +392,8 @@ the database that points from Bugs to Products. // entities/Bug.php class Bug { + // ... (previous code) + protected $products = null; public function assignToProduct($product) @@ -450,13 +461,15 @@ the most simple one: // entities/Product.php /** * @Entity @Table(name="products") - */ + **/ class Product { - /** @Id @Column(type="integer") @GeneratedValue */ + /** @Id @Column(type="integer") @GeneratedValue **/ protected $id; - /** @Column(type="string") */ + /** @Column(type="string") **/ protected $name; + + // .. (other code) } .. code-block:: xml @@ -509,40 +522,42 @@ We then go on specifying the definition of a Bug: // entities/Bug.php /** * @Entity @Table(name="bugs") - */ + **/ class Bug { /** * @Id @Column(type="integer") @GeneratedValue - */ + **/ protected $id; /** * @Column(type="string") - */ + **/ protected $description; /** * @Column(type="datetime") - */ + **/ protected $created; /** * @Column(type="string") - */ + **/ protected $status; /** * @ManyToOne(targetEntity="User", inversedBy="assignedBugs") - */ + **/ protected $engineer; /** * @ManyToOne(targetEntity="User", inversedBy="reportedBugs") - */ + **/ protected $reporter; /** * @ManyToMany(targetEntity="Product") - */ + **/ protected $products; + + // ... (other code) } .. code-block:: xml @@ -637,33 +652,36 @@ The last missing definition is that of the User entity: // entities/User.php /** * @Entity @Table(name="users") - */ + **/ class User { /** * @Id @GeneratedValue @Column(type="integer") * @var int - */ + **/ protected $id; /** * @Column(type="string") * @var string - */ + **/ protected $name; /** * @OneToMany(targetEntity="Bug", mappedBy="reporter") * @var Bug[] - */ + **/ protected $reportedBugs = null; /** * @OneToMany(targetEntity="Bug", mappedBy="engineer") * @var Bug[] - */ + **/ protected $assignedBugs = null; + // .. (other code) + } + .. code-block:: xml @@ -1161,7 +1179,7 @@ looks like: /** * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. - */ + **/ class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy { // .. lazy load code here @@ -1411,7 +1429,7 @@ we have to adjust the metadata slightly. /** * @Entity(repositoryClass="BugRepository") * @Table(name="bugs") - */ + **/ class Bug { //... From 8a39a6605740bd8b8c33afb7edfb2895517a5b87 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 29 Jan 2012 20:03:40 +0100 Subject: [PATCH 324/430] Some fixes in the docs --- en/reference/configuration.rst | 4 ++-- en/reference/introduction.rst | 2 +- en/toc.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index 57534ab32..6a2a192b5 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -496,7 +496,7 @@ implementations. Default Repository (***OPTIONAL***) -------------------------- +----------------------------------- Specifies the FQCN of a subclass of the EntityRepository. That will be available for all entities without a custom repository class. @@ -508,4 +508,4 @@ That will be available for all entities without a custom repository class. $config->getDefaultRepositoryClassName(); The default value is ``Doctrine\ORM\EntityRepository``. -Any repository class must be a subclass of EntityRepository otherwise you got an ORMException \ No newline at end of file +Any repository class must be a subclass of EntityRepository otherwise you got an ORMException diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst index fcc8c4917..84c2b5f04 100644 --- a/en/reference/introduction.rst +++ b/en/reference/introduction.rst @@ -398,6 +398,6 @@ Can you **find** the easier way?). Instead of reading through the reference manual we also recommend to look at the tutorials: -:doc:`Getting Started Tutorial <../tutorials/getting-started-xml-edition>` +:doc:`Getting Started Tutorial <../tutorials/getting-started>` diff --git a/en/toc.rst b/en/toc.rst index f4f407735..7ab8805b0 100644 --- a/en/toc.rst +++ b/en/toc.rst @@ -7,7 +7,7 @@ Tutorials .. toctree:: :maxdepth: 1 - tutorials/getting-started-xml-edition + tutorials/getting-started tutorials/working-with-indexed-associations tutorials/extra-lazy-associations tutorials/composite-primary-keys From 02e7dcdc87ce5037ef61a0e9261df6369e773c15 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 29 Jan 2012 20:06:07 +0100 Subject: [PATCH 325/430] Fix conf.py for 2.2 release --- en/_theme | 2 +- en/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/en/_theme b/en/_theme index a32137e07..65a4cd407 160000 --- a/en/_theme +++ b/en/_theme @@ -1 +1 @@ -Subproject commit a32137e076d0cfbcec79c4f7fd04ab55fbbdc8e6 +Subproject commit 65a4cd4073a46176dfefe031d75724b0e4d80269 diff --git a/en/conf.py b/en/conf.py index fbb344b04..7e785aeb4 100644 --- a/en/conf.py +++ b/en/conf.py @@ -38,14 +38,14 @@ master_doc = 'index' # General information about the project. project = u'Doctrine 2 ORM' -copyright = u'2010-11, Doctrine Project Team' +copyright = u'2010-12, Doctrine Project Team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '2.1' +version = '2.2' # The full version, including alpha/beta/rc tags. release = '2.1' From 781f83f6487aaa4d335c6d61f26055d842426055 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 29 Jan 2012 20:45:17 +0100 Subject: [PATCH 326/430] Update unitofwork section --- en/_theme | 2 +- en/reference/unitofwork.rst | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/en/_theme b/en/_theme index 65a4cd407..1ce5c0ebd 160000 --- a/en/_theme +++ b/en/_theme @@ -1 +1 @@ -Subproject commit 65a4cd4073a46176dfefe031d75724b0e4d80269 +Subproject commit 1ce5c0ebd6ed7e05a083d85bce8be34badaf12db diff --git a/en/reference/unitofwork.rst b/en/reference/unitofwork.rst index 856f30a6e..abe7543e7 100644 --- a/en/reference/unitofwork.rst +++ b/en/reference/unitofwork.rst @@ -133,3 +133,69 @@ optimize the performance of the Flush Operation: - Use :doc:`Change Tracking Policies ` to use more explicit strategies of notifying the UnitOfWork what objects/properties changed. + + +Query Internals +--------------- + +The different ORM Layers +------------------------ + +Doctrine ships with a set of layers with different responsibilities. This +section gives a short explanation of each layer. + +Hydration +~~~~~~~~~ + +Responsible for creating a final result from a raw database statement and a +result-set mapping object. The developer can choose which kind of result he +wishes to be hydrated. Default result-types include: + +- SQL to Entities +- SQL to structured Arrays +- SQL to simple scalar result arrays +- SQL to a single result variable + +Hydration to entities and arrays is one of most complex parts of Doctrine +algorithm-wise. It can built results with for example: + +- Single table selects +- Joins with n:1 or 1:n cardinality, grouping belonging to the same parent. +- Mixed results of objects and scalar values +- Hydration of results by a given scalar value as key. + +Persisters +~~~~~~~~~~ + +tbr + +UnitOfWork +~~~~~~~~~~ + +tbr + +ResultSetMapping +~~~~~~~~~~~~~~~~ + +tbr + +DQL Parser +~~~~~~~~~~ + +tbr + +SQLWalker +~~~~~~~~~ + +tbr + +EntityManager +~~~~~~~~~~~~~ + +tbr + +ClassMetadataFactory +~~~~~~~~~~~~~~~~~~~~ + +tbr + From cfd5eefa7a8a286754b745b8e09dfa1651968593 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 29 Jan 2012 20:54:51 +0100 Subject: [PATCH 327/430] Update theme --- en/_theme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/_theme b/en/_theme index 1ce5c0ebd..adf4de994 160000 --- a/en/_theme +++ b/en/_theme @@ -1 +1 @@ -Subproject commit 1ce5c0ebd6ed7e05a083d85bce8be34badaf12db +Subproject commit adf4de9945cbf27039c572fa85ffe596dd05793a From 9bd51f20628c0b68db6051686b31dd1f29b2c803 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 29 Jan 2012 20:56:48 +0100 Subject: [PATCH 328/430] Update theme --- en/_theme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/_theme b/en/_theme index adf4de994..08ef5235a 160000 --- a/en/_theme +++ b/en/_theme @@ -1 +1 @@ -Subproject commit adf4de9945cbf27039c572fa85ffe596dd05793a +Subproject commit 08ef5235a2416611c205732627ced0066cea3c08 From c613351f39e4607da977619727dae6744604d258 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 29 Jan 2012 22:40:38 +0100 Subject: [PATCH 329/430] Added 10 quick steps section --- en/index.rst | 3 +- en/toc.rst | 1 + en/tutorials/in-ten-quick-steps.rst | 300 ++++++++++++++++++++++++++++ 3 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 en/tutorials/in-ten-quick-steps.rst diff --git a/en/index.rst b/en/index.rst index 034432add..8be6565fb 100644 --- a/en/index.rst +++ b/en/index.rst @@ -26,7 +26,8 @@ Getting Started --------------- * **Tutorial**: - :doc:`Getting Started ` + :doc:`Getting Started ` | + :doc:`In 10 quick steps ` * **Reference**: :doc:`Introduction ` | diff --git a/en/toc.rst b/en/toc.rst index 7ab8805b0..8178f8c05 100644 --- a/en/toc.rst +++ b/en/toc.rst @@ -12,6 +12,7 @@ Tutorials tutorials/extra-lazy-associations tutorials/composite-primary-keys tutorials/ordered-associations + tutorials/in-ten-quick-steps Reference Guide --------------- diff --git a/en/tutorials/in-ten-quick-steps.rst b/en/tutorials/in-ten-quick-steps.rst new file mode 100644 index 000000000..754887d66 --- /dev/null +++ b/en/tutorials/in-ten-quick-steps.rst @@ -0,0 +1,300 @@ +Doctrine explained in 10 quick steps +==================================== + +You can follow this tutorial step by step yourself and end up with a simple +Doctrine application. It assumed that you installed Doctrine via PEAR. To work +with another setup just take a look into the :doc:`Installation help +<../reference/introduction>`. + +1. Allows you to map PHP Objects to database tables +--------------------------------------------------- + +.. code-block:: php + + CREATE TABLE Post (id INT AUTO_INCREMENT PRIMARY KEY, title + VARCHAR(255), body TEXT); + + mysql> DESCRIBE Post; + +-------+--------------+------+-----+---------+----------------+ + | Field | Type | Null | Key | Default | Extra | + +-------+--------------+------+-----+---------+----------------+ + | id | int(11) | NO | PRI | NULL | auto_increment | + | title | varchar(255) | YES | | NULL | | + | body | text | YES | | NULL | | + +-------+--------------+------+-----+---------+----------------+ + +.. tip:: + + Objects mapped with Doctrine are called Entities. They don't need to extend + a base class and even allow constructors with required parameters. + + You are responsible for implementing getters, setters and constructors of + your entities yourself. This gives you full freedom to design your business + objects as you wish. + +2. Using Annotations, XML or YAML for Metadata Mapping +------------------------------------------------------ + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + +3. Object References map to Foreign keys +---------------------------------------- + +.. code-block:: php + + author = $user; + } + } + + /** @Entity **/ + class User + { + /** @Id @GeneratedValue @Column(type="integer") **/ + protected $id; + /** @Column(type="string") **/ + protected $name; + } + + $user = new User(); + $post = new Post($user); + + +:: + + mysql> CREATE TABLE Post (id INT AUTO_INCREMENT PRIMARY KEY, title + VARCHAR(255), body TEXT, author_id INT); + + mysql> CREATE TABLE User (id INT AUTO_INCREMENT PRIMARY KEY, name + VARCHAR(255)); + + mysql> ALTER TABLE Post ADD FOREIGN KEY (author_id) REFERENCES User (id); + + mysql> DESCRIBE Post; + +-----------+--------------+------+-----+---------+----------------+ + | Field | Type | Null | Key | Default | Extra | + +-----------+--------------+------+-----+---------+----------------+ + | id | int(11) | NO | PRI | NULL | auto_increment | + | title | varchar(255) | YES | | NULL | | + | body | text | YES | | NULL | | + | author_id | int(11) | YES | MUL | NULL | | + +-----------+--------------+------+-----+---------+----------------+ + +.. tip:: + + This means you don't have to mess with foreign keys yourself, just use + references to connect objects with each other and let Doctrine handle the + rest. + +4. Collections handle sets of objects references +------------------------------------------------ + +.. code-block:: php + + author = $author; + $this->posts = new ArrayCollection(); + } + + public function addComment($text) + { + $this->comments[] = $new Comment($this, $text); + } + } + + /** @Entity **/ + class Comment + { + /** @Id @GeneratedValue @Column(type="integer") **/ + protected $id; + /** @Column(type="text") **/ + protected $comment; + /** + * @ManyToOne(targetEntity="Post", inversedBy="comments") + **/ + protected $post; + + public function __construct(Post $post, $text) + { + $this->post = $post; + $this->comment = $text; + } + } + + $post->addComment("First.."); + $post->addComment("Second!"); + +5. Easy to setup for the default configuration case +--------------------------------------------------- + +.. code-block:: php + + register(); + + $dbParams = array( + 'driver' => 'pdo_mysql', + 'user' => 'root', + 'password' => '', + 'dbname' => 'tests' + ); + $path = 'path/to/entities'; + $config = Setup::createAnnotationMetadataConfiguration($path, true); + $entityManager = EntityManager::create($dbParams, $config); + + +6. The EntityManager needs to know about your new objects +--------------------------------------------------------- + +.. code-block:: php + + persist($user); + $entityManager->persist($post); + +.. warning:: + + This does not lead to INSERT/UPDATE statements yet. You need to call + EntityManager#flush() + + +7. EntityManager#flush() batches SQL INSERT/UPDATE/DELETE statements +-------------------------------------------------------------------- + +.. code-block:: php + + flush(); + +.. tip:: + + Batching all write-operations against the database allows Doctrine to wrap all + statements into a single transaction and benefit from other performance + optimizations such as prepared statement re-use. + +8. You can fetch objects from the database through the EntityManager +-------------------------------------------------------------------- + +.. code-block:: php + + find("Post", $id); + +9. ..or through a Repository +---------------------------- + +.. code-block:: php + + getRepository("Author"); + $author = $authorRepository->find($authorId); + + $postRepository = $entityManager->getRepository("Post"); + $post = $postRepository->findOneBy(array("title" => "Hello World!")); + + $posts = $repository->findBy( + array("author" => $author), + array("title" => "ASC") + ); + + +10. Or complex finder scenarios with the Doctrine Query Language +---------------------------------------------------------------- + +.. code-block:: php + + createQuery($dql)->getResult(); From d378b5aec8d440b97e8914ae34fea53e3c9b3e08 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 30 Jan 2012 00:56:14 +0100 Subject: [PATCH 330/430] Update theme --- en/_theme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/_theme b/en/_theme index 08ef5235a..bd047f830 160000 --- a/en/_theme +++ b/en/_theme @@ -1 +1 @@ -Subproject commit 08ef5235a2416611c205732627ced0066cea3c08 +Subproject commit bd047f830839db82678dfed84616b1dc623d3481 From 7c8a9c0f9a2439bc0559ac5d7941327b057a6a06 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 18 Jan 2012 20:50:20 +0100 Subject: [PATCH 331/430] Initial draft for filter documentation --- en/reference/filters.rst | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 en/reference/filters.rst diff --git a/en/reference/filters.rst b/en/reference/filters.rst new file mode 100644 index 000000000..487c52c8d --- /dev/null +++ b/en/reference/filters.rst @@ -0,0 +1,68 @@ +Filters +======= + +Doctrine 2.2 features a filter system that allows the developer to add SQL to +the conditional clauses of queries, regardless the place where the SQL is +generated (e.g. from a DQL query, or by loading associated entities). To give +you an idea on how it works, this chapter starts with an example of a filter. + + +Example filter class +-------------------- +Throughout this document the example ``MyLocaleFilter`` class will be used to +illustrate how the filter feature works. A filter class should extend the base +``Doctrine\ORM\Query\Filter\SQLFilter`` class and implement the ``addFilterConstraint`` +method. The method receives the ClassMetadata of the filtered entity and the +table alias of the table of the entity. + +.. code-block:: php + reflClass->getInterfaceNames())) { + return ""; + } + + return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); + } + } + + + + +Configuration +------------- +Filter classes are added to the configuration as following: + +.. code-block:: php + addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter"); + + +The ``addFilter()`` method takes a name for the filter and the name of the +class responsible for the actual filtering. + + +Enabling Filters and Setting Parameters +--------------------------------------------------- +Filters can be enabled via the ``FilterCollection`` that is available in the +``EntityManager``. The ``enable`` function will return the filter object. This +object can be used to set certain parameters for the filter. + +.. code-block:: php + getFilters()->enable("locale"); + $filter->setParameter('locale', 'en'); + + +Disabling Filters +----------------- +.. code-block:: php + $filter = $em->getFilters()->disable("locale"); From cfbfac6a51b21fdd287a5df47eee6b1847efea75 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 31 Jan 2012 08:49:16 +0100 Subject: [PATCH 332/430] General information on how the filter work etc + warning on the enabling/disabling part --- en/reference/filters.rst | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/en/reference/filters.rst b/en/reference/filters.rst index 487c52c8d..d925fae54 100644 --- a/en/reference/filters.rst +++ b/en/reference/filters.rst @@ -3,17 +3,32 @@ Filters Doctrine 2.2 features a filter system that allows the developer to add SQL to the conditional clauses of queries, regardless the place where the SQL is -generated (e.g. from a DQL query, or by loading associated entities). To give -you an idea on how it works, this chapter starts with an example of a filter. +generated (e.g. from a DQL query, or by loading associated entities). +The filter functionality works on SQL level. Whether an SQL query is generated +in a Persister, during lazy loading, in extra lazy collections or from DQL. +Each time the system iterates over all the enabled filters, adding a new SQL +part as a filter returns. + +By adding SQL to the conditional clauses of queries, the filter system filters +out rows belonging to the entities at the level of the SQL result set. This +means that the filtered entities are never hydrated (which can be expensive). + +To give you an idea on how it works, the next section contains an example of a +filter. Example filter class -------------------- Throughout this document the example ``MyLocaleFilter`` class will be used to illustrate how the filter feature works. A filter class should extend the base ``Doctrine\ORM\Query\Filter\SQLFilter`` class and implement the ``addFilterConstraint`` -method. The method receives the ClassMetadata of the filtered entity and the -table alias of the table of the entity. +method. The method receives the ``ClassMetadata`` of the filtered entity and the +table alias of the SQL table of the entity. + +Parameters for the query should be set on the filter object by +``SQLFilter::setParameter()``. Only parameters set via this function used in +the filters. The ``SQLFilter::getParameter()`` function takes care of the +proper quoting of parameters. .. code-block:: php getParameter('locale'); + return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); // Automatically quoted } } - - Configuration ------------- Filter classes are added to the configuration as following: @@ -61,6 +74,11 @@ object can be used to set certain parameters for the filter. $filter = $em->getFilters()->enable("locale"); $filter->setParameter('locale', 'en'); +.. warning:: + Disabling and enabling filters does not have effect on objects that you + already have. If you want to reload an object after you disabled, enabled + or changed a filter, then you should clear the EM and re-fetch the object + so the appropriate SQL will be executed. Disabling Filters ----------------- From ff9e7ef64b5f18941f663ded9aa99bb862455369 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 31 Jan 2012 15:38:27 +0100 Subject: [PATCH 333/430] Processed comments of @Ocramius --- en/reference/filters.rst | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/en/reference/filters.rst b/en/reference/filters.rst index d925fae54..6c663c465 100644 --- a/en/reference/filters.rst +++ b/en/reference/filters.rst @@ -14,20 +14,18 @@ By adding SQL to the conditional clauses of queries, the filter system filters out rows belonging to the entities at the level of the SQL result set. This means that the filtered entities are never hydrated (which can be expensive). -To give you an idea on how it works, the next section contains an example of a -filter. Example filter class -------------------- Throughout this document the example ``MyLocaleFilter`` class will be used to -illustrate how the filter feature works. A filter class should extend the base +illustrate how the filter feature works. A filter class must extend the base ``Doctrine\ORM\Query\Filter\SQLFilter`` class and implement the ``addFilterConstraint`` method. The method receives the ``ClassMetadata`` of the filtered entity and the table alias of the SQL table of the entity. Parameters for the query should be set on the filter object by -``SQLFilter::setParameter()``. Only parameters set via this function used in -the filters. The ``SQLFilter::getParameter()`` function takes care of the +``SQLFilter#setParameter()``. Only parameters set via this function used in +the filters. The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters. .. code-block:: php @@ -45,7 +43,7 @@ proper quoting of parameters. return ""; } - return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); // Automatically quoted + return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); // getParameter applies quoting automatically } } @@ -59,28 +57,27 @@ Filter classes are added to the configuration as following: $config->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter"); -The ``addFilter()`` method takes a name for the filter and the name of the +The ``Configuration#addFilter()`` method takes a name for the filter and the name of the class responsible for the actual filtering. -Enabling Filters and Setting Parameters +Disabling/Enabling Filters and Setting Parameters --------------------------------------------------- -Filters can be enabled via the ``FilterCollection`` that is available in the -``EntityManager``. The ``enable`` function will return the filter object. This -object can be used to set certain parameters for the filter. +Filters can be disabled and enabled via the ``FilterCollection`` which is +stored in the ``EntityManager``. The ``FilterCollection#enable($name)`` method +will retrieve the filter object. You can set the filter parameters on that +object. .. code-block:: php getFilters()->enable("locale"); $filter->setParameter('locale', 'en'); -.. warning:: - Disabling and enabling filters does not have effect on objects that you - already have. If you want to reload an object after you disabled, enabled - or changed a filter, then you should clear the EM and re-fetch the object - so the appropriate SQL will be executed. - -Disabling Filters ------------------ -.. code-block:: php + // Disable it $filter = $em->getFilters()->disable("locale"); + +.. warning:: + Disabling and enabling filters has no effect on managed entities. If you + want to refresh or reload an object after having modified a filter or the + FilterCollection, then you should clear the EntityManager and re-fetch your + entities, having the new rules for filtering applied. From 6f56dbe39552d4133fd032833a370ae979139927 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 31 Jan 2012 15:58:31 +0100 Subject: [PATCH 334/430] Added filters to the index --- en/index.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/en/index.rst b/en/index.rst index 8be6565fb..c58b7528c 100644 --- a/en/index.rst +++ b/en/index.rst @@ -80,6 +80,9 @@ Advanced Topics * **Database Integration**: :doc:`Transactions and Concurrency ` +* **Filtering entities**: + :doc:`Filters ` + * **Performance**: :doc:`Improving Performance ` | :doc:`Caching ` | From bd30a04d0d2dec00ea93cf38a78f28fb9578111f Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 31 Jan 2012 22:24:36 +0100 Subject: [PATCH 335/430] Fix code blocks --- en/reference/filters.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/en/reference/filters.rst b/en/reference/filters.rst index 6c663c465..ef8b36e52 100644 --- a/en/reference/filters.rst +++ b/en/reference/filters.rst @@ -29,6 +29,7 @@ the filters. The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters. .. code-block:: php + addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter"); @@ -69,6 +71,7 @@ will retrieve the filter object. You can set the filter parameters on that object. .. code-block:: php + getFilters()->enable("locale"); $filter->setParameter('locale', 'en'); From 984c8f7db12a044c0b3e285e1b0c73587d6a1a2a Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 5 Feb 2012 22:28:30 +0100 Subject: [PATCH 336/430] Fix versions --- en/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/conf.py b/en/conf.py index 7e785aeb4..4d6ec244e 100644 --- a/en/conf.py +++ b/en/conf.py @@ -47,7 +47,7 @@ copyright = u'2010-12, Doctrine Project Team' # The short X.Y version. version = '2.2' # The full version, including alpha/beta/rc tags. -release = '2.1' +release = '2.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 6fa7580d10326ab8f862d1f7edf18037fe52cc13 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 5 Feb 2012 22:46:43 +0100 Subject: [PATCH 337/430] Add tutorial about Pagination --- en/index.rst | 9 +++++---- en/tutorials/pagination.rst | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 en/tutorials/pagination.rst diff --git a/en/index.rst b/en/index.rst index c58b7528c..16800fe21 100644 --- a/en/index.rst +++ b/en/index.rst @@ -25,7 +25,7 @@ of contents `. Getting Started --------------- -* **Tutorial**: +* **Tutorial**: :doc:`Getting Started ` | :doc:`In 10 quick steps ` @@ -59,20 +59,21 @@ Working with Objects :doc:`Assocations ` | :doc:`Events ` -* **Querying Objects**: +* **Query Reference**: :doc:`Doctrine Query Language (DQL) ` | :doc:`QueryBuilder ` | :doc:`Native SQL Queries ` * **UnitOfWork dissected**: :doc:`Doctrine Internals explained ` | - :doc:`Owning and Inverse Side Associations ` + :doc:`Owning and Inverse Side Associations ` * **Tutorials**: :doc:`Indexed associations ` | :doc:`Extra Lazy Assocations ` | :doc:`Composite Primary Keys ` | - :doc:`Ordered associations ` + :doc:`Ordered associations ` | + :doc:`Pagination ` Advanced Topics --------------- diff --git a/en/tutorials/pagination.rst b/en/tutorials/pagination.rst new file mode 100644 index 000000000..ef04a8d89 --- /dev/null +++ b/en/tutorials/pagination.rst @@ -0,0 +1,40 @@ +Pagination +========== + +Starting with version 2.2 Doctrine ships with a Paginator for DQL queries. It +has a very simple API and implements the SPL interfaces ``Countable`` and +``IteratorAggregate``. + +Paginating Doctrine queries is not as simple as you might think in the +beginning. If you have complex fetch-join scenarios with one-to-many or +many-to-many associations using the "default" LIMIT functionality of database +vendors is not sufficient to get the correct results. + +By default the pagination extension does the following steps to compute the +correct result: + +1. Perform a Count query using `DISTINCT` keyword. +2. Perform a Limit Subquery with `DISTINCT` to find all ids of the entity in from on the current page. +3. Perform a WHERE IN query to get all results for the current page. + +This behavior is only necessary if you actually fetch join a to-many +collection. You can disable this behavior by setting the +``$fetchJoinCollection`` flag of. We hope to automate the detection for this in +the future. + +.. code-block:: php + + createQuery($dql) + ->setFirstResult(0) + ->setMaxResults(100); + + $paginator = new Paginator($query, $fetchJoin = true); + + $c = count($paginator); + foreach ($paginator as $post) { + echo $post->getHeadline() . "\n"; + } From 3def8484223a03c8cdc9a28cb91b991c4617e070 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 5 Feb 2012 22:51:42 +0100 Subject: [PATCH 338/430] Fix enumeration --- en/tutorials/pagination.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/en/tutorials/pagination.rst b/en/tutorials/pagination.rst index ef04a8d89..bb402fc87 100644 --- a/en/tutorials/pagination.rst +++ b/en/tutorials/pagination.rst @@ -13,9 +13,9 @@ vendors is not sufficient to get the correct results. By default the pagination extension does the following steps to compute the correct result: -1. Perform a Count query using `DISTINCT` keyword. -2. Perform a Limit Subquery with `DISTINCT` to find all ids of the entity in from on the current page. -3. Perform a WHERE IN query to get all results for the current page. +- Perform a Count query using `DISTINCT` keyword. +- Perform a Limit Subquery with `DISTINCT` to find all ids of the entity in from on the current page. +- Perform a WHERE IN query to get all results for the current page. This behavior is only necessary if you actually fetch join a to-many collection. You can disable this behavior by setting the From ea95bd57ef7a52b526bd62ac6db80fcdc719a068 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 5 Feb 2012 22:54:28 +0100 Subject: [PATCH 339/430] Rework a bit --- en/tutorials/pagination.rst | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/en/tutorials/pagination.rst b/en/tutorials/pagination.rst index bb402fc87..63e50e6c5 100644 --- a/en/tutorials/pagination.rst +++ b/en/tutorials/pagination.rst @@ -5,23 +5,6 @@ Starting with version 2.2 Doctrine ships with a Paginator for DQL queries. It has a very simple API and implements the SPL interfaces ``Countable`` and ``IteratorAggregate``. -Paginating Doctrine queries is not as simple as you might think in the -beginning. If you have complex fetch-join scenarios with one-to-many or -many-to-many associations using the "default" LIMIT functionality of database -vendors is not sufficient to get the correct results. - -By default the pagination extension does the following steps to compute the -correct result: - -- Perform a Count query using `DISTINCT` keyword. -- Perform a Limit Subquery with `DISTINCT` to find all ids of the entity in from on the current page. -- Perform a WHERE IN query to get all results for the current page. - -This behavior is only necessary if you actually fetch join a to-many -collection. You can disable this behavior by setting the -``$fetchJoinCollection`` flag of. We hope to automate the detection for this in -the future. - .. code-block:: php getHeadline() . "\n"; } + +Paginating Doctrine queries is not as simple as you might think in the +beginning. If you have complex fetch-join scenarios with one-to-many or +many-to-many associations using the "default" LIMIT functionality of database +vendors is not sufficient to get the correct results. + +By default the pagination extension does the following steps to compute the +correct result: + +- Perform a Count query using `DISTINCT` keyword. +- Perform a Limit Subquery with `DISTINCT` to find all ids of the entity in from on the current page. +- Perform a WHERE IN query to get all results for the current page. + +This behavior is only necessary if you actually fetch join a to-many +collection. You can disable this behavior by setting the +``$fetchJoinCollection`` flag of, in that case only 2 instead of the 3 queries +described are executed. We hope to automate the detection for this in +the future. From 138b67db86414510b6aa390ee7126d664eea5a21 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 13 Feb 2012 11:18:35 +0100 Subject: [PATCH 340/430] Fix wrong use of private in mapped superclasses, only protected is supported here. --- en/reference/inheritance-mapping.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/en/reference/inheritance-mapping.rst b/en/reference/inheritance-mapping.rst index 48ff855d8..9b44630a9 100644 --- a/en/reference/inheritance-mapping.rst +++ b/en/reference/inheritance-mapping.rst @@ -35,14 +35,14 @@ Example: class MappedSuperclassBase { /** @Column(type="integer") */ - private $mapped1; + protected $mapped1; /** @Column(type="string") */ - private $mapped2; + protected $mapped2; /** * @OneToOne(targetEntity="MappedSuperclassRelated1") * @JoinColumn(name="related1_id", referencedColumnName="id") */ - private $mappedRelated1; + protected $mappedRelated1; // ... more fields and methods } From 86010bdb0d528ee03d83209988984c5552ff4ba0 Mon Sep 17 00:00:00 2001 From: meze Date: Sun, 19 Feb 2012 03:01:29 +0400 Subject: [PATCH 341/430] Fix a typo in a code example (getResults instead of getResult) --- en/reference/dql-doctrine-query-language.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index 8350c9a03..b5c01d776 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -208,7 +208,7 @@ Retrieve the Username and Name of a CmsUser: createQuery('SELECT u.username, u.name FROM CmsUser u'); - $users = $query->getResults(); // array of CmsUser username and name values + $users = $query->getResult(); // array of CmsUser username and name values echo $users[0]['username']; Retrieve a ForumUser and his single associated entity: From 35ded56fdde3eb67649f4bdaae2bbf37629deef6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 20 Feb 2012 10:48:57 +0100 Subject: [PATCH 342/430] Add note about debugging DQL queries. --- en/reference/faq.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/en/reference/faq.rst b/en/reference/faq.rst index 9c5b1faad..2cc1c24c1 100644 --- a/en/reference/faq.rst +++ b/en/reference/faq.rst @@ -204,3 +204,21 @@ No, it is not supported to sort by function in DQL. If you need this functionali use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow starting with 1000 rows. +A Query fails, how can I debug it? +---------------------------------- + +First, if you are using the QueryBuilder you can use +``$queryBuilder->getDQL()`` to get the DQL string of this query. The +corresponding SQL you can get from the Query instance by calling +``$query->getSQL()``. + +.. code-block:: php + + createQuery($dql); + var_dump($query->getSQL()); + + $qb = $entityManager->createQueryBuilder(); + $qb->select('u')->from('User', 'u'); + var_dump($qb->getDQL()); From e8fbafd1547d556d763031924c6420f18f919075 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sun, 26 Feb 2012 11:40:13 -0500 Subject: [PATCH 343/430] Fixed OneToMany bidirectional association mapping in Annotations and also included the YAML missing one. --- en/reference/association-mapping.rst | 78 +++++++++++++++++----------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst index 1622be013..df456b201 100644 --- a/en/reference/association-mapping.rst +++ b/en/reference/association-mapping.rst @@ -53,7 +53,7 @@ the ``Product`` so it is unidirectional. } .. code-block:: xml - + @@ -98,7 +98,7 @@ Here is a one-to-one relationship between a ``Customer`` and a ``Cart``. The ``Cart`` has a reference back to the ``Customer`` so it is bidirectional. -.. configuration-block:: +.. configuration-block:: .. code-block:: php @@ -193,13 +193,13 @@ below. class Student { // ... - + /** * @OneToOne(targetEntity="Student") * @JoinColumn(name="mentor_id", referencedColumnName="id") **/ private $mentor; - + // ... } @@ -307,19 +307,19 @@ Generates the following MySQL Schema: id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; - + CREATE TABLE users_phonenumbers ( user_id INT NOT NULL, phonenumber_id INT NOT NULL, UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id), PRIMARY KEY(user_id, phonenumber_id) ) ENGINE = InnoDB; - + CREATE TABLE Phonenumber ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; - + ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id); ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id); @@ -385,12 +385,12 @@ Generated MySQL Schema: address_id INT DEFAULT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; - + CREATE TABLE Address ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; - + ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id); One-To-Many, Bidirectional @@ -400,7 +400,7 @@ Bidirectional one-to-many associations are very common. The following code shows an example with a Product and a Feature class: -.. configuration-block:: +.. configuration-block:: .. code-block:: php @@ -432,7 +432,7 @@ class: // ... } - .. code-block:: xml + .. code-block:: xml @@ -445,6 +445,24 @@ class: + .. code-block:: yaml + + Product: + type: entity + oneToMany: + features: + targetEntity: Feature + mappedBy: product + Feature: + type: entity + manyToOne: + product: + targetEntity: Product + inversedBy: features + joinColumn: + name: product_id + referencedColumnName: id + Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. @@ -628,7 +646,7 @@ Generated MySQL Schema: ALTER TABLE users_groups ADD FOREIGN KEY (group_id) REFERENCES Group(id); .. note:: - + Why are many-to-many associations less common? Because frequently you want to associate additional attributes with an association, in which case you introduce an association class. @@ -754,18 +772,18 @@ understandable: class Article { private $tags; - + public function addTag(Tag $tag) { $tag->addArticle($this); // synchronously updating inverse side $this->tags[] = $tag; } } - + class Tag { private $articles; - + public function addArticle(Article $article) { $this->articles[] = $article; @@ -798,12 +816,12 @@ field named ``$friendsWithMe`` and ``$myFriends``. class User { // ... - + /** * @ManyToMany(targetEntity="User", mappedBy="myFriends") **/ private $friendsWithMe; - + /** * @ManyToMany(targetEntity="User", inversedBy="friendsWithMe") * @JoinTable(name="friends", @@ -812,12 +830,12 @@ field named ``$friendsWithMe`` and ``$myFriends``. * ) **/ private $myFriends; - + public function __construct() { $this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection(); $this->myFriends = new \Doctrine\Common\Collections\ArrayCollection(); } - + // ... } @@ -864,7 +882,7 @@ As an example, consider this mapping: private $shipping; .. code-block:: xml - + @@ -894,7 +912,7 @@ mapping: private $shipping; .. code-block:: xml - + @@ -931,7 +949,7 @@ similar defaults. As an example, consider this mapping: } .. code-block:: xml - + @@ -969,7 +987,7 @@ mapping: } .. code-block:: xml - + @@ -999,7 +1017,7 @@ mapping: referencedColumnName: id inverseJoinColumns: Group_id: - referencedColumnName: id + referencedColumnName: id In that case, the name of the join table defaults to a combination of the simple, unqualified class names of the participating @@ -1068,7 +1086,7 @@ contains a collection of groups: { /** @ManyToMany(targetEntity="Group") **/ private $groups; - + public function getGroups() { return $this->groups; @@ -1088,18 +1106,18 @@ empty ``ArrayCollection`` in your entities constructor: groups = new ArrayCollection(); } - + public function getGroups() { return $this->groups; @@ -1136,10 +1154,10 @@ Or you can trigger the validation manually: validateMapping(); - + if (count($errors) > 0) { // Lots of errors! echo implode("\n\n", $errors); From 579774f505341d03789c78e717f2db2c55f7baad Mon Sep 17 00:00:00 2001 From: Augusto Pascutti Date: Mon, 27 Feb 2012 14:55:52 -0300 Subject: [PATCH 344/430] Fixed typo "Assocations" --- en/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/index.rst b/en/index.rst index 16800fe21..a02f80f55 100644 --- a/en/index.rst +++ b/en/index.rst @@ -56,7 +56,7 @@ Working with Objects * **Basic Reference**: :doc:`Entities ` | - :doc:`Assocations ` | + :doc:`Associations ` | :doc:`Events ` * **Query Reference**: From 01381fae1ff3d4944086c7cfe46721925bf6ca15 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 14 Mar 2012 20:11:17 +0100 Subject: [PATCH 345/430] [DDC-1698] Autoloading proxies --- en/reference/configuration.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index 6a2a192b5..1724886cb 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -471,6 +471,29 @@ proxy classes like so: $ ./doctrine orm:generate-proxies +Autoloading Proxies +------------------- + +When you deserialize proxy objects from the session or any other storage +it is necessary to have an autoloading mechanism in place for these classes. +For implementation reasons Proxy class names are not PSR-0 compliant. This +means that you have to register a special autoloader for these classes: + +.. code-block:: php + + addDriver($xmlDriver, 'Doctrine\Tests\Models\Company'); $chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping'); From e168b4e543ce1ae324ee9cb3a34909900c7cbfc9 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 10 Apr 2012 23:16:51 +0200 Subject: [PATCH 346/430] More prominent note about ResultSetMappingBuilder in native-sql chapter --- en/reference/native-sql.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/en/reference/native-sql.rst b/en/reference/native-sql.rst index 4afe2e2f2..b1d0740c2 100644 --- a/en/reference/native-sql.rst +++ b/en/reference/native-sql.rst @@ -9,6 +9,10 @@ of the database result should be mapped by Doctrine in terms of the object graph. This allows you to map arbitrary SQL code to objects, such as highly vendor-optimized SQL or stored-procedures. +Because writing ``ResultSetMapping`` is not so simple, there is a convenience +wrapper around it called a ``ResultSetMappingBuilder``. The last section +of this chapter describes its usage. + .. note:: If you want to execute DELETE, UPDATE or INSERT statements @@ -377,13 +381,16 @@ in your sQL statement: addRootEntityFromClassMetadata('MyProject\User', 'u'); $rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' => 'address_id')); For entites with more columns the builder is very convenient to use. It extends the ``ResultSetMapping`` class and as such has all the functionality of it as well. Currently the ``ResultSetMappingBuilder`` does not support entities with inheritance. + From 429ac54a3424f0f75ef414260c4bc63d83270359 Mon Sep 17 00:00:00 2001 From: gedrox Date: Wed, 11 Apr 2012 10:02:42 +0300 Subject: [PATCH 347/430] [#DWEB-103] Fixed UTC timezone creation. Constant DateTimeZone::UTC is 1024, string name is required for the DateTimeZone constructor. --- en/cookbook/working-with-datetime.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/cookbook/working-with-datetime.rst b/en/cookbook/working-with-datetime.rst index a4d9af898..c8620dccf 100644 --- a/en/cookbook/working-with-datetime.rst +++ b/en/cookbook/working-with-datetime.rst @@ -98,7 +98,7 @@ the UTC time at the time of the booking and the timezone the event happend in. return $value->format($platform->getDateTimeFormatString(), - (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone(\DateTimeZone::UTC)) + (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC')) ); } @@ -111,7 +111,7 @@ the UTC time at the time of the booking and the timezone the event happend in. $val = \DateTime::createFromFormat( $platform->getDateTimeFormatString(), $value, - (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone(\DateTimeZone::UTC)) + (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC')) ); if (!$val) { throw ConversionException::conversionFailed($value, $this->getName()); From 301f4d034697e894be369c4d98b8b3cefe4326a3 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Thu, 12 Apr 2012 23:22:06 +0300 Subject: [PATCH 348/430] fix confusing typo in ordered collections --- en/tutorials/ordered-associations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/tutorials/ordered-associations.rst b/en/tutorials/ordered-associations.rst index 52b217308..23f438300 100644 --- a/en/tutorials/ordered-associations.rst +++ b/en/tutorials/ordered-associations.rst @@ -64,7 +64,7 @@ However the following: SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC -You can't reverse the order with an explicit DQL ORDER BY: +You can reverse the order with an explicit DQL ORDER BY: .. code-block:: sql From 3076e2a1f7828f80a70f5cce0a7d24a132fdd175 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Thu, 12 Apr 2012 22:39:39 -0300 Subject: [PATCH 349/430] docs for NamingStrategy --- en/index.rst | 3 + en/reference/namingstrategy.rst | 141 ++++++++++++++++++++++++++++++++ en/toc.rst | 3 + 3 files changed, 147 insertions(+) create mode 100644 en/reference/namingstrategy.rst diff --git a/en/index.rst b/en/index.rst index a02f80f55..3841e0654 100644 --- a/en/index.rst +++ b/en/index.rst @@ -84,6 +84,9 @@ Advanced Topics * **Filtering entities**: :doc:`Filters ` +* **Implementing a NamingStrategy**: + :doc:`NamingStrategy ` + * **Performance**: :doc:`Improving Performance ` | :doc:`Caching ` | diff --git a/en/reference/namingstrategy.rst b/en/reference/namingstrategy.rst new file mode 100644 index 000000000..9963fc798 --- /dev/null +++ b/en/reference/namingstrategy.rst @@ -0,0 +1,141 @@ +Implementing a NamingStrategy +============================== + +Using a naming strategy you can provide rules for automatically generating database identifiers, columns and tables names +when the table/column name is not given. +This feature helps reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``). + + +Configuring a naming strategy +----------------------------- +The default strategy used by Doctrine is quite minimal. + +By default the ``Doctrine\ORM\Mapping\DefaultNamingStrategy`` +uses the simple class name and the attributes names to generate tables and columns + +You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` : + +.. code-block:: php + + setNamingStrategy($namingStrategy); + +Underscore naming strategy +--------------------------- + +``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy that might be a useful if you want to use a underlying convention. + +.. code-block:: php + + setNamingStrategy(namingStrategy); + +Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER or some_entity_name using CASE_LOWER is given. + + +Naming strategy interface +------------------------- +The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify a "naming standard" for database tables and columns. + +.. code-block:: php + + referenceColumnName(); + } + public function joinTableName($sourceEntity, $targetEntity, $propertyName = null) + { + return strtolower($this->classToTableName($sourceEntity) . '_' . + $this->classToTableName($targetEntity)); + } + public function joinKeyColumnName($entityName, $referencedColumnName = null) + { + return strtolower($this->classToTableName($entityName) . '_' . + ($referencedColumnName ?: $this->referenceColumnName())); + } + } + +Configuring the namingstrategy is easy if. +Just set your naming strategy calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :. + +.. code-block:: php + + setNamingStrategy($namingStrategy); \ No newline at end of file diff --git a/en/toc.rst b/en/toc.rst index 8178f8c05..5cdbe90c7 100644 --- a/en/toc.rst +++ b/en/toc.rst @@ -50,6 +50,9 @@ Reference Guide reference/metadata-drivers reference/best-practices reference/limitations-and-known-issues + tutorials/pagination.rst + reference/filters.rst + reference/namingstrategy.rst Cookbook From 987834a2ddd3b746c9d63c92dd68f30cdc43a6db Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Fri, 13 Apr 2012 09:13:18 -0300 Subject: [PATCH 350/430] wrap lines --- en/reference/namingstrategy.rst | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/en/reference/namingstrategy.rst b/en/reference/namingstrategy.rst index 9963fc798..f1e856b58 100644 --- a/en/reference/namingstrategy.rst +++ b/en/reference/namingstrategy.rst @@ -1,9 +1,11 @@ Implementing a NamingStrategy ============================== -Using a naming strategy you can provide rules for automatically generating database identifiers, columns and tables names +Using a naming strategy you can provide rules for automatically generating +database identifiers, columns and tables names when the table/column name is not given. -This feature helps reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``). +This feature helps reduce the verbosity of the mapping document, +eliminating repetitive noise (eg: ``TABLE_``). Configuring a naming strategy @@ -24,20 +26,23 @@ You can specify a different strategy by calling ``Doctrine\ORM\Configuration#set Underscore naming strategy --------------------------- -``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy that might be a useful if you want to use a underlying convention. +``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy +that might be a useful if you want to use a underlying convention. .. code-block:: php setNamingStrategy(namingStrategy); + $configuration()->setNamingStrategy($namingStrategy); -Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER or some_entity_name using CASE_LOWER is given. +Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER +or some_entity_name using CASE_LOWER is given. Naming strategy interface ------------------------- -The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify a "naming standard" for database tables and columns. +The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify +a "naming standard" for database tables and columns. .. code-block:: php @@ -94,7 +99,9 @@ The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify a "n Implementing a naming strategy ------------------------------- -If you have database naming standards like all tables names should be prefixed by the application prefix, all column names should be upper case, you can easily achieve such standards by implementing a naming strategy. +If you have database naming standards like all tables names should be prefixed +by the application prefix, all column names should be upper case, +you can easily achieve such standards by implementing a naming strategy. You need to implements NamingStrategy first. Following is an example From 2007f1ab95f478447e3be3d2f2de9c2bfc627b3e Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 15 Apr 2012 16:57:44 -0300 Subject: [PATCH 351/430] annotations reference for named native query --- en/reference/annotations-reference.rst | 220 +++++++++++++++++++++++++ 1 file changed, 220 insertions(+) diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst index 1760ca731..0383831ef 100644 --- a/en/reference/annotations-reference.rst +++ b/en/reference/annotations-reference.rst @@ -8,10 +8,13 @@ Index ----- - :ref:`@Column ` +- :ref:`@ColumnResult ` - :ref:`@ChangeTrackingPolicy ` - :ref:`@DiscriminatorColumn ` - :ref:`@DiscriminatorMap ` - :ref:`@Entity ` +- :ref:`@EntityResult ` +- :ref:`@FieldResult ` - :ref:`@GeneratedValue ` - :ref:`@HasLifecycleCallbacks ` - :ref:`@Index ` @@ -22,6 +25,7 @@ Index - :ref:`@ManyToOne ` - :ref:`@ManyToMany ` - :ref:`@MappedSuperclass ` +- :ref:`@NamedNativeQuery ` - :ref:`@OneToOne ` - :ref:`@OneToMany ` - :ref:`@OrderBy ` @@ -33,6 +37,7 @@ Index - :ref:`@PreRemove ` - :ref:`@PreUpdate ` - :ref:`@SequenceGenerator ` +- :ref:`@SqlResultSetMapping ` - :ref:`@Table ` - :ref:`@UniqueConstraint ` - :ref:`@Version ` @@ -109,6 +114,17 @@ Examples: */ protected $height; +.. _annref_column_result: + +@ColumnResult +~~~~~~~~~~~~~~ +References name of a column in the SELECT clause of a SQL query. +Scalar result types can be included in the query result by specifying this annotation in the metadata. + +Required attributes: + +- **name**: The name of a column in the SELECT clause of a SQL query + .. _annref_changetrackingpolicy: @ChangeTrackingPolicy @@ -219,6 +235,39 @@ Example: //... } +.. _annref_entity_result: + +@EntityResult +~~~~~~~~~~~~~~ +References an entity in the SELECT clause of a SQL query. +If this annotation is used, the SQL statement should select all of the columns that are mapped to the entity object. +This should include foreign key columns to related entities. +The results obtained when insufficient data is available are undefined. + +Required attributes: + +- **entityClass**: The class of the result. + +Optional attributes: + +- **fields**: Array of @FieldResult, Maps the columns specified in the SELECT list of the query to the properties or fields of the entity class. +- **discriminatorColumn**: Specifies the column name of the column in the SELECT list that is used to determine the type of the entity instance. + +.. _annref_field_result: + +@FieldResult +~~~~~~~~~~~~~ +Is used to map the columns specified in the SELECT list of the query to the properties or fields of the entity class. + +Required attributes: + +- **name**: Name of the persistent field or property of the class. + + +Optional attributes: + +- **column**: Name of the column in the SELECT clause. + .. _annref_generatedvalue: @GeneratedValue @@ -597,6 +646,77 @@ Example: // ... fields and methods } +.. _annref_named_native_query: + +@NamedNativeQuery +~~~~~~~~~~~~~~~~~ +Is used to specify a native SQL named query. +The NamedNativeQuery annotation can be applied to an entity or mapped superclass. + +Required attributes: + +- **name**: The name used to refer to the query with the EntityManager methods that create query objects. +- **query**: The SQL query string. + + +Optional attributes: + +- **resultClass**: The class of the result. +- **sqlResultSetMapping**: The name of a SqlResultSetMapping, as defined in metadata. + + +Example: + +.. code-block:: php + + Date: Sun, 15 Apr 2012 19:49:42 -0300 Subject: [PATCH 352/430] docs for Named Native Query --- en/reference/native-sql.rst | 474 ++++++++++++++++++++++++++++++++++++ 1 file changed, 474 insertions(+) diff --git a/en/reference/native-sql.rst b/en/reference/native-sql.rst index b1d0740c2..477297485 100644 --- a/en/reference/native-sql.rst +++ b/en/reference/native-sql.rst @@ -394,3 +394,477 @@ For entites with more columns the builder is very convenient to use. It extends and as such has all the functionality of it as well. Currently the ``ResultSetMappingBuilder`` does not support entities with inheritance. + +Named Native Query +------------------ + +You can also map a native query using a named native query mapping. + +To achieve that, you must describe the SQL resultset structure +using named native query (and sql resultset mappings if is a several resultset mappings). + +Like named query, a named native query can be defined at class level or in a XML or YAML file. + + +A resultSetMapping parameter is defined in @NamedNativeQuery, +it represents the name of a defined @SqlResultSetMapping. + +.. configuration-block:: + + .. code-block:: php + + + + + + SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username + + + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + MyProject\Model\User: + type: entity + namedNativeQueries: + fetchMultipleJoinsEntityResults: + name: fetchMultipleJoinsEntityResults + resultSetMapping: mappingMultipleJoinsEntityResults + query: SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username + sqlResultSetMappings: + mappingMultipleJoinsEntityResults: + name: mappingMultipleJoinsEntityResults + columnResult: + 0: + name: numphones + entityResult: + 0: + entityClass: __CLASS__ + fieldResult: + 0: + name: id + column: u_id + 1: + name: name + column: u_name + 2: + name: status + column: u_status + 1: + entityClass: Address + fieldResult: + 0: + name: id + column: a_id + 1: + name: zip + column: a_zip + 2: + name: country + column: a_country + + +Things to note: + - The resultset mapping declares the entities retrieved by this native query. + - Each field of the entity is bound to an SQL alias (or column name). + - All fields of the entity including the ones of subclasses + and the foreign key columns of related entities have to be present in the SQL query. + - Field definitions are optional provided that they map to the same + column name as the one declared on the class property. + - ``__CLASS__`` is a alias for the mapped class + + +In the above example, +the ``fetchJoinedAddress`` named query use the joinMapping result set mapping. +This mapping returns 2 entities, User and Address, each property is declared and associated to a column name, +actually the column name retrieved by the query. + +Let's now see an implicit declaration of the property / column. + +.. configuration-block:: + + .. code-block:: php + + + + + + SELECT * FROM addresses + + + + + + + + + + .. code-block:: yaml + + MyProject\Model\Address: + type: entity + namedNativeQueries: + findAll: + resultSetMapping: mappingFindAll + query: SELECT * FROM addresses + sqlResultSetMappings: + mappingFindAll: + name: mappingFindAll + entityResult: + address: + entityClass: Address + + +In this example, we only describe the entity member of the result set mapping. +The property / column mappings is done using the entity mapping values. +In this case the model property is bound to the model_txt column. +If the association to a related entity involve a composite primary key, +a @FieldResult element should be used for each foreign key column. +The @FieldResult name is composed of the property name for the relationship, +followed by a dot ("."), followed by the name or the field or property of the primary key. + + +.. configuration-block:: + + .. code-block:: php + + + + + + SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ? + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + MyProject\Model\User: + type: entity + namedNativeQueries: + fetchJoinedAddress: + name: fetchJoinedAddress + resultSetMapping: mappingJoinedAddress + query: SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ? + sqlResultSetMappings: + mappingJoinedAddress: + entityResult: + 0: + entityClass: __CLASS__ + fieldResult: + 0: + name: id + 1: + name: name + 2: + name: status + 3: + name: address.id + column: a_id + 4: + name: address.zip + column: a_zip + 5: + name: address.city + column: a_city + 6: + name: address.country + column: a_country + + + +If you retrieve a single entity and if you use the default mapping, +you can use the resultClass attribute instead of resultSetMapping: + +.. configuration-block:: + + .. code-block:: php + + + + + + SELECT * FROM addresses WHERE id = ? + + + + + .. code-block:: yaml + + MyProject\Model\Address: + type: entity + namedNativeQueries: + findAll: + name: findAll + resultClass: Address + query: SELECT * FROM addresses + + +In some of your native queries, you'll have to return scalar values, +for example when building report queries. +You can map them in the @SqlResultsetMapping through @ColumnResult. +You actually can even mix, entities and scalar returns in the same native query (this is probably not that common though). + +.. configuration-block:: + + .. code-block:: php + + + + + SELECT COUNT(*) AS count FROM addresses + + + + + + + + + .. code-block:: yaml + + MyProject\Model\Address: + type: entity + namedNativeQueries: + count: + name: count + resultSetMapping: mappingCount + query: SELECT COUNT(*) AS count FROM addresses + sqlResultSetMappings: + mappingCount: + name: mappingCount + columnResult: + count: + name: count \ No newline at end of file From 6a80ebf985ba8c337dceed2869a99af90afc1839 Mon Sep 17 00:00:00 2001 From: Arnaud BUCHOUX Date: Mon, 16 Apr 2012 17:15:18 +0200 Subject: [PATCH 353/430] Fix typo --- en/reference/annotations-reference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst index 1760ca731..73859093c 100644 --- a/en/reference/annotations-reference.rst +++ b/en/reference/annotations-reference.rst @@ -141,7 +141,7 @@ Example: .. _annref_discriminatorcolumn: -@DiscrimnatorColumn +@DiscriminatorColumn ~~~~~~~~~~~~~~~~~~~~~ This annotation is a required annotation for the topmost/super From 05a188da384866f5ea525790117d745694acb8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Osman=20U=CC=88ngu=CC=88r?= Date: Wed, 18 Apr 2012 17:13:14 +0300 Subject: [PATCH 354/430] Fix for collection handling code sample --- en/tutorials/in-ten-quick-steps.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/tutorials/in-ten-quick-steps.rst b/en/tutorials/in-ten-quick-steps.rst index 754887d66..8ae25a067 100644 --- a/en/tutorials/in-ten-quick-steps.rst +++ b/en/tutorials/in-ten-quick-steps.rst @@ -177,7 +177,7 @@ with another setup just take a look into the :doc:`Installation help public function addComment($text) { - $this->comments[] = $new Comment($this, $text); + $this->comments[] = new Comment($this, $text); } } From 488914a4acfa3470a2c5895469c02998db004faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Osman=20U=CC=88ngu=CC=88r?= Date: Thu, 19 Apr 2012 11:33:26 +0300 Subject: [PATCH 355/430] Added section about cache driver --- en/reference/caching.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/en/reference/caching.rst b/en/reference/caching.rst index 1da48f2ee..52545d842 100644 --- a/en/reference/caching.rst +++ b/en/reference/caching.rst @@ -100,6 +100,28 @@ driver by itself. $cacheDriver = new \Doctrine\Common\Cache\XcacheCache(); $cacheDriver->save('cache_id', 'my_data'); +Redis +~~~~~ + +In order to use the Redis cache driver you must have it compiled +and enabled in your php.ini. You can read about what is Redis +`from here `_. Also check +`here `_ for how you can use +and install Redis PHP extension. + +Below is a simple example of how you could use the Redis cache +driver by itself. + +.. code-block:: php + + connect('redis_host', 6379); + + $cacheDriver = new \Doctrine\Common\Cache\RedisCache(); + $cacheDriver->setRedis($redis); + $cacheDriver->save('cache_id', 'my_data'); + Using Cache Drivers ------------------- From 022298116164322fc5eb4c8eb6c7f32394399cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Osman=20U=CC=88ngu=CC=88r?= Date: Thu, 19 Apr 2012 11:34:12 +0300 Subject: [PATCH 356/430] Added notes about configuration --- en/reference/configuration.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index 1724886cb..fe0b7bb0c 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -177,7 +177,7 @@ setup methods: These setup commands make several assumptions: - If `$devMode` is true always use an ``ArrayCache`` and set ``setAutoGenerateProxyClasses(true)``. -- If `$devMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211) unless `$cache` is passed as fourth argument. +- If `$devMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument. - If `$devMode` is false, set ``setAutoGenerateProxyClasses(false)`` - If third argument `$proxyDir` is not set, use the systems temporary directory. @@ -280,6 +280,7 @@ The recommended implementations for production are: - ``Doctrine\Common\Cache\ApcCache`` - ``Doctrine\Common\Cache\MemcacheCache`` - ``Doctrine\Common\Cache\XcacheCache`` +- ``Doctrine\Common\Cache\RedisCache`` For development you should use the ``Doctrine\Common\Cache\ArrayCache`` which only caches data on a @@ -310,6 +311,7 @@ The recommended implementations for production are: - ``Doctrine\Common\Cache\ApcCache`` - ``Doctrine\Common\Cache\MemcacheCache`` - ``Doctrine\Common\Cache\XcacheCache`` +- ``Doctrine\Common\Cache\RedisCache`` For development you should use the ``Doctrine\Common\Cache\ArrayCache`` which only caches data on a From a1e7389e71393d5934fb0b218ddef0b8b0a8feba Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 15 Apr 2012 11:25:36 -0300 Subject: [PATCH 357/430] docs for association/attribute override --- en/reference/inheritance-mapping.rst | 296 +++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) diff --git a/en/reference/inheritance-mapping.rst b/en/reference/inheritance-mapping.rst index 9b44630a9..a89ab4b6b 100644 --- a/en/reference/inheritance-mapping.rst +++ b/en/reference/inheritance-mapping.rst @@ -261,3 +261,299 @@ have a foreign key pointing from the id column to the root table id column and cascading on delete. +Overrieds +--------- +Used to override a mapping for an entity field or relationship. +May be applied to an entity that extends a mapped superclass +to override a relationship or field mapping defined by the mapped superclass. + + +Association Override +~~~~~~~~~~~~~~~~~~~~ +Override a mapping for an entity relationship. + +Could be used by an entity that extends a mapped superclass +to override a relationship mapping defined by the mapped superclass. + +Example: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + # user mapping + MyProject\Model\User: + type: mappedSuperclass + # other fields mapping + manyToOne: + address: + targetEntity: Address + joinColumn: + name: address_id + referencedColumnName: id + cascade: [ persist, merge ] + manyToMany: + groups: + targetEntity: Group + joinTable: + name: users_groups + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + group_id: + referencedColumnName: id + cascade: [ persist, merge, detach ] + + # admin mapping + MyProject\Model\Admin: + type: entity + associationOverride: + address: + joinColumn: + adminaddress_id: + name: adminaddress_id + referencedColumnName: id + groups: + joinTable: + name: users_admingroups + joinColumns: + adminuser_id: + referencedColumnName: id + inverseJoinColumns: + admingroup_id: + referencedColumnName: id + + +Things to note: + +- The "association override" specifies the overrides base on the property name. +- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany) +- The association type *CANNOT* be changed. +- The override could redefine the joinTables or joinColumns depending on the association type. + +Attribute Override +~~~~~~~~~~~~~~~~~~~~ +Override the mapping of a field. + +Could be used by an entity that extends a mapped superclass to override a field mapping defined by the mapped superclass. + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + # user mapping + MyProject\Model\User: + type: mappedSuperclass + id: + id: + type: integer + column: user_id + length: 150 + generator: + strategy: AUTO + fields: + name: + type: string + column: user_name + length: 250 + nullable: true + unique: false + #other fields mapping + + + # guest mapping + MyProject\Model\Guest: + type: entity + attributeOverride: + id: + column: guest_id + type: integer + length: 140 + name: + column: guest_name + type: string + length: 240 + nullable: false + unique: true + +Things to note: + +- The "attribute override" specifies the overrides base on the property name. +- The column type *CANNOT* be changed. if the column type is not equals you got a ``MappingException`` +- The override can redefine all the column except the type. \ No newline at end of file From e65dbcf2b53ddb991e3580c3f2f0c5fb840510c7 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Thu, 19 Apr 2012 20:58:31 -0300 Subject: [PATCH 358/430] Fix typo --- en/reference/inheritance-mapping.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/inheritance-mapping.rst b/en/reference/inheritance-mapping.rst index a89ab4b6b..bb175c8f2 100644 --- a/en/reference/inheritance-mapping.rst +++ b/en/reference/inheritance-mapping.rst @@ -261,7 +261,7 @@ have a foreign key pointing from the id column to the root table id column and cascading on delete. -Overrieds +Overrides --------- Used to override a mapping for an entity field or relationship. May be applied to an entity that extends a mapped superclass From b1847723492020a641f49655eb9ad30a2e1eedb8 Mon Sep 17 00:00:00 2001 From: patrick-mcdougle Date: Thu, 26 Apr 2012 14:21:51 -0500 Subject: [PATCH 359/430] Updated the decimal type mapping have a string on the php side. (current behavior) --- en/reference/basic-mapping.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst index 90970de2e..932fdb417 100644 --- a/en/reference/basic-mapping.rst +++ b/en/reference/basic-mapping.rst @@ -155,7 +155,7 @@ built-in mapping types: integer. - ``bigint``: Type that maps a database BIGINT to a PHP string. - ``boolean``: Type that maps an SQL boolean to a PHP boolean. -- ``decimal``: Type that maps an SQL DECIMAL to a PHP double. +- ``decimal``: Type that maps an SQL DECIMAL to a PHP string. - ``date``: Type that maps an SQL DATETIME to a PHP DateTime object. - ``time``: Type that maps an SQL TIME to a PHP DateTime object. From 4627c8b3ee29eb2d00e63277134953b6a1295031 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 21 May 2012 16:13:15 -0400 Subject: [PATCH 360/430] Re-synchronized DQL EBNF with current DQL support. --- en/reference/dql-doctrine-query-language.rst | 160 ++++++++++--------- 1 file changed, 87 insertions(+), 73 deletions(-) diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index b5c01d776..a80b6f6ef 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -354,10 +354,10 @@ IN() Expression: createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)'); $usernames = $query->getResult(); - + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id IN (1, 2)'); $users = $query->getResult(); - + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id NOT IN (1)'); $users = $query->getResult(); @@ -369,7 +369,7 @@ CONCAT() DQL Function: $query = $em->createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); $query->setParameter(1, 'Jess'); $ids = $query->getResult(); - + $query = $em->createQuery('SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1'); $query->setParameter(1, 321); $idUsernames = $query->getResult(); @@ -417,6 +417,14 @@ hierarchies: $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1'); $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1'); +Get all users visible on a given website that have chosen certain gender: + +.. code-block:: php + + createQuery('SELECT u FROM User u WHERE u.gender IN (SELECT IDENTITY(agl.gender) FROM Site s JOIN s.activeGenderList agl WHERE s.id = ?1)'); + + Partial Object Syntax ^^^^^^^^^^^^^^^^^^^^^ @@ -539,6 +547,7 @@ The following functions are supported in SELECT, WHERE and HAVING clauses: +- IDENTITY(single\_association\_path\_expression) - Retrieve the foreign key column of association of the owning side - ABS(arithmetic\_expression) - CONCAT(str1, str2) - CURRENT\_DATE() - Return the current date @@ -613,7 +622,7 @@ You can register custom DQL functions in your ORM Configuration: $config->addCustomStringFunction($name, $class); $config->addCustomNumericFunction($name, $class); $config->addCustomDatetimeFunction($name, $class); - + $em = EntityManager::create($dbParams, $config); The functions have to return either a string, numeric or datetime @@ -625,30 +634,30 @@ classes have to implement the base class : walkSimpleArithmeticExpression( $this->simpleArithmeticExpression ) . ')'; } - + public function parse(\Doctrine\ORM\Query\Parser $parser) { $lexer = $parser->getLexer(); - + $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); - + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); - + $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } @@ -683,7 +692,7 @@ scenario it is a generic Person and Employee example: createQuery('select u from MyProject\Model\User u'); - + // example2: using setDql $q = $em->createQuery(); $q->setDql('select u from MyProject\Model\User u'); @@ -1096,9 +1105,9 @@ creating a class which extends ``AbstractHydrator``: createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); $query->setParameter(1, 12); - + $query->setResultCacheDriver(new ApcCache()); - + $query->useResultCache(true) ->setResultCacheLifeTime($seconds = 3600); - + $result = $query->getResult(); // cache miss - + $query->expireResultCache(true); $result = $query->getResult(); // forced expire, cache miss - + $query->setResultCacheId('my_query_result'); $result = $query->getResult(); // saved in given result cache id. - + // or call useResultCache() with all parameters: $query->useResultCache(true, $seconds = 3600, 'my_query_result'); $result = $query->getResult(); // cache hit! @@ -1358,33 +1367,33 @@ Identifiers /* Alias Identification usage (the "u" of "u.name") */ IdentificationVariable ::= identifier - + /* Alias Identification declaration (the "u" of "FROM User u") */ AliasIdentificationVariable :: = identifier - + /* identifier that must be a class name (the "User" of "FROM User u") */ AbstractSchemaName ::= identifier - + /* identifier that must be a field (the "name" of "u.name") */ /* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */ FieldIdentificationVariable ::= identifier - + /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ CollectionValuedAssociationField ::= FieldIdentificationVariable - + /* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */ SingleValuedAssociationField ::= FieldIdentificationVariable - + /* identifier that must be an embedded class state field (for the future) */ EmbeddedClassStateField ::= FieldIdentificationVariable - + /* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ SimpleStateField ::= FieldIdentificationVariable - + /* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */ AliasResultVariable = identifier - + /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ ResultVariable = identifier @@ -1395,27 +1404,24 @@ Path Expressions /* "u.Group" or "u.Phonenumbers" declarations */ JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) - + /* "u.Group" or "u.Phonenumbers" usages */ AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression - + /* "u.name" or "u.Group" */ SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression - + /* "u.name" or "u.Group.name" */ - StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField - + StateFieldPathExpression ::= IdentificationVariable "." StateField + /* "u.Group" */ SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField /* "u.Group.Permissions" */ - CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField - + CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField + /* "name" */ StateField ::= {EmbeddedClassStateField "."}* SimpleStateField - - /* "u.name" or "u.address.zip" (address = EmbeddedClassStateField) */ - SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField Clauses ~~~~~~~ @@ -1439,10 +1445,10 @@ Items .. code-block:: php - UpdateItem ::= IdentificationVariable "." (StateField | SingleValuedAssociationField) "=" NewValue - OrderByItem ::= (ResultVariable | SingleValuedPathExpression) ["ASC" | "DESC"] - GroupByItem ::= IdentificationVariable | SingleValuedPathExpression - NewValue ::= ScalarExpression | SimpleEntityExpression | "NULL" + UpdateItem ::= SingleValuedPathExpression "=" NewValue + OrderByItem ::= (SimpleArithmeticExpression | SingleValuedPathExpression | ScalarExpression | ResultVariable) ["ASC" | "DESC"] + GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression + NewValue ::= SimpleArithmeticExpression | "NULL" From, Join and Index by ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1453,18 +1459,16 @@ From, Join and Index by SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) JoinVariableDeclaration ::= Join [IndexBy] RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable - Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression - ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] - IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression + Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] + IndexBy ::= "INDEX" "BY" StateFieldPathExpression Select Expressions ~~~~~~~~~~~~~~~~~~ .. code-block:: php - SelectExpression ::= IdentificationVariable | PartialObjectExpression | (AggregateExpression | "(" Subselect ")" | FunctionDeclaration | ScalarExpression) [["AS"] AliasResultVariable] - SimpleSelectExpression ::= ScalarExpression | IdentificationVariable | - (AggregateExpression [["AS"] AliasResultVariable]) + SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression) [["AS"] ["HIDDEN"] AliasResultVariable] + SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable] PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" @@ -1519,15 +1523,15 @@ Arithmetic Expressions ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings - | FunctionsReturningDatetime | IdentificationVariable | InputParameter | CaseExpression + | FunctionsReturningDatetime | IdentificationVariable | ResultVariable + | InputParameter | CaseExpression Scalar and Type Expressions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php - ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression - BooleanPrimary | EntityTypeExpression | CaseExpression + ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression | BooleanPrimary | CaseExpression | InstanceOfExpression StringExpression ::= StringPrimary | "(" Subselect ")" StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression BooleanExpression ::= BooleanPrimary | "(" Subselect ")" @@ -1554,13 +1558,13 @@ Case Expressions .. code-block:: php - CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression - GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" - WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression - SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" - CaseOperand ::= StateFieldPathExpression | TypeDiscriminator - SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression - CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" + CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression + GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" + WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" + CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" Other Expressions @@ -1573,10 +1577,10 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) - InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" + InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") InstanceOfParameter ::= AbstractSchemaName | InputParameter - LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] + LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char] NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" @@ -1587,21 +1591,31 @@ Functions .. code-block:: php FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime - + FunctionsReturningNumerics ::= "LENGTH" "(" StringPrimary ")" | "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | - "ABS" "(" SimpleArithmeticExpression ")" | "SQRT" "(" SimpleArithmeticExpression ")" | + "ABS" "(" SimpleArithmeticExpression ")" | + "SQRT" "(" SimpleArithmeticExpression ")" | "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | - "SIZE" "(" CollectionValuedPathExpression ")" - - FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" - + "SIZE" "(" CollectionValuedPathExpression ")" | + "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | + "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | + "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" + + FunctionsReturningDateTime ::= + "CURRENT_DATE" | + "CURRENT_TIME" | + "CURRENT_TIMESTAMP" | + "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" | + "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" + FunctionsReturningStrings ::= "CONCAT" "(" StringPrimary "," StringPrimary ")" | "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | "LOWER" "(" StringPrimary ")" | - "UPPER" "(" StringPrimary ")" + "UPPER" "(" StringPrimary ")" | + "IDENTITY" "(" SingleValuedAssociationPathExpression ")" From 8b4e08d694318459dc994ce253c9011f27042e52 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 28 May 2012 12:39:31 -0400 Subject: [PATCH 361/430] Updated docs (trying to fix one-to-many with unidirectional join table example). --- en/reference/association-mapping.rst | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst index df456b201..91e5c7149 100644 --- a/en/reference/association-mapping.rst +++ b/en/reference/association-mapping.rst @@ -223,9 +223,15 @@ One-To-Many, Unidirectional with Join Table A unidirectional one-to-many association can be mapped through a join table. From Doctrine's point of view, it is simply mapped as a unidirectional many-to-many whereby a unique constraint on one of -the join columns enforces the one-to-many cardinality. The -following example sets up such a unidirectional one-to-many -association: +the join columns enforces the one-to-many cardinality. + +.. note:: + + One-To-Many uni-directional relations with join-table only + work using the @ManyToMany annotation and a unique-constraint. + + +The following example sets up such a unidirectional one-to-many association: .. configuration-block:: @@ -246,7 +252,8 @@ association: **/ private $phonenumbers; - public function __construct() { + public function __construct() + { $this->phonenumbers = new \Doctrine\Common\Collections\ArrayCollection(); } @@ -293,11 +300,6 @@ association: referencedColumnName: id unique: true -.. note:: - - One-To-Many uni-directional relations with join-table only - work using the @ManyToMany annotation and a unique-constraint. - Generates the following MySQL Schema: @@ -323,6 +325,7 @@ Generates the following MySQL Schema: ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id); ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id); + Many-To-One, Unidirectional --------------------------- From 671177e1629ef2779b7199f848ca54a3556a76a4 Mon Sep 17 00:00:00 2001 From: Douglas Greenshields Date: Wed, 30 May 2012 15:59:58 +0200 Subject: [PATCH 362/430] Improved grammar/ punctuation in pagination tutorial, and brought parameter name in code example into line with actual parameter --- en/tutorials/pagination.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/tutorials/pagination.rst b/en/tutorials/pagination.rst index 63e50e6c5..dc789540a 100644 --- a/en/tutorials/pagination.rst +++ b/en/tutorials/pagination.rst @@ -15,7 +15,7 @@ has a very simple API and implements the SPL interfaces ``Countable`` and ->setFirstResult(0) ->setMaxResults(100); - $paginator = new Paginator($query, $fetchJoin = true); + $paginator = new Paginator($query, $fetchJoinCollection = true); $c = count($paginator); foreach ($paginator as $post) { @@ -36,6 +36,6 @@ correct result: This behavior is only necessary if you actually fetch join a to-many collection. You can disable this behavior by setting the -``$fetchJoinCollection`` flag of, in that case only 2 instead of the 3 queries +``$fetchJoinCollection`` flag to ``false``; in that case only 2 instead of the 3 queries described are executed. We hope to automate the detection for this in the future. From b25548414b01ce101c87826f675d08b13a710c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Lobato?= Date: Thu, 31 May 2012 10:51:38 +0200 Subject: [PATCH 363/430] Fixed some typos on Inheritance docs --- en/reference/inheritance-mapping.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/en/reference/inheritance-mapping.rst b/en/reference/inheritance-mapping.rst index 9b44630a9..06ae0c656 100644 --- a/en/reference/inheritance-mapping.rst +++ b/en/reference/inheritance-mapping.rst @@ -4,7 +4,7 @@ Inheritance Mapping Mapped Superclasses ------------------- -An mapped superclass is an abstract or concrete class that provides +A mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information for its subclasses, but which is not itself an entity. Typically, the purpose of such a mapped superclass is to define state and mapping information that @@ -142,7 +142,7 @@ very performant. There is a general performance consideration with Single Table Inheritance: If the target-entity of a many-to-one or one-to-one association is an STI entity, it is preferable for performance reasons that it -be a leaf entity in the inheritance heirarchy, (ie. have no subclasses). +be a leaf entity in the inheritance hierarchy, (ie. have no subclasses). Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly. @@ -245,7 +245,7 @@ subtypes after such a query is not safe. There is a general performance consideration with Class Table Inheritance: If the target-entity of a many-to-one or one-to-one association is a CTI entity, it is preferable for performance reasons that it -be a leaf entity in the inheritance heirarchy, (ie. have no subclasses). +be a leaf entity in the inheritance hierarchy, (ie. have no subclasses). Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly. From e41704b2110ca5111a0003cd6134be354e2cd8c1 Mon Sep 17 00:00:00 2001 From: Calum Brodie Date: Thu, 31 May 2012 14:08:56 +0200 Subject: [PATCH 364/430] Fixed inline example of concat method --- en/reference/query-builder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst index 0217719fd..c0ae0e660 100644 --- a/en/reference/query-builder.rst +++ b/en/reference/query-builder.rst @@ -356,7 +356,7 @@ complete list of supported helper methods available: // Example - $qb->expr()->trim('u.firstname') public function trim($x); // Returns Expr\Func - // Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat(' ', 'u.lastname')) + // Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat($qb->expr()->literal(' '), 'u.lastname')) public function concat($x, $y); // Returns Expr\Func // Example - $qb->expr()->substr('u.firstname', 0, 1) From 9f575aad5b4ac55eb3d6a7d66cc2986946568c94 Mon Sep 17 00:00:00 2001 From: patrick-mcdougle Date: Tue, 5 Jun 2012 16:35:37 -0500 Subject: [PATCH 365/430] Fixed wording on the Alice and Bob Optimistic locking example. --- en/reference/transactions-and-concurrency.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/en/reference/transactions-and-concurrency.rst b/en/reference/transactions-and-concurrency.rst index 56a9c7db7..0a752ad5b 100644 --- a/en/reference/transactions-and-concurrency.rst +++ b/en/reference/transactions-and-concurrency.rst @@ -264,9 +264,8 @@ Important Implementation Notes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can easily get the optimistic locking workflow wrong if you -compare the wrong versions. Say you have Alice and Bob accessing a -hypothetical bank account: - +compare the wrong versions. Say you have Alice and Bob editing a +hypothetical blog post: - Alice reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request) From 24d488b5f1017bebe267d6049ab136b89f1231b4 Mon Sep 17 00:00:00 2001 From: Erik Dubbelboer Date: Thu, 7 Jun 2012 13:53:58 +0200 Subject: [PATCH 366/430] fixed minor errors --- en/tutorials/getting-started.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/en/tutorials/getting-started.rst b/en/tutorials/getting-started.rst index ab67aefe4..5788d5c6f 100644 --- a/en/tutorials/getting-started.rst +++ b/en/tutorials/getting-started.rst @@ -136,6 +136,7 @@ following set of classes. Put them into `entities/Bug.php`, .. code-block:: php + Date: Wed, 13 Jun 2012 01:36:40 +0300 Subject: [PATCH 367/430] Fix typo/Add missing words --- en/tutorials/ordered-associations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/tutorials/ordered-associations.rst b/en/tutorials/ordered-associations.rst index 23f438300..c34b01ce7 100644 --- a/en/tutorials/ordered-associations.rst +++ b/en/tutorials/ordered-associations.rst @@ -1,7 +1,7 @@ Ordering To-Many Assocations ---------------------------- -There use-cases you will want to sort collections when they are +There are use-cases when you'll want to sort collections when they are retrieved from the database. In userland you do this as long as you haven't initially saved an entity with its associations into the database. To retrieve a sorted collection from the database you can From e5bac27fcc3e517dd1a307b7f1fc622c135d80e6 Mon Sep 17 00:00:00 2001 From: Tim Nagel Date: Sat, 16 Jun 2012 17:36:40 +1000 Subject: [PATCH 368/430] Initial ResolveTargetEntityListener cookbook entry --- .../resolve-target-entity-listener.rst | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 en/cookbook/resolve-target-entity-listener.rst diff --git a/en/cookbook/resolve-target-entity-listener.rst b/en/cookbook/resolve-target-entity-listener.rst new file mode 100644 index 000000000..0e9947755 --- /dev/null +++ b/en/cookbook/resolve-target-entity-listener.rst @@ -0,0 +1,136 @@ +Keeping your Modules independent +================================= + +One of the goals of using modules is to create discreet units of functionality +that do not have many (if any) dependencies, allowing you to use that +functionality in other applications without including unnecessary items. + +Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``, +that functions by intercepting certain calls inside Doctrine and rewrite +targetEntity parameters in your metadata mapping at runtime. It means that +in your bundle you are able to use an interface or abstract class in your +mappings and expect correct mapping to a concrete entity at runtime. + +This functionality allows you to define relationships between different entities +but not making them hard dependencies. + +Background +---------- + +In the following example, the situation is we have an `InvoiceModule` +which provides invoicing functionality, and a `CustomerModule` that +contains customer management tools. We want to keep these separated, +because they can be used in other systems without each other, but for +our application we want to use them together. + +In this case, we have an ``Invoice`` entity with a relationship to a +non-existant object, an ``InvoiceSubjectInterface``. The goal is to get +the ``ResolveTargetEntityListener`` to replace any mention of the interface +with a real object that implements that interface. + +Set up +------ + +We're going to use the following basic entities (which are incomplete +for brevity) to explain how to set up and use the RTEL. + +A Customer entity + +.. code-block:: php + + // src/Acme/AppModule/Entity/Customer.php + + namespace Acme\AppModule\Entity; + + use Doctrine\ORM\Mapping as ORM; + use Acme\CustomerModule\Entity\Customer as BaseCustomer; + use Acme\InvoiceModule\Model\InvoiceSubjectInterface; + + /** + * @ORM\Entity + * @ORM\Table(name="customer") + */ + class Customer extends BaseCustomer implements InvoiceSubjectInterface + { + // In our example, any methods defined in the InvoiceSubjectInterface + // are already implemented in the BaseCustomer + } + +An Invoice entity + +.. code-block:: php + + // src/Acme/InvoiceModule/Entity/Invoice.php + + namespace Acme\InvoiceModule\Entity; + + use Doctrine\ORM\Mapping AS ORM; + use Acme\InvoiceModule\Model\InvoiceSubjectInterface; + + /** + * Represents an Invoice. + * + * @ORM\Entity + * @ORM\Table(name="invoice") + */ + class Invoice + { + /** + * @ORM\ManyToOne(targetEntity="Acme\InvoiceModule\Model\InvoiceSubjectInterface") + * @var InvoiceSubjectInterface + */ + protected $subject; + } + +An InvoiceSubjectInterface + +.. code-block:: php + + // src/Acme/InvoiceModule/Model/InvoiceSubjectInterface.php + + namespace Acme\InvoiceModule\Model; + + /** + * An interface that the invoice Subject object should implement. + * In most circumstances, only a single object should implement + * this interface as the ResolveTargetEntityListener can only + * change the target to a single object. + */ + interface InvoiceSubjectInterface + { + // List any additional methods that your InvoiceModule + // will need to access on the subject so that you can + // be sure that you have access to those methods. + + /** + * @return string + */ + public function getName(); + } + +Next, we need to configure the listener. Add this to the area you set up Doctrine. You +must set this up in the way outlined below, otherwise you can not be guaranteed that +the targetEntity resolution will occur reliably:: + +.. code-block:: php + + $evm = new \Doctrine\Common\EventManager; + + $rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener; + $rtel->addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface', + 'Acme\\CustomerModule\\Entity\\Customer', array()); + + // Add the ResolveTargetEntityListener + $evm->addEventSubscriber($rtel); + + $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm); + +Final Thoughts +-------------- + +With the ``ResolveTargetEntityListener``, we are able to decouple our +bundles, keeping them usable by themselves, but still being able to +define relationships between different objects. By using this method, +I've found my bundles end up being easier to maintain independently. + + From 65e2f60b40feaad8cbce1eb2ef3c7df3c54374e1 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 16 Jun 2012 11:57:56 +0200 Subject: [PATCH 369/430] Rework configuration and tools section to include Composer --- en/reference/configuration.rst | 126 ++++++++++++++++++++--------- en/reference/tools.rst | 143 ++++++++++++--------------------- 2 files changed, 138 insertions(+), 131 deletions(-) diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index fe0b7bb0c..6d4f291a7 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -1,18 +1,40 @@ Configuration ============= -Bootstrapping -------------- - Bootstrapping Doctrine is a relatively simple procedure that -roughly exists of just 2 steps: - +roughly exists of four steps: +- Installation - Making sure Doctrine class files can be loaded on demand. - Obtaining an EntityManager instance. +- Optional: Configuration of the Console Tool + +Installation +------------ + +`Composer `_ is the suggested installation method for Doctrine. +Define the following requirement in your ``composer.json`` file: + + { + "require": { + "doctrine/orm": "*" + } + } + +Then run the composer command. Class loading -~~~~~~~~~~~~~ +------------- + +Autoloading is taken care of by Composer. You just have to include the composer autoload file in your project: + +.. code-block:: php + + setCatchExceptions(true); + $cli->setHelperSet($helperSet); + Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli); + $cli->run(); diff --git a/en/reference/tools.rst b/en/reference/tools.rst index 25d591df7..69babfc27 100644 --- a/en/reference/tools.rst +++ b/en/reference/tools.rst @@ -8,15 +8,8 @@ The Doctrine Console is a Command Line Interface tool for simplifying common tasks during the development of a project that uses Doctrine 2. -Installation -~~~~~~~~~~~~ - -If you installed Doctrine 2 through PEAR, the ``doctrine`` command -line tool should already be available to you. Y - -In any other case you should create a project specific doctrine command -on your own. This is a combination of the PEAR ``doctrine`` commands -code and some of your own bootstrap code. +Take a look at the `Configuration ` for more +information how to setup the console command. Getting Help ~~~~~~~~~~~~ @@ -30,23 +23,6 @@ about the use of generate entities for example, you can call: doctrine orm:generate-entities --help -Setting up the Console ----------------------- - -Doctrine uses the Symfony Console component for generating the command -line interface. You can take a look at the ``bin/doctrine.php`` -script and the ``Doctrine\ORM\Tools\Console\ConsoleRunner`` command -for inspiration how to setup the cli. - -In general the required code looks like this: - -.. code-block:: php - - $cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION); - $cli->setCatchExceptions(true); - $cli->setHelperSet($helperSet); - Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli); - $cli->run(); Configuration (PEAR) ~~~~~~~~~~~~~~~~~~~~ @@ -94,18 +70,10 @@ sample ``cli-config.php`` file looks as follows: new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), @@ -117,7 +85,6 @@ script will ultimately use. The Doctrine Binary will automatically find the first instance of HelperSet in the global variable namespace and use this. - .. note:: You have to adjust this snippet for your specific application or framework @@ -136,22 +103,15 @@ there whenever you want to access the Doctrine console. new ConnectionHelper($em->getConnection()), @@ -160,50 +120,6 @@ there whenever you want to access the Doctrine console. ConsoleRunner::run($helperSet); -Adding own commands -~~~~~~~~~~~~~~~~~~~ - -You can also add your own commands on-top of the Doctrine supported -tools if you are using a manually built (Non-PEAR) binary. - -To include a new command on Doctrine Console, you need to do modify the -``doctrine.php`` file a little: - -.. code-block:: php - - setCatchExceptions(true); - $cli->setHelperSet($helperSet); - - // Register All Doctrine Commands - ConsoleRunner::addCommands($cli); - - // Register your own command - $cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand); - - // Runs console application - $cli->run(); - -Additionally, include multiple commands (and overriding previously -defined ones) is possible through the command: - -.. code-block:: php - - addCommands(array( - new \MyProject\Tools\Console\Commands\MyCustomCommand(), - new \MyProject\Tools\Console\Commands\SomethingCommand(), - new \MyProject\Tools\Console\Commands\AnotherCommand(), - new \MyProject\Tools\Console\Commands\OneMoreCommand(), - )); Command Overview ~~~~~~~~~~~~~~~~ @@ -518,4 +434,47 @@ You can also reverse engineer a database using the a useful domain model. +Adding own commands +------------------- +You can also add your own commands on-top of the Doctrine supported +tools if you are using a manually built (Non-PEAR) binary. + +To include a new command on Doctrine Console, you need to do modify the +``doctrine.php`` file a little: + +.. code-block:: php + + setCatchExceptions(true); + $cli->setHelperSet($helperSet); + + // Register All Doctrine Commands + ConsoleRunner::addCommands($cli); + + // Register your own command + $cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand); + + // Runs console application + $cli->run(); + +Additionally, include multiple commands (and overriding previously +defined ones) is possible through the command: + +.. code-block:: php + + addCommands(array( + new \MyProject\Tools\Console\Commands\MyCustomCommand(), + new \MyProject\Tools\Console\Commands\SomethingCommand(), + new \MyProject\Tools\Console\Commands\AnotherCommand(), + new \MyProject\Tools\Console\Commands\OneMoreCommand(), + )); From a3883eb3064b42cfb4d0f00d4351c3fefc67b3be Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 16 Jun 2012 12:12:04 +0200 Subject: [PATCH 370/430] Reworked docs towards composer, simplified chapters --- en/index.rst | 2 +- en/reference/architecture.rst | 69 +++++- en/reference/configuration.rst | 16 +- en/reference/installation.rst | 103 +++++++++ en/reference/introduction.rst | 403 --------------------------------- en/reference/tools.rst | 2 +- 6 files changed, 172 insertions(+), 423 deletions(-) create mode 100644 en/reference/installation.rst delete mode 100644 en/reference/introduction.rst diff --git a/en/index.rst b/en/index.rst index 3841e0654..4a9d74839 100644 --- a/en/index.rst +++ b/en/index.rst @@ -30,8 +30,8 @@ Getting Started :doc:`In 10 quick steps ` * **Reference**: - :doc:`Introduction ` | :doc:`Architecture ` | + :doc:`Installation ` | :doc:`Configuration ` | :doc:`Tools ` | :doc:`Limitations and knowns issues ` diff --git a/en/reference/architecture.rst b/en/reference/architecture.rst index c3508bf7b..54122bcf1 100644 --- a/en/reference/architecture.rst +++ b/en/reference/architecture.rst @@ -5,8 +5,69 @@ This chapter gives an overview of the overall architecture, terminology and constraints of Doctrine 2. It is recommended to read this chapter carefully. +Using an Object-Relational Mapper +--------------------------------- + +As the term ORM already hints at, Doctrine 2 aims to simplify the +translation between database rows and the PHP object model. The +primary use case for Doctrine are therefore applications that +utilize the Object-Oriented Programming Paradigm. For applications +that not primarily work with objects Doctrine 2 is not suited very +well. + +Requirements +------------ + +Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved +performance it is also recommended that you use APC with PHP. + +Doctrine 2 Packages +------------------- + +Doctrine 2 is divided into three main packages. + +- Common +- DBAL (includes Common) +- ORM (includes DBAL+Common) + +This manual mainly covers the ORM package, sometimes touching parts +of the underlying DBAL and Common packages. The Doctrine code base +is split in to these packages for a few reasons and they are to... + + +- ...make things more maintainable and decoupled +- ...allow you to use the code in Doctrine Common without the ORM + or DBAL +- ...allow you to use the DBAL without the ORM + +The Common Package +~~~~~~~~~~~~~~~~~~ + +The Common package contains highly reusable components that have no +dependencies beyond the package itself (and PHP, of course). The +root namespace of the Common package is ``Doctrine\Common``. + +The DBAL Package +~~~~~~~~~~~~~~~~ + +The DBAL package contains an enhanced database abstraction layer on +top of PDO but is not strongly bound to PDO. The purpose of this +layer is to provide a single API that bridges most of the +differences between the different RDBMS vendors. The root namespace +of the DBAL package is ``Doctrine\DBAL``. + +The ORM Package +~~~~~~~~~~~~~~~ + +The ORM package contains the object-relational mapping toolkit that +provides transparent relational persistence for plain PHP objects. +The root namespace of the ORM package is ``Doctrine\ORM``. + +Terminology +----------- + Entities --------- +~~~~~~~~ An entity is a lightweight, persistent domain object. An entity can be any regular PHP class observing the following restrictions: @@ -38,7 +99,9 @@ polymorphic queries. Both abstract and concrete classes can be entities. Entities may extend non-entity classes as well as entity classes, and non-entity classes may extend entity classes. - **TIP** The constructor of an entity is only ever invoked when +.. note:: + + The constructor of an entity is only ever invoked when *you* construct a new instance with the *new* keyword. Doctrine never calls entity constructors, thus you are free to use them as you wish and even have it require arguments of any type. @@ -101,7 +164,7 @@ work well with any potential cyclic object references (at least we did not find a way yet, if you did, please contact us). The EntityManager ------------------ +~~~~~~~~~~~~~~~~~ The ``EntityManager`` class is a central access point to the ORM functionality provided by Doctrine 2. The ``EntityManager`` API is diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index 6d4f291a7..679997be8 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -4,25 +4,11 @@ Configuration Bootstrapping Doctrine is a relatively simple procedure that roughly exists of four steps: -- Installation +- `Installation ` - Making sure Doctrine class files can be loaded on demand. - Obtaining an EntityManager instance. - Optional: Configuration of the Console Tool -Installation ------------- - -`Composer `_ is the suggested installation method for Doctrine. -Define the following requirement in your ``composer.json`` file: - - { - "require": { - "doctrine/orm": "*" - } - } - -Then run the composer command. - Class loading ------------- diff --git a/en/reference/installation.rst b/en/reference/installation.rst new file mode 100644 index 000000000..43cb28e70 --- /dev/null +++ b/en/reference/installation.rst @@ -0,0 +1,103 @@ +Installation +============ + +Doctrine can be installed many different ways. We will describe all the different ways and you can choose which one suits you best. + +Composer +-------- + +`Composer `_ is the suggested installation method for Doctrine. +Define the following requirement in your ``composer.json`` file: + +:: + + { + "require": { + "doctrine/orm": "*" + } + } + +Then run the composer command and you are done. Continue with the +:doc:`Configuration `. + +PEAR +---- + +You can easily install any of the three Doctrine packages from the +PEAR command line installation utility. + +To install just the ``Common`` package you can run the following +command: + +.. code-block:: bash + + $ sudo pear install pear.doctrine-project.org/DoctrineCommon- + +If you want to use the Doctrine Database Abstraction Layer you can +install it with the following command. + +.. code-block:: bash + + $ sudo pear install pear.doctrine-project.org/DoctrineDBAL- + +Or, if you want to get the works and go for the ORM you can install +it with the following command. + +.. code-block:: bash + + $ sudo pear install pear.doctrine-project.org/DoctrineORM- + + +.. note:: + + The ```` tag above represents the version you + want to install. For example if the current version at the time of + writing this is ``2.2.2`` for the ORM, so you could install it + like the following: + + .. code-block:: bash + + $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.2.2 + +Package Download +---------------- + +You can also use Doctrine 2 by downloading the latest release +package from +`the download page `_. + +See the configuration section on how to configure and bootstrap a +downloaded version of Doctrine. + +GitHub +------ + +Alternatively you can clone the latest version of Doctrine 2 via +GitHub.com: + +.. code-block:: php + + $ git clone git://github.com/doctrine/doctrine2.git doctrine + +This downloads all the sources of the ORM package. You need to +initialize the Github submodules for the Common and DBAL package +dependencies: + +.. code-block:: php + + $ git submodule init + $ git submodule update + +This updates your Git checkout to use the Doctrine and Doctrine +package versions that are recommended for the cloned Master version +of Doctrine 2. + +See the configuration chapter on how to configure a Github +installation of Doctrine with regards to autoloading. + +.. note:: + + You should not combine the Doctrine-Common, Doctrine-DBAL and + Doctrine-ORM master commits with each other in combination. The ORM + may not work with the current Common or DBAL master versions. + Instead the ORM ships with the Git Submodules that are required. diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst deleted file mode 100644 index 84c2b5f04..000000000 --- a/en/reference/introduction.rst +++ /dev/null @@ -1,403 +0,0 @@ -Introduction -============ - -Welcome -------- - -Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that -provides transparent persistence for PHP objects. It sits on top of -a powerful database abstraction layer (DBAL). Object-Relational -Mappers primary task is the transparent translation between (PHP) -objects and relational database rows. - -One of Doctrines key features is the option to write database -queries in a proprietary object oriented SQL dialect called -Doctrine Query Language (DQL), inspired by Hibernates HQL. Besides -DQLs slight differences to SQL it abstracts the mapping between -database rows and objects considerably, allowing developers to -write powerful queries in a simple and flexible fashion. - -Disclaimer ----------- - -This is the Doctrine 2 reference documentation. Introductory guides -and tutorials that you can follow along from start to finish, like -the "Guide to Doctrine" book known from the Doctrine 1.x series, -will be available at a later date. - -Using an Object-Relational Mapper ---------------------------------- - -As the term ORM already hints at, Doctrine 2 aims to simplify the -translation between database rows and the PHP object model. The -primary use case for Doctrine are therefore applications that -utilize the Object-Oriented Programming Paradigm. For applications -that not primarily work with objects Doctrine 2 is not suited very -well. - -Requirements ------------- - -Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved -performance it is also recommended that you use APC with PHP. - -Doctrine 2 Packages -------------------- - -Doctrine 2 is divided into three main packages. - - -- Common -- DBAL (includes Common) -- ORM (includes DBAL+Common) - -This manual mainly covers the ORM package, sometimes touching parts -of the underlying DBAL and Common packages. The Doctrine code base -is split in to these packages for a few reasons and they are to... - - -- ...make things more maintainable and decoupled -- ...allow you to use the code in Doctrine Common without the ORM - or DBAL -- ...allow you to use the DBAL without the ORM - -The Common Package -~~~~~~~~~~~~~~~~~~ - -The Common package contains highly reusable components that have no -dependencies beyond the package itself (and PHP, of course). The -root namespace of the Common package is ``Doctrine\Common``. - -The DBAL Package -~~~~~~~~~~~~~~~~ - -The DBAL package contains an enhanced database abstraction layer on -top of PDO but is not strongly bound to PDO. The purpose of this -layer is to provide a single API that bridges most of the -differences between the different RDBMS vendors. The root namespace -of the DBAL package is ``Doctrine\DBAL``. - -The ORM Package -~~~~~~~~~~~~~~~ - -The ORM package contains the object-relational mapping toolkit that -provides transparent relational persistence for plain PHP objects. -The root namespace of the ORM package is ``Doctrine\ORM``. - -Installing ----------- - -Doctrine can be installed many different ways. We will describe all -the different ways and you can choose which one suits you best. - -PEAR -~~~~ - -You can easily install any of the three Doctrine packages from the -PEAR command line installation utility. - -To install just the ``Common`` package you can run the following -command: - -.. code-block:: bash - - $ sudo pear install pear.doctrine-project.org/DoctrineCommon- - -If you want to use the Doctrine Database Abstraction Layer you can -install it with the following command. - -.. code-block:: bash - - $ sudo pear install pear.doctrine-project.org/DoctrineDBAL- - -Or, if you want to get the works and go for the ORM you can install -it with the following command. - -.. code-block:: bash - - $ sudo pear install pear.doctrine-project.org/DoctrineORM- - - -.. note:: - - The ```` tag above represents the version you - want to install. For example if the current version at the time of - writing this is ``2.0.7`` for the ORM, so you could install it - like the following: - - .. code-block:: bash - - $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.7 - - -When you have a package installed via PEAR you can require and load -the ``ClassLoader`` with the following code. - -.. code-block:: php - - `_. - -See the configuration section on how to configure and bootstrap a -downloaded version of Doctrine. - -GitHub -~~~~~~ - -Alternatively you can clone the latest version of Doctrine 2 via -GitHub.com: - -.. code-block:: php - - $ git clone git://github.com/doctrine/doctrine2.git doctrine - -This downloads all the sources of the ORM package. You need to -initialize the Github submodules for the Common and DBAL package -dependencies: - -.. code-block:: php - - $ git submodule init - $ git submodule update - -This updates your Git checkout to use the Doctrine and Doctrine -package versions that are recommended for the cloned Master version -of Doctrine 2. - -See the configuration chapter on how to configure a Github -installation of Doctrine with regards to autoloading. - - **NOTE** - - You should not combine the Doctrine-Common, Doctrine-DBAL and - Doctrine-ORM master commits with each other in combination. The ORM - may not work with the current Common or DBAL master versions. - Instead the ORM ships with the Git Submodules that are required. - - -Subversion -~~~~~~~~~~ - - **NOTE** - - Using the SVN Mirror is not recommended. It only allows access to - the latest master commit and does not automatically fetch the - submodules. - - -If you prefer subversion you can also checkout the code from -GitHub.com through the subversion protocol: - -.. code-block:: php - - $ svn co http://svn.github.com/doctrine/doctrine2.git doctrine2 - -However this only allows you to check out the current master of -Doctrine 2, without the Common and DBAL dependencies. You have to -grab them yourself, but might run into version incompatibilities -between the different master branches of Common, DBAL and ORM. - -Sandbox Quickstart ------------------- - - **NOTE** The sandbox is only available via the Doctrine2 Github - Repository or soon as a separate download on the downloads page. - You will find it in the $root/tools/sandbox folder. - - -The sandbox is a pre-configured environment for evaluating and -playing with Doctrine 2. - -Overview -~~~~~~~~ - -After navigating to the sandbox directory, you should see the -following structure: - -.. code-block:: php - - sandbox/ - Entities/ - Address.php - User.php - xml/ - Entities.Address.dcm.xml - Entities.User.dcm.xml - yaml/ - Entities.Address.dcm.yml - Entities.User.dcm.yml - cli-config.php - doctrine - doctrine.php - index.php - -Here is a short overview of the purpose of these folders and -files: - - -- The ``Entities`` folder is where any model classes are created. - Two example entities are already there. -- The ``xml`` folder is where any XML mapping files are created - (if you want to use XML mapping). Two example mapping documents for - the 2 example entities are already there. -- The ``yaml`` folder is where any YAML mapping files are created - (if you want to use YAML mapping). Two example mapping documents - for the 2 example entities are already there. -- The ``cli-config.php`` contains bootstrap code for a - configuration that is used by the Console tool ``doctrine`` - whenever you execute a task. -- ``doctrine``/``doctrine.php`` is a command-line tool. -- ``index.php`` is a basic classical bootstrap file of a php - application that uses Doctrine 2. - -Mini-tutorial -~~~~~~~~~~~~~ - - -1) From within the tools/sandbox folder, run the following command - and you should see the same output. - - $ php doctrine orm:schema-tool:create Creating database schema... - Database schema created successfully! - -2) Take another look into the tools/sandbox folder. A SQLite - database should have been created with the name - ``database.sqlite``. - -3) Open ``index.php`` and at the bottom edit it so it looks like - the following: - - -.. code-block:: php - - setName('Garfield'); - $em->persist($user); - $em->flush(); - - echo "User saved!"; - - -Open index.php in your browser or execute it on the command line. -You should see the output "User saved!". - - -4) Inspect the SQLite database. Again from within the tools/sandbox - folder, execute the following command: - - $ php doctrine dbal:run-sql "select \* from users" - - -You should get the following output: - -.. code-block:: php - - array(1) { - [0]=> - array(2) { - ["id"]=> - string(1) "1" - ["name"]=> - string(8) "Garfield" - } - } - -You just saved your first entity with a generated ID in an SQLite -database. - - -5) Replace the contents of index.php with the following: - - -.. code-block:: php - - createQuery('select u from Entities\User u where u.name = ?1'); - $q->setParameter(1, 'Garfield'); - $garfield = $q->getSingleResult(); - - echo "Hello " . $garfield->getName() . "!"; - - -You just created your first DQL query to retrieve the user with the -name 'Garfield' from an SQLite database (Yes, there is an easier -way to do it, but we wanted to introduce you to DQL at this point. -Can you **find** the easier way?). - - **TIP** When you create new model classes or alter existing ones - you can recreate the database schema with the command - ``doctrine orm:schema-tool --drop`` followed by - ``doctrine orm:schema-tool --create``. - - - -6) Explore Doctrine 2! - -Instead of reading through the reference manual we also recommend to look at the tutorials: - -:doc:`Getting Started Tutorial <../tutorials/getting-started>` - - diff --git a/en/reference/tools.rst b/en/reference/tools.rst index 69babfc27..6f01b0527 100644 --- a/en/reference/tools.rst +++ b/en/reference/tools.rst @@ -8,7 +8,7 @@ The Doctrine Console is a Command Line Interface tool for simplifying common tasks during the development of a project that uses Doctrine 2. -Take a look at the `Configuration ` for more +Take a look at the :doc:`Configuration ` for more information how to setup the console command. Getting Help From 63ebaea25a7e62c81ba5f64d79ba0ef0d1031507 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 16 Jun 2012 12:34:26 +0200 Subject: [PATCH 371/430] Some more work on index --- en/index.rst | 73 ++++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/en/index.rst b/en/index.rst index 4a9d74839..66658c48c 100644 --- a/en/index.rst +++ b/en/index.rst @@ -26,30 +26,31 @@ Getting Started --------------- * **Tutorial**: - :doc:`Getting Started ` | - :doc:`In 10 quick steps ` + :doc:`Getting Started ` -* **Reference**: +* **Introduction**: + :doc:`In 10 quick steps ` | :doc:`Architecture ` | + :doc:`Limitations and knowns issues ` + +* **Setup**: :doc:`Installation ` | :doc:`Configuration ` | - :doc:`Tools ` | - :doc:`Limitations and knowns issues ` + :doc:`Tools ` Mapping Objects onto a Database ------------------------------- -* **Basic Reference**: - :doc:`Objects and Fields ` | +* **Mapping**: + :doc:`Objects ` | :doc:`Associations ` | :doc:`Inheritance ` -* **Mapping Driver References**: +* **Drivers**: + :doc:`Docblock Annotations ` | :doc:`XML ` | :doc:`YAML ` | - :doc:`Docblock Annotations ` | - :doc:`PHP Mapping ` | - :doc:`Metadata Drivers ` + :doc:`PHP ` Working with Objects -------------------- @@ -60,41 +61,35 @@ Working with Objects :doc:`Events ` * **Query Reference**: - :doc:`Doctrine Query Language (DQL) ` | + :doc:`DQL ` | :doc:`QueryBuilder ` | - :doc:`Native SQL Queries ` + :doc:`Native SQL ` -* **UnitOfWork dissected**: - :doc:`Doctrine Internals explained ` | - :doc:`Owning and Inverse Side Associations ` - -* **Tutorials**: - :doc:`Indexed associations ` | - :doc:`Extra Lazy Assocations ` | - :doc:`Composite Primary Keys ` | - :doc:`Ordered associations ` | - :doc:`Pagination ` +* **Internals**: + :doc:`Internals explained ` | + :doc:`Associations ` Advanced Topics --------------- -* **Database Integration**: - :doc:`Transactions and Concurrency ` + * :doc:`Transactions and Concurrency ` + * :doc:`Filters ` + * :doc:`NamingStrategy ` + * :doc:`Improving Performance ` + * :doc:`Caching ` + * :doc:`Partial Objects ` + * :doc:`Change Tracking Policies ` + * :doc:`Best Practices ` + * :doc:`Metadata Drivers ` -* **Filtering entities**: - :doc:`Filters ` +Tutorials +--------- -* **Implementing a NamingStrategy**: - :doc:`NamingStrategy ` - -* **Performance**: - :doc:`Improving Performance ` | - :doc:`Caching ` | - :doc:`Partial Objects ` | - :doc:`Change Tracking Policies ` - -* **Best Practices**: - :doc:`Best Practices ` + * :doc:`Indexed associations ` | + * :doc:`Extra Lazy Assocations ` | + * :doc:`Composite Primary Keys ` | + * :doc:`Ordered associations ` | + * :doc:`Pagination ` Cookbook -------- @@ -102,7 +97,7 @@ Cookbook * **Patterns**: :doc:`Aggregate Fields ` | :doc:`Decorator Pattern ` | - :doc:`Strategy Pattern ` | + :doc:`Strategy Pattern ` * **DQL Extension Points**: :doc:`DQL Custom Walkers ` | From 50879db0017c2a76d30664ebeec9bcb53f70729b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 16 Jun 2012 12:39:01 +0200 Subject: [PATCH 372/430] Fixes --- en/index.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/en/index.rst b/en/index.rst index 66658c48c..5e77c9689 100644 --- a/en/index.rst +++ b/en/index.rst @@ -31,13 +31,14 @@ Getting Started * **Introduction**: :doc:`In 10 quick steps ` | :doc:`Architecture ` | - :doc:`Limitations and knowns issues ` * **Setup**: :doc:`Installation ` | :doc:`Configuration ` | :doc:`Tools ` +* :doc:`Limitations and knowns issues ` + Mapping Objects onto a Database ------------------------------- @@ -85,10 +86,10 @@ Advanced Topics Tutorials --------- - * :doc:`Indexed associations ` | - * :doc:`Extra Lazy Assocations ` | - * :doc:`Composite Primary Keys ` | - * :doc:`Ordered associations ` | + * :doc:`Indexed associations ` + * :doc:`Extra Lazy Assocations ` + * :doc:`Composite Primary Keys ` + * :doc:`Ordered associations ` * :doc:`Pagination ` Cookbook From f854a99e0a8ba6e9df76dda49d5e5d51ec3b9a60 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 16 Jun 2012 12:39:58 +0200 Subject: [PATCH 373/430] Dum --- en/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/index.rst b/en/index.rst index 5e77c9689..e250925c8 100644 --- a/en/index.rst +++ b/en/index.rst @@ -30,7 +30,7 @@ Getting Started * **Introduction**: :doc:`In 10 quick steps ` | - :doc:`Architecture ` | + :doc:`Architecture ` * **Setup**: :doc:`Installation ` | From 1a9443b55afae54530f1a3b1e3aa3f0608656169 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 16 Jun 2012 12:44:00 +0200 Subject: [PATCH 374/430] Let docs point to github for XSDs, much better to maintain for us. --- en/reference/xml-mapping.rst | 6 +++--- en/tutorials/getting-started.rst | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/en/reference/xml-mapping.rst b/en/reference/xml-mapping.rst index 5fbfdd1ff..d82c82fab 100644 --- a/en/reference/xml-mapping.rst +++ b/en/reference/xml-mapping.rst @@ -21,7 +21,7 @@ setup for the latest code in trunk. + https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd"> ... @@ -99,7 +99,7 @@ of several common elements: + http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd"> @@ -733,7 +733,7 @@ entity relationship. You can define this in XML with the "association-key" attri + http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd"> diff --git a/en/tutorials/getting-started.rst b/en/tutorials/getting-started.rst index 5788d5c6f..5a6bff118 100644 --- a/en/tutorials/getting-started.rst +++ b/en/tutorials/getting-started.rst @@ -479,7 +479,7 @@ the most simple one: + http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd"> @@ -567,7 +567,7 @@ We then go on specifying the definition of a Bug: + http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd"> @@ -689,7 +689,7 @@ The last missing definition is that of the User entity: + http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd"> @@ -1441,7 +1441,7 @@ we have to adjust the metadata slightly. + http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd"> From edb7950be85cf2ffbe2effd70e2d9c352c6983eb Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 16 Jun 2012 13:34:23 +0200 Subject: [PATCH 375/430] Getting STarted: Code, Model, Database first --- en/index.rst | 4 ++- en/tutorials/getting-started-database.rst | 27 ++++++++++++++++++ en/tutorials/getting-started-models.rst | 24 ++++++++++++++++ en/tutorials/getting-started.rst | 34 +++++++++++++++-------- 4 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 en/tutorials/getting-started-database.rst create mode 100644 en/tutorials/getting-started-models.rst diff --git a/en/index.rst b/en/index.rst index e250925c8..e6c9c070e 100644 --- a/en/index.rst +++ b/en/index.rst @@ -26,7 +26,9 @@ Getting Started --------------- * **Tutorial**: - :doc:`Getting Started ` + :doc:`Code First ` | + :doc:`Model First ` | + :doc:`Database First ` * **Introduction**: :doc:`In 10 quick steps ` | diff --git a/en/tutorials/getting-started-database.rst b/en/tutorials/getting-started-database.rst new file mode 100644 index 000000000..c96c90158 --- /dev/null +++ b/en/tutorials/getting-started-database.rst @@ -0,0 +1,27 @@ +Getting Started: Database First +=============================== + +.. note:: *Development Workflows* + + When you :doc:`Code First `, you + start with developing Objects and then map them onto your database. When + you :doc:`Model First `, you are modelling your application using tools (for + example UML) and generate database schema and PHP code from this model. + When you have a :doc:`Database First `, then you already have a database schema + and generate the correspdongin PHP code from it. + +.. note:: + + This getting started guide is in development. + +Development of new applications often starts with an existing database schema. +When the database schema is the starting point for your application, then +development is said to use the *Database First* approach to Doctrine. + +In this workflow you would always do changes to the database schema and then +regenerate the PHP code to use with this schema. You need a flexible +code-generator for this task and up to Doctrine 2.2, the code generator hasn't +been flexible enough to achieve this. + +We spinned of a subproject Doctrine CodeGenerator that will fill this gap and +allow you to do *Database First* development. diff --git a/en/tutorials/getting-started-models.rst b/en/tutorials/getting-started-models.rst new file mode 100644 index 000000000..4bf3cec16 --- /dev/null +++ b/en/tutorials/getting-started-models.rst @@ -0,0 +1,24 @@ +Getting Started: Model First +============================ + +.. note:: *Development Workflows* + + When you :doc:`Code First `, you + start with developing Objects and then map them onto your database. When + you :doc:`Model First `, you are modelling your application using tools (for + example UML) and generate database schema and PHP code from this model. + When you have a :doc:`Database First `, then you already have a database schema + and generate the correspdongin PHP code from it. + +.. note:: + + This getting started guide is in development. + +There are applications when you start with a high-level description of the +model using modelling tools such as UML. Modelling tools could also be Excel, +XML or CSV files that describe the model in some structured way. If your +application is using a modelling tool, then the development workflow is said to +be a *Model First* approach to Doctrine2. + +In this workflow you always change the model description and then regenerate +both PHP code and database schema from this model. diff --git a/en/tutorials/getting-started.rst b/en/tutorials/getting-started.rst index 5a6bff118..d59acace1 100644 --- a/en/tutorials/getting-started.rst +++ b/en/tutorials/getting-started.rst @@ -1,17 +1,29 @@ -Getting Started -=============== +Getting Started: Code First +=========================== -Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides transparent persistence for PHP objects. -It uses the Data Mapper pattern at the heart of this project, aiming for a complete separation of -the domain/business logic from the persistence in a relational -database management system. The benefit of Doctrine for the -programmer is the ability to focus solely on the object-oriented business logic and -worry about persistence only as a secondary task. This doesn't mean -persistence is not important to Doctrine 2, however it is our -belief that there are considerable benefits for object-oriented -programming if persistence and entities are kept perfectly +.. note:: *Development Workflows* + + When you :doc:`Code First `, you + start with developing Objects and then map them onto your database. When + you :doc:`Model First `, you are modelling your application using tools (for + example UML) and generate database schema and PHP code from this model. + When you have a :doc:`Database First `, then you already have a database schema + and generate the correspdongin PHP code from it. + +Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides +transparent persistence for PHP objects. It uses the Data Mapper pattern at +the heart of this project, aiming for a complete separation of the +domain/business logic from the persistence in a relational database management +system. The benefit of Doctrine for the programmer is the ability to focus +solely on the object-oriented business logic and worry about persistence only +as a secondary task. This doesn't mean persistence is not important to Doctrine +2, however it is our belief that there are considerable benefits for +object-oriented programming if persistence and entities are kept perfectly separated. +Starting with the object-oriented model is called the *Code First* approach to +Doctrine. + .. note:: The code of this tutorial is `available on Github `_. From 6e78973eec886212787628db8d10e40d75d85d8d Mon Sep 17 00:00:00 2001 From: Dinduks Date: Mon, 18 Jun 2012 17:42:49 +0200 Subject: [PATCH 376/430] Add a missing section in the Configuration manual --- en/reference/configuration.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index 679997be8..f5b640fe5 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -120,6 +120,13 @@ setup methods: $paths = array("/path/to/entities-or-mapping-files"); $isDevMode = false; + $dbParams = array( + 'driver' => 'pdo_mysql', + 'user' => 'root', + 'password' => '', + 'dbname' => 'foo', + ); + $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode); $em = EntityManager::create($dbParams, $config); From 458b0df39ea33066b7f573bdfbf3c7d9c75f700a Mon Sep 17 00:00:00 2001 From: Dinduks Date: Mon, 18 Jun 2012 18:43:10 +0200 Subject: [PATCH 377/430] Add a link to the connection configuration --- en/reference/configuration.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index f5b640fe5..2851cf79a 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -120,6 +120,7 @@ setup methods: $paths = array("/path/to/entities-or-mapping-files"); $isDevMode = false; + // the connection configuration $dbParams = array( 'driver' => 'pdo_mysql', 'user' => 'root', @@ -141,6 +142,11 @@ These setup commands make several assumptions: - If `$devMode` is false, set ``setAutoGenerateProxyClasses(false)`` - If third argument `$proxyDir` is not set, use the systems temporary directory. +.. note:: + + You can learn more about the connection configuration in the + `Doctrine DBAL connection configuration reference `_. + Full Configuration Example ~~~~~~~~~~~~~~~~~~~~~~~~~~ From bc76f330927022c98540652ab522207518472274 Mon Sep 17 00:00:00 2001 From: Douglas Greenshields Date: Mon, 18 Jun 2012 19:21:44 +0100 Subject: [PATCH 378/430] added missing JoinColumn node for xml and yaml for many-to-one unidirectional mapping --- en/reference/association-mapping.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst index 91e5c7149..aebfc5fa1 100644 --- a/en/reference/association-mapping.rst +++ b/en/reference/association-mapping.rst @@ -359,7 +359,9 @@ with the following: - + + + @@ -370,6 +372,9 @@ with the following: manyToOne: address: targetEntity: Address + joinColumn: + name: address_id + referencedColumnName: id .. note:: From de9f053cfb6cc33e84aace8ae1bbfaac0153fae9 Mon Sep 17 00:00:00 2001 From: Richard Shank Date: Wed, 20 Jun 2012 03:41:20 -0700 Subject: [PATCH 379/430] Add xml code block for OrderBy --- en/tutorials/ordered-associations.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/en/tutorials/ordered-associations.rst b/en/tutorials/ordered-associations.rst index c34b01ce7..1f84ea0e6 100644 --- a/en/tutorials/ordered-associations.rst +++ b/en/tutorials/ordered-associations.rst @@ -27,6 +27,18 @@ can specify the ``@OrderBy`` in the following way: private $groups; } +.. code-block:: xml + + + + + + + + + + + The DQL Snippet in OrderBy is only allowed to consist of unqualified, unquoted field names and of an optional ASC/DESC positional statement. Multiple Fields are separated by a comma (,). From b5e11259e1d7c2bc14509e971c4642fb15e91153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Perrin?= Date: Thu, 28 Jun 2012 15:34:58 +0200 Subject: [PATCH 380/430] Fix typo in the "Getting started database" tutorial --- en/tutorials/getting-started-database.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/tutorials/getting-started-database.rst b/en/tutorials/getting-started-database.rst index c96c90158..77392e473 100644 --- a/en/tutorials/getting-started-database.rst +++ b/en/tutorials/getting-started-database.rst @@ -8,7 +8,7 @@ Getting Started: Database First you :doc:`Model First `, you are modelling your application using tools (for example UML) and generate database schema and PHP code from this model. When you have a :doc:`Database First `, then you already have a database schema - and generate the correspdongin PHP code from it. + and generate the corresponding PHP code from it. .. note:: From 69432441074daf02091423f2a7c54ef7cd7e8507 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 4 Jul 2012 22:15:36 +0200 Subject: [PATCH 381/430] Fix docs --- en/reference/dql-doctrine-query-language.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index a80b6f6ef..2ad6f5faf 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -667,7 +667,9 @@ We will register the function by calling and can then use it: .. code-block:: php getConfiguration(); + $config->registerNumericFunction('FLOOR', 'MyProject\Query\MysqlFloor'); + $dql = "SELECT FLOOR(person.salary * 1.75) FROM CompanyPerson person"; Querying Inherited Classes From 15c9f10bc1597a17768b815d186830ba4b06470a Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 7 Jul 2012 18:25:45 +0200 Subject: [PATCH 382/430] Make composer subtitle to clarify this section applies to Composer only --- en/reference/configuration.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index 2851cf79a..cd4ea8dfc 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -12,6 +12,9 @@ roughly exists of four steps: Class loading ------------- +Composer +^^^^^^^^ + Autoloading is taken care of by Composer. You just have to include the composer autoload file in your project: .. code-block:: php From 610295f875bcde46b61c95ca9b5d84a23d0bf9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesc=20Ros=C3=A0s?= Date: Sun, 8 Jul 2012 21:30:00 +0200 Subject: [PATCH 383/430] Document preFlush and postFlush events --- en/reference/events.rst | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/en/reference/events.rst b/en/reference/events.rst index dc4f40fe0..dac8ce557 100644 --- a/en/reference/events.rst +++ b/en/reference/events.rst @@ -163,9 +163,13 @@ the life-time of their registered entities. - loadClassMetadata - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (annotations/xml/yaml). +- preFlush - The preFlush event occurs at the very beginning of a flush + operation. This event is not a lifecycle callback. - onFlush - The onFlush event occurs after the change-sets of all managed entities are computed. This event is not a lifecycle callback. +- postFlush - The postFlush event occurs at the end of a flush operation. This + event is not a lifecycle callback. - onClear - The onClear event occurs when the EntityManager#clear() operation is invoked, after all references to entities have been removed from the unit of work. @@ -420,6 +424,27 @@ There are no restrictions to what methods can be called inside the ``preRemove`` event, except when the remove method itself was called during a flush operation. +preFlush +~~~~~~~~ + +``preFlush`` is called at ``EntityManager#flush()`` before +anything else. ``EntityManager#flush()`` can be called safely +inside its listeners. + +.. code-block:: php + + recomputeSingleEntityChangeSet($classMetadata, $entity)``. +postFlush +~~~~~~~~~ + +``postFlush`` is called at the end of ``EntityManager#flush()`` before the +lists of scheduled changes are cleared. ``EntityManager#flush()`` can be +called safely inside its listeners. + +.. code-block:: php + + Date: Mon, 9 Jul 2012 10:09:28 +0200 Subject: [PATCH 384/430] fix typo on namespace --- en/cookbook/working-with-datetime.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/cookbook/working-with-datetime.rst b/en/cookbook/working-with-datetime.rst index c8620dccf..35858085f 100644 --- a/en/cookbook/working-with-datetime.rst +++ b/en/cookbook/working-with-datetime.rst @@ -83,7 +83,7 @@ the UTC time at the time of the booking and the timezone the event happend in. namespace DoctrineExtensions\DBAL\Types; - use Doctrine\DBAL\Platforms\AbtractPlatform; + use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; class UTCDateTimeType extends DateTimeType From 9b2f8dca640b63245a7fbaeed616dd9418221d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Perrin?= Date: Wed, 11 Jul 2012 11:51:29 +0200 Subject: [PATCH 385/430] [Index] Small typo fix --- en/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/index.rst b/en/index.rst index e6c9c070e..e6e90f179 100644 --- a/en/index.rst +++ b/en/index.rst @@ -89,7 +89,7 @@ Tutorials --------- * :doc:`Indexed associations ` - * :doc:`Extra Lazy Assocations ` + * :doc:`Extra Lazy Associations ` * :doc:`Composite Primary Keys ` * :doc:`Ordered associations ` * :doc:`Pagination ` From f0cc192d7b615908903a0255851176915aa0f23c Mon Sep 17 00:00:00 2001 From: Adel Date: Thu, 19 Jul 2012 16:46:56 +0200 Subject: [PATCH 386/430] Fix typo --- en/cookbook/working-with-datetime.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/cookbook/working-with-datetime.rst b/en/cookbook/working-with-datetime.rst index 35858085f..c0308f186 100644 --- a/en/cookbook/working-with-datetime.rst +++ b/en/cookbook/working-with-datetime.rst @@ -74,7 +74,7 @@ that even allows correct date-time handling with timezones: 3. Save the Timezone in the Entity for persistence. Say we have an application for an international postal company and employees insert events regarding postal-package -around the world, in their current timezones. To determine the exact time an event occoured means to save both +around the world, in their current timezones. To determine the exact time an event occurred means to save both the UTC time at the time of the booking and the timezone the event happend in. .. code-block:: php From 7c79985460bd7524a26d246ad0633074dda8ccb7 Mon Sep 17 00:00:00 2001 From: Ben Lumley Date: Wed, 25 Jul 2012 23:31:30 +0200 Subject: [PATCH 387/430] Fix missing : --- en/reference/association-mapping.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst index 91e5c7149..d800369ea 100644 --- a/en/reference/association-mapping.rst +++ b/en/reference/association-mapping.rst @@ -295,7 +295,7 @@ The following example sets up such a unidirectional one-to-many association: joinColumns: user_id: referencedColumnName: id - inverseJoinColumns + inverseJoinColumns: phonenumber_id: referencedColumnName: id unique: true From 26608fed5ace883646869b0b51cae0fcb614cca7 Mon Sep 17 00:00:00 2001 From: Luis Cordova Date: Sun, 29 Jul 2012 00:39:24 -0500 Subject: [PATCH 388/430] [DDC-1872] add tutorial for override annotations mappings --- en/index.rst | 1 + en/toc.rst | 1 + .../override-mappings-with-annotations.rst | 88 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 en/tutorials/override-mappings-with-annotations.rst diff --git a/en/index.rst b/en/index.rst index e6e90f179..f066b5bc3 100644 --- a/en/index.rst +++ b/en/index.rst @@ -93,6 +93,7 @@ Tutorials * :doc:`Composite Primary Keys ` * :doc:`Ordered associations ` * :doc:`Pagination ` + * :doc:`Override Mappings With Annotations ` Cookbook -------- diff --git a/en/toc.rst b/en/toc.rst index 5cdbe90c7..9e9edd744 100644 --- a/en/toc.rst +++ b/en/toc.rst @@ -13,6 +13,7 @@ Tutorials tutorials/composite-primary-keys tutorials/ordered-associations tutorials/in-ten-quick-steps + tutorials/override-mappings-with-annotations Reference Guide --------------- diff --git a/en/tutorials/override-mappings-with-annotations.rst b/en/tutorials/override-mappings-with-annotations.rst new file mode 100644 index 000000000..eaf316213 --- /dev/null +++ b/en/tutorials/override-mappings-with-annotations.rst @@ -0,0 +1,88 @@ +Override Mappings With Annotations +---------------------------------- + +Sometimes there is a need to persist entities but override all or part of the +mapping metadata. Sometimes also the mapping to override comes from entities +using traits where the traits have mapping metadata. +This tutorial explains how to override mapping metadata, +i.e. attributes and associations metadata in particular. The example here shows +the overriding of a class that uses a trait but is similar when extending a base +class as shown at the end of this tutorial. + +Suppose we have a class ExampleEntityWithOverride. This class uses trait ExampleTrait: + +.. code-block:: php + + Date: Sun, 29 Jul 2012 23:33:18 -0500 Subject: [PATCH 389/430] [DDC-1872] address comments from beberlei --- en/index.rst | 2 +- en/toc.rst | 2 +- ...> override-field-association-mappings-in-subclasses.rst} | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) rename en/tutorials/{override-mappings-with-annotations.rst => override-field-association-mappings-in-subclasses.rst} (94%) diff --git a/en/index.rst b/en/index.rst index f066b5bc3..9536ee56d 100644 --- a/en/index.rst +++ b/en/index.rst @@ -93,7 +93,7 @@ Tutorials * :doc:`Composite Primary Keys ` * :doc:`Ordered associations ` * :doc:`Pagination ` - * :doc:`Override Mappings With Annotations ` + * :doc:`Override Field/Association Mappings In Subclasses ` Cookbook -------- diff --git a/en/toc.rst b/en/toc.rst index 9e9edd744..73f1f25b9 100644 --- a/en/toc.rst +++ b/en/toc.rst @@ -13,7 +13,7 @@ Tutorials tutorials/composite-primary-keys tutorials/ordered-associations tutorials/in-ten-quick-steps - tutorials/override-mappings-with-annotations + tutorials/override-field-association-mappings-in-subclasses Reference Guide --------------- diff --git a/en/tutorials/override-mappings-with-annotations.rst b/en/tutorials/override-field-association-mappings-in-subclasses.rst similarity index 94% rename from en/tutorials/override-mappings-with-annotations.rst rename to en/tutorials/override-field-association-mappings-in-subclasses.rst index eaf316213..792d97887 100644 --- a/en/tutorials/override-mappings-with-annotations.rst +++ b/en/tutorials/override-field-association-mappings-in-subclasses.rst @@ -1,5 +1,5 @@ -Override Mappings With Annotations ----------------------------------- +Override Field Association Mappings In Subclasses +------------------------------------------------- Sometimes there is a need to persist entities but override all or part of the mapping metadata. Sometimes also the mapping to override comes from entities @@ -86,3 +86,5 @@ The case for just extending a class would be just the same but: { // ... } + +Overriding is also supported via XML and YAML. \ No newline at end of file From 5e9255dda8696459cf97e5df428d48163a12b94d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 31 Jul 2012 23:20:25 +0200 Subject: [PATCH 390/430] Reorganize query builder docs to put the useful stuf fon top. --- en/reference/query-builder.rst | 341 ++++++++++++++++++--------------- 1 file changed, 182 insertions(+), 159 deletions(-) diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst index c0ae0e660..6470ad656 100644 --- a/en/reference/query-builder.rst +++ b/en/reference/query-builder.rst @@ -76,32 +76,120 @@ STATE\_DIRTY. One ``QueryBuilder`` can be in two different states: Working with QueryBuilder ~~~~~~~~~~~~~~~~~~~~~~~~~ -All helper methods in ``QueryBuilder`` actually rely on a single -one: ``add()``. This method is responsible of building every piece -of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and -``$append`` (default=false) +High level API methods +^^^^^^^^^^^^^^^^^^^^^^ -- ``$dqlPartName``: Where the ``$dqlPart`` should be placed. - Possible values: select, from, where, groupBy, having, orderBy -- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts - a string or any instance of ``Doctrine\ORM\Query\Expr\*`` -- ``$append``: Optional flag (default=false) if the ``$dqlPart`` - should override all previously defined items in ``$dqlPartName`` or - not - -- +To simplify even more the way you build a query in Doctrine, we can take +advantage of what we call Helper methods. For all base code, there +is a set of useful methods to simplify a programmer's life. To +illustrate how to work with them, here is the same example 6 +re-written using ``QueryBuilder`` helper methods: .. code-block:: php add('select', 'u') - ->add('from', 'User u') - ->add('where', 'u.id = ?1') - ->add('orderBy', 'u.name ASC'); + $qb->select('u') + ->from('User', 'u') + ->where('u.id = ?1') + ->orderBy('u.name', 'ASC'); + +``QueryBuilder`` helper methods are considered the standard way to +build DQL queries. Although it is supported, it should be avoided +to use string based queries and greatly encouraged to use +``$qb->expr()->*`` methods. Here is a converted example 8 to +suggested standard way to build queries: + +.. code-block:: php + + select(array('u')) // string 'u' is converted to array internally + ->from('User', 'u') + ->where($qb->expr()->orX( + $qb->expr()->eq('u.id', '?1'), + $qb->expr()->like('u.nickname', '?2') + )) + ->orderBy('u.surname', 'ASC')); + +Here is a complete list of helper methods available in ``QueryBuilder``: + +.. code-block:: php + + select('u') + // Example - $qb->select(array('u', 'p')) + // Example - $qb->select($qb->expr()->select('u', 'p')) + public function select($select = null); + + // Example - $qb->delete('User', 'u') + public function delete($delete = null, $alias = null); + + // Example - $qb->update('Group', 'g') + public function update($update = null, $alias = null); + + // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold')) + // Example - $qb->set('u.numChilds', 'u.numChilds + ?1') + // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1')) + public function set($key, $value); + + // Example - $qb->from('Phonenumber', 'p') + public function from($from, $alias = null); + + // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1')) + // Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1') + public function innerJoin($join, $alias = null, $conditionType = null, $condition = null); + + // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55)) + // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55') + public function leftJoin($join, $alias = null, $conditionType = null, $condition = null); + + // NOTE: ->where() overrides all previously set conditions + // + // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2')) + // Example - $qb->where($qb->expr()->andX($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2'))) + // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2') + public function where($where); + + // Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0')) + public function andWhere($where); + + // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10)); + public function orWhere($where); + + // NOTE: -> groupBy() overrides all previously set grouping conditions + // + // Example - $qb->groupBy('u.id') + public function groupBy($groupBy); + + // Example - $qb->addGroupBy('g.name') + public function addGroupBy($groupBy); + + // NOTE: -> having() overrides all previously set having conditions + // + // Example - $qb->having('u.salary >= ?1') + // Example - $qb->having($qb->expr()->gte('u.salary', '?1')) + public function having($having); + + // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0)) + public function andHaving($having); + + // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100')) + public function orHaving($having); + + // NOTE: -> orderBy() overrides all previously set ordering conditions + // + // Example - $qb->orderBy('u.surname', 'DESC') + public function orderBy($sort, $order = null); + + // Example - $qb->addOrderBy('u.firstName') + public function addOrderBy($sort, $order = null); // Default $order = 'ASC' + } Binding parameters to your query ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -117,11 +205,10 @@ allowed. Binding parameters can simply be achieved as follows: add('select', 'u') - ->add('from', 'User u') - ->add('where', 'u.id = ?1') - ->add('orderBy', 'u.name ASC'); + $qb->select('u') + ->from('User u') + ->where('u.id = ?1') + ->orderBy('u.name ASC'); ->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100 You are not forced to enumerate your placeholders as the @@ -132,16 +219,21 @@ alternative syntax is available: add('select', 'u') - ->add('from', 'User u') - ->add('where', 'u.id = :identifier') - ->add('orderBy', 'u.name ASC'); + $qb->select('u') + ->from('User u') + ->where('u.id = :identifier') + ->orderBy('u.name ASC'); ->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100 Note that numeric placeholders start with a ? followed by a number while the named placeholders start with a : followed by a string. +Calling ``setParameter()`` automatically infers which type you are setting as +value. This works for integers, arrays of strings/integers, DateTime instances +and for managed entities. If you want to set a type explicitly you can call +the third argument to ``setParameter()`` explicitly. It accepts either a PDO +type or a DBAL Type name for conversion. + If you've got several parameters to bind to your query, you can also use setParameters() instead of setParameter() with the following syntax: @@ -163,13 +255,30 @@ mentioned syntax with "getParameter()" or "getParameters()": // $qb instanceof QueryBuilder // See example above - $params = $qb->getParameters(array(1, 2)); + $params = $qb->getParameters(); + // $params instanceof \Doctrine\Common\Collections\ArrayCollection + // Equivalent to - $param = array($qb->getParameter(1), $qb->getParameter(2)); + $param = $qb->getParameter(1); + // $param instanceof \Doctrine\ORM\Query\Parameter Note: If you try to get a parameter that was not bound yet, getParameter() simply returns NULL. +The API of a Query Parameter is: + +.. code-block:: php + + namespace Doctrine\ORM\Query; + + class Parameter + { + public function getName(); + public function getValue(); + public function getType(); + public function setValue($value, $type = null); + } + Limiting the Result ^^^^^^^^^^^^^^^^^^^ @@ -214,29 +323,6 @@ a querybuilder instance into a Query object: $scalar = $query->getScalarResult(); $singleScalar = $query->getSingleScalarResult(); -Expr\* classes -^^^^^^^^^^^^^^ - -When you call ``add()`` with string, it internally evaluates to an -instance of ``Doctrine\ORM\Query\Expr\Expr\*`` class. Here is the -same query of example 6 written using -``Doctrine\ORM\Query\Expr\Expr\*`` classes: - -.. code-block:: php - - add('select', new Expr\Select(array('u'))) - ->add('from', new Expr\From('User', 'u')) - ->add('where', new Expr\Comparison('u.id', '=', '?1')) - ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); - -Of course this is the hardest way to build a DQL query in Doctrine. -To simplify some of these efforts, we introduce what we call as -``Expr`` helper class. - The Expr class ^^^^^^^^^^^^^^ @@ -393,125 +479,62 @@ complete list of supported helper methods available: public function countDistinct($x); // Returns Expr\Func } -Helper methods -^^^^^^^^^^^^^^ -Until now we have described the lowest level (thought of as the +Low Level API +^^^^^^^^^^^^^ + +Now we have describe the low level (thought of as the hardcore method) of creating queries. It may be useful to work at this level for optimization purposes, but most of the time it is -preferred to work at a higher level of abstraction. To simplify -even more the way you build a query in Doctrine, we can take -advantage of what we call Helper methods. For all base code, there -is a set of useful methods to simplify a programmer's life. To -illustrate how to work with them, here is the same example 6 -re-written using ``QueryBuilder`` helper methods: +preferred to work at a higher level of abstraction. + +All helper methods in ``QueryBuilder`` actually rely on a single +one: ``add()``. This method is responsible of building every piece +of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and +``$append`` (default=false) + + +- ``$dqlPartName``: Where the ``$dqlPart`` should be placed. + Possible values: select, from, where, groupBy, having, orderBy +- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts + a string or any instance of ``Doctrine\ORM\Query\Expr\*`` +- ``$append``: Optional flag (default=false) if the ``$dqlPart`` + should override all previously defined items in ``$dqlPartName`` or + not + +- .. code-block:: php select('u') - ->from('User', 'u') - ->where('u.id = ?1') - ->orderBy('u.name', 'ASC'); + // example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support + $qb->add('select', 'u') + ->add('from', 'User u') + ->add('where', 'u.id = ?1') + ->add('orderBy', 'u.name ASC'); -``QueryBuilder`` helper methods are considered the standard way to -build DQL queries. Although it is supported, it should be avoided -to use string based queries and greatly encouraged to use -``$qb->expr()->*`` methods. Here is a converted example 8 to -suggested standard way to build queries: +Expr\* classes +^^^^^^^^^^^^^^ + +When you call ``add()`` with string, it internally evaluates to an +instance of ``Doctrine\ORM\Query\Expr\Expr\*`` class. Here is the +same query of example 6 written using +``Doctrine\ORM\Query\Expr\Expr\*`` classes: .. code-block:: php - select(array('u')) // string 'u' is converted to array internally - ->from('User', 'u') - ->where($qb->expr()->orX( - $qb->expr()->eq('u.id', '?1'), - $qb->expr()->like('u.nickname', '?2') - )) - ->orderBy('u.surname', 'ASC')); - -Here is a complete list of helper methods available in -``QueryBuilder``: - -.. code-block:: php - - select('u') - // Example - $qb->select(array('u', 'p')) - // Example - $qb->select($qb->expr()->select('u', 'p')) - public function select($select = null); - - // Example - $qb->delete('User', 'u') - public function delete($delete = null, $alias = null); - - // Example - $qb->update('Group', 'g') - public function update($update = null, $alias = null); - - // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold')) - // Example - $qb->set('u.numChilds', 'u.numChilds + ?1') - // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1')) - public function set($key, $value); - - // Example - $qb->from('Phonenumber', 'p') - public function from($from, $alias = null); - - // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1')) - // Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1') - public function innerJoin($join, $alias = null, $conditionType = null, $condition = null); - - // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55)) - // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55') - public function leftJoin($join, $alias = null, $conditionType = null, $condition = null); - - // NOTE: ->where() overrides all previously set conditions - // - // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2')) - // Example - $qb->where($qb->expr()->andX($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2'))) - // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2') - public function where($where); - - // Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0')) - public function andWhere($where); - - // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10)); - public function orWhere($where); - - // NOTE: -> groupBy() overrides all previously set grouping conditions - // - // Example - $qb->groupBy('u.id') - public function groupBy($groupBy); - - // Example - $qb->addGroupBy('g.name') - public function addGroupBy($groupBy); - - // NOTE: -> having() overrides all previously set having conditions - // - // Example - $qb->having('u.salary >= ?1') - // Example - $qb->having($qb->expr()->gte('u.salary', '?1')) - public function having($having); - - // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0)) - public function andHaving($having); - - // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100')) - public function orHaving($having); - - // NOTE: -> orderBy() overrides all previously set ordering conditions - // - // Example - $qb->orderBy('u.surname', 'DESC') - public function orderBy($sort, $order = null); - - // Example - $qb->addOrderBy('u.firstName') - public function addOrderBy($sort, $order = null); // Default $order = 'ASC' - } + // example7: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder using Expr\* instances + $qb->add('select', new Expr\Select(array('u'))) + ->add('from', new Expr\From('User', 'u')) + ->add('where', new Expr\Comparison('u.id', '=', '?1')) + ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); +Of course this is the hardest way to build a DQL query in Doctrine. +To simplify some of these efforts, we introduce what we call as +``Expr`` helper class. From 71d3f5852dd870997597dcb471b86c54f61382f8 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 31 Jul 2012 23:25:33 +0200 Subject: [PATCH 391/430] Move Mapping Validation into Configuration section --- en/reference/association-mapping.rst | 47 --------------------------- en/reference/configuration.rst | 48 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst index d800369ea..8c20693af 100644 --- a/en/reference/association-mapping.rst +++ b/en/reference/association-mapping.rst @@ -1136,50 +1136,3 @@ been associated with an EntityManager yet: $group = $entityManager->find('Group', $groupId); $user = new User(); $user->getGroups()->add($group); - -Runtime vs Development Mapping Validation ------------------------------------------ - -For performance reasons Doctrine 2 has to skip some of the -necessary validation of association mappings. You have to execute -this validation in your development workflow to verify the -associations are correctly defined. - -You can either use the Doctrine Command Line Tool: - -.. code-block:: php - - doctrine orm:validate-schema - -Or you can trigger the validation manually: - -.. code-block:: php - - validateMapping(); - - if (count($errors) > 0) { - // Lots of errors! - echo implode("\n\n", $errors); - } - -If the mapping is invalid the errors array contains a positive -number of elements with error messages. - -.. warning:: - - One mapping option that is not validated is the use of the referenced column name. - It has to point to the equivalent primary key otherwise Doctrine will not work. - -.. note:: - - One common error is to use a backlash in front of the - fully-qualified class-name. Whenever a FQCN is represented inside a - string (such as in your mapping definitions) you have to drop the - prefix backslash. PHP does this with ``get_class()`` or Reflection - methods for backwards compatibility reasons. - - diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index cd4ea8dfc..415659733 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -581,8 +581,56 @@ In general the required code looks like this: .. code-block:: php + setCatchExceptions(true); $cli->setHelperSet($helperSet); Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli); $cli->run(); + +Runtime vs Development Mapping Validation +----------------------------------------- + +For performance reasons Doctrine 2 has to skip some of the +necessary validation of metadata mappings. You have to execute +this validation in your development workflow to verify the +associations are correctly defined. + +You can either use the Doctrine Command Line Tool: + +.. code-block:: php + + doctrine orm:validate-schema + +Or you can trigger the validation manually: + +.. code-block:: php + + validateMapping(); + + if (count($errors) > 0) { + // Lots of errors! + echo implode("\n\n", $errors); + } + +If the mapping is invalid the errors array contains a positive +number of elements with error messages. + +.. warning:: + + One mapping option that is not validated is the use of the referenced column name. + It has to point to the equivalent primary key otherwise Doctrine will not work. + +.. note:: + + One common error is to use a backlash in front of the + fully-qualified class-name. Whenever a FQCN is represented inside a + string (such as in your mapping definitions) you have to drop the + prefix backslash. PHP does this with ``get_class()`` or Reflection + methods for backwards compatibility reasons. + + From 83b509e033f994e3dfbe373b26ff45816ff33cd3 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 31 Jul 2012 23:26:40 +0200 Subject: [PATCH 392/430] Move again into tools --- en/reference/configuration.rst | 46 ---------------------------------- en/reference/tools.rst | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index 415659733..65d047b8b 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -588,49 +588,3 @@ In general the required code looks like this: Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli); $cli->run(); -Runtime vs Development Mapping Validation ------------------------------------------ - -For performance reasons Doctrine 2 has to skip some of the -necessary validation of metadata mappings. You have to execute -this validation in your development workflow to verify the -associations are correctly defined. - -You can either use the Doctrine Command Line Tool: - -.. code-block:: php - - doctrine orm:validate-schema - -Or you can trigger the validation manually: - -.. code-block:: php - - validateMapping(); - - if (count($errors) > 0) { - // Lots of errors! - echo implode("\n\n", $errors); - } - -If the mapping is invalid the errors array contains a positive -number of elements with error messages. - -.. warning:: - - One mapping option that is not validated is the use of the referenced column name. - It has to point to the equivalent primary key otherwise Doctrine will not work. - -.. note:: - - One common error is to use a backlash in front of the - fully-qualified class-name. Whenever a FQCN is represented inside a - string (such as in your mapping definitions) you have to drop the - prefix backslash. PHP does this with ``get_class()`` or Reflection - methods for backwards compatibility reasons. - - diff --git a/en/reference/tools.rst b/en/reference/tools.rst index 6f01b0527..c028bf1f5 100644 --- a/en/reference/tools.rst +++ b/en/reference/tools.rst @@ -434,6 +434,52 @@ You can also reverse engineer a database using the a useful domain model. +Runtime vs Development Mapping Validation +----------------------------------------- + +For performance reasons Doctrine 2 has to skip some of the +necessary validation of metadata mappings. You have to execute +this validation in your development workflow to verify the +associations are correctly defined. + +You can either use the Doctrine Command Line Tool: + +.. code-block:: php + + doctrine orm:validate-schema + +Or you can trigger the validation manually: + +.. code-block:: php + + validateMapping(); + + if (count($errors) > 0) { + // Lots of errors! + echo implode("\n\n", $errors); + } + +If the mapping is invalid the errors array contains a positive +number of elements with error messages. + +.. warning:: + + One mapping option that is not validated is the use of the referenced column name. + It has to point to the equivalent primary key otherwise Doctrine will not work. + +.. note:: + + One common error is to use a backlash in front of the + fully-qualified class-name. Whenever a FQCN is represented inside a + string (such as in your mapping definitions) you have to drop the + prefix backslash. PHP does this with ``get_class()`` or Reflection + methods for backwards compatibility reasons. + + Adding own commands ------------------- From eaeda36bdb934630dcde94eaee64e3065f14d0b5 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 31 Jul 2012 23:58:37 +0200 Subject: [PATCH 393/430] Add filtering collections --- en/reference/association-mapping.rst | 1 + en/reference/working-with-associations.rst | 79 ++++++++++++++++++++++ en/reference/working-with-objects.rst | 10 +++ 3 files changed, 90 insertions(+) diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst index 8c20693af..0b83b7474 100644 --- a/en/reference/association-mapping.rst +++ b/en/reference/association-mapping.rst @@ -1136,3 +1136,4 @@ been associated with an EntityManager yet: $group = $entityManager->find('Group', $groupId); $user = new User(); $user->getGroups()->add($group); + diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst index caa9266ed..fee7d6731 100644 --- a/en/reference/working-with-associations.rst +++ b/en/reference/working-with-associations.rst @@ -608,3 +608,82 @@ you have also removed the references for standing data and as well as one address reference. When flush is called not only are the references removed but both the old standing data and the one address entity are also deleted from the database. + +Filtering Collections +--------------------- + +.. filtering-collections: + +Collections have a filtering API that allows to slice parts of data from +a collection. If the collection has not been loaded from the database yet, +the filtering API can work on the SQL level to make optimized access to +large collections. + +.. code-block:: php + + find('Group', $groupId); + $userCollection = $group->getUsers(); + $expr = $userCollection->getExpressionBuilder(); + + $criteria = Criteria::create(); + $criteria->where($expr->eq("birthday", "1982-02-17")); + + $birthdayUsers = $userCollection->matching($criteria); + +The Criteria has a limited matching language that works both on the +SQL and on the PHP collection level. This means you can use collection matching +interchangeably, independent of in-memory or sql-backed collections. + +.. code-block:: php + + getRepository('MyProject\Domain\User')->findOneByNickname('romanb'); +By Criteria +~~~~~~~~~~~ + +The Repository implement the ``Doctrine\Common\Collections\Selectable`` +interface. That means you can build ``Doctrine\Common\Collections\Criteria`` +and pass them to the ``matching($criteria)`` method. + +See the :ref:`Working with Associations: Filtering collections +`. + By Eager Loading ~~~~~~~~~~~~~~~~ From 2f72219d6ef87f2b2cc7db2bc4f455cc40fc0390 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 1 Aug 2012 22:15:44 +0200 Subject: [PATCH 394/430] Finish first version of filtering api documentation --- en/reference/working-with-associations.rst | 31 +++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst index fee7d6731..2781d5cd1 100644 --- a/en/reference/working-with-associations.rst +++ b/en/reference/working-with-associations.rst @@ -627,13 +627,21 @@ large collections. $group = $entityManager->find('Group', $groupId); $userCollection = $group->getUsers(); - $expr = $userCollection->getExpressionBuilder(); - $criteria = Criteria::create(); - $criteria->where($expr->eq("birthday", "1982-02-17")); + $criteria = Criteria::create() + ->where(Criteria::expr()->eq("birthday", "1982-02-17")) + ->orderBy(array("username" => "ASC")) + ->setFirstResult(0) + ->setMaxResults(20) + ; $birthdayUsers = $userCollection->matching($criteria); +.. tip:: + + You can move the access of slices of collections into dedicated methods of + an entity. For example ``Group#getTodaysBirthdayUsers()``. + The Criteria has a limited matching language that works both on the SQL and on the PHP collection level. This means you can use collection matching interchangeably, independent of in-memory or sql-backed collections. @@ -686,4 +694,19 @@ interchangeably, independent of in-memory or sql-backed collections. public function getMaxResults(); } -You can build expressions through the ExpressionBuilder +You can build expressions through the ExpressionBuilder. It has the following +methods: + +* ``andX($arg1, $arg2, ...)`` +* ``orX($arg1, $arg2, ...)`` +* ``eq($field, $value)`` +* ``gt($field, $value)`` +* ``lt($field, $value)`` +* ``lte($field, $value)`` +* ``gte($field, $value)`` +* ``neq($field, $value)`` +* ``isNull($field)`` +* ``in($field, array $values)`` +* ``notIn($field, array $values)`` + + From f3dea276e75ca843c802bb4078781c1841ab725a Mon Sep 17 00:00:00 2001 From: Gary Hockin Date: Tue, 7 Aug 2012 13:10:25 +0200 Subject: [PATCH 395/430] Update en/tutorials/getting-started.rst Added note that tripped me up. --- en/tutorials/getting-started.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/en/tutorials/getting-started.rst b/en/tutorials/getting-started.rst index d59acace1..6d1c5431f 100644 --- a/en/tutorials/getting-started.rst +++ b/en/tutorials/getting-started.rst @@ -1431,6 +1431,11 @@ the previoiusly discussed query functionality in it: Dont forget to add a `require_once` call for this class to the bootstrap.php +.. note:: + + **require_once gotcha** + Make sure you add the require_once call for `BugRepository.php` *after* the require_once call for `bootstrap_doctrine.php`, or you will get some class not found errors. + To be able to use this query logic through ``$this->getEntityManager()->getRepository('Bug')`` we have to adjust the metadata slightly. From 711844296d602568c62e5f2f7d89915e55eaa82d Mon Sep 17 00:00:00 2001 From: Gary Hockin Date: Tue, 7 Aug 2012 13:12:09 +0200 Subject: [PATCH 396/430] Update en/tutorials/getting-started.rst Fixed failed markdown --- en/tutorials/getting-started.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/en/tutorials/getting-started.rst b/en/tutorials/getting-started.rst index 6d1c5431f..fc5228c85 100644 --- a/en/tutorials/getting-started.rst +++ b/en/tutorials/getting-started.rst @@ -1434,7 +1434,9 @@ Dont forget to add a `require_once` call for this class to the bootstrap.php .. note:: **require_once gotcha** - Make sure you add the require_once call for `BugRepository.php` *after* the require_once call for `bootstrap_doctrine.php`, or you will get some class not found errors. + + Make sure you add the require_once call for `BugRepository.php` *after* the require_once + call for `bootstrap_doctrine.php`, or you will get some class not found errors. To be able to use this query logic through ``$this->getEntityManager()->getRepository('Bug')`` we have to adjust the metadata slightly. From b6ddb5863410dd0a8d5335214ad70bdc8ffc4a97 Mon Sep 17 00:00:00 2001 From: Gary Hockin Date: Tue, 7 Aug 2012 13:26:51 +0200 Subject: [PATCH 397/430] Update en/tutorials/getting-started.rst Cleaner to remove the note, and modify the code to put the doctrine_bootstrap.php include first? --- en/tutorials/getting-started.rst | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/en/tutorials/getting-started.rst b/en/tutorials/getting-started.rst index fc5228c85..13a702c18 100644 --- a/en/tutorials/getting-started.rst +++ b/en/tutorials/getting-started.rst @@ -821,15 +821,14 @@ We also have to create a general bootstrap file for our application: getEntityManager()->getRepository('Bug')`` we have to adjust the metadata slightly. From 46be81115a9a6737da6a28f34004f7352129e0da Mon Sep 17 00:00:00 2001 From: Brett Bieber Date: Thu, 23 Aug 2012 08:41:19 -0500 Subject: [PATCH 398/430] Minor spelling correction & grammatical formatting --- en/tutorials/getting-started-database.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/en/tutorials/getting-started-database.rst b/en/tutorials/getting-started-database.rst index 77392e473..c625193ae 100644 --- a/en/tutorials/getting-started-database.rst +++ b/en/tutorials/getting-started-database.rst @@ -7,7 +7,7 @@ Getting Started: Database First start with developing Objects and then map them onto your database. When you :doc:`Model First `, you are modelling your application using tools (for example UML) and generate database schema and PHP code from this model. - When you have a :doc:`Database First `, then you already have a database schema + When you have a :doc:`Database First `, you already have a database schema and generate the corresponding PHP code from it. .. note:: @@ -18,10 +18,10 @@ Development of new applications often starts with an existing database schema. When the database schema is the starting point for your application, then development is said to use the *Database First* approach to Doctrine. -In this workflow you would always do changes to the database schema and then +In this workflow you would modify the database schema first and then regenerate the PHP code to use with this schema. You need a flexible code-generator for this task and up to Doctrine 2.2, the code generator hasn't been flexible enough to achieve this. -We spinned of a subproject Doctrine CodeGenerator that will fill this gap and +We spinned off a subproject, Doctrine CodeGenerator, that will fill this gap and allow you to do *Database First* development. From e873351624bea86e0d6136b1368349e78dc21046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesc=20Ros=C3=A0s?= Date: Thu, 23 Aug 2012 21:51:33 +0300 Subject: [PATCH 399/430] Remove "before the lists of scheduled changes are cleared" It is not true. --- en/reference/events.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/en/reference/events.rst b/en/reference/events.rst index dac8ce557..9596c0e6d 100644 --- a/en/reference/events.rst +++ b/en/reference/events.rst @@ -511,8 +511,7 @@ The following restrictions apply to the onFlush event: postFlush ~~~~~~~~~ -``postFlush`` is called at the end of ``EntityManager#flush()`` before the -lists of scheduled changes are cleared. ``EntityManager#flush()`` can be +``postFlush`` is called at the end of ``EntityManager#flush()``. ``EntityManager#flush()`` can be called safely inside its listeners. .. code-block:: php From 4e4cf33342352bfc74efeff13eaf3d8c51e12ae9 Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Thu, 30 Aug 2012 11:56:06 +0300 Subject: [PATCH 400/430] Update en/reference/working-with-associations.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarifiy that orphanRemoval works with one-to-one, one-to-many and many-to-many associations. --- en/reference/working-with-associations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/working-with-associations.rst b/en/reference/working-with-associations.rst index 2781d5cd1..253edad7e 100644 --- a/en/reference/working-with-associations.rst +++ b/en/reference/working-with-associations.rst @@ -541,7 +541,7 @@ from collections. If an Entity of type ``A`` contains references to privately owned Entities ``B`` then if the reference from ``A`` to ``B`` is removed the entity ``B`` should also be removed, because it is not used anymore. -OrphanRemoval works with both one-to-one and one-to-many associations. +OrphanRemoval works with one-to-one, one-to-many and many-to-many associations. .. note:: From 7c7118d8835252cd821b7ad113d11181859af092 Mon Sep 17 00:00:00 2001 From: Bilal Amarni Date: Thu, 30 Aug 2012 16:25:50 +0300 Subject: [PATCH 401/430] simplified interface check --- en/reference/filters.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/filters.rst b/en/reference/filters.rst index ef8b36e52..abe789412 100644 --- a/en/reference/filters.rst +++ b/en/reference/filters.rst @@ -40,7 +40,7 @@ proper quoting of parameters. public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) { // Check if the entity implements the LocalAware interface - if (!in_array("LocaleAware", $targetEntity->reflClass->getInterfaceNames())) { + if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) { return ""; } From d47b8722923a4a6c6dcea8c44503c28671eadf04 Mon Sep 17 00:00:00 2001 From: Bilal Amarni Date: Thu, 30 Aug 2012 16:35:46 +0300 Subject: [PATCH 402/430] fixed typo --- en/reference/filters.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/reference/filters.rst b/en/reference/filters.rst index ef8b36e52..88005791b 100644 --- a/en/reference/filters.rst +++ b/en/reference/filters.rst @@ -24,8 +24,8 @@ method. The method receives the ``ClassMetadata`` of the filtered entity and the table alias of the SQL table of the entity. Parameters for the query should be set on the filter object by -``SQLFilter#setParameter()``. Only parameters set via this function used in -the filters. The ``SQLFilter#getParameter()`` function takes care of the +``SQLFilter#setParameter()``. Only parameters set via this function can be used +in filters. The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters. .. code-block:: php From 0ff1f418bd7481db5ed41728ba35acf011a3a60e Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 5 Sep 2012 22:31:07 +0300 Subject: [PATCH 403/430] Update en/reference/tools.rst Fixed import as reported by manoelcampos@gmail.com --- en/reference/tools.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/tools.rst b/en/reference/tools.rst index c028bf1f5..cc087c24f 100644 --- a/en/reference/tools.rst +++ b/en/reference/tools.rst @@ -104,7 +104,7 @@ there whenever you want to access the Doctrine console. // doctrine.php - Put in your application root use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper; - use Doctrine\DBAL\Tools\Console\Helper\EntityManagerHelper; + use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper; use Doctrine\ORM\Tools\Console\ConsoleRunner; use Symfony\Component\Console\Helper\HelperSet; From c463121da8ba6dcc74c5f304e53ad9b39fb50f6e Mon Sep 17 00:00:00 2001 From: barelon Date: Tue, 18 Sep 2012 11:28:24 +0300 Subject: [PATCH 404/430] Fix some typos in annotations reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changed capitalization of the `@GeneratedValue` and `@Version` examples which incorrectly used lower case - Fixed parameter names in `@NamedNativeQuery` and `@SqlResultSetMapping` - Changed the Docblock style for the @MappedSuperclass example to the style used by all examples --- en/reference/annotations-reference.rst | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst index da79f41e5..1510d8c97 100644 --- a/en/reference/annotations-reference.rst +++ b/en/reference/annotations-reference.rst @@ -295,7 +295,7 @@ Example: /** * @Id * @Column(type="integer") - * @generatedValue(strategy="IDENTITY") + * @GeneratedValue(strategy="IDENTITY") */ protected $id = null; @@ -634,13 +634,17 @@ Example: .. code-block:: php Date: Thu, 20 Sep 2012 13:11:09 +0200 Subject: [PATCH 405/430] Added .idea for PhpStorm contributers --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2091122d0..59cd005a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ en/_exts/configurationblock.pyc build en/_build +.idea From a61afedcf9e65e26c6ea658b77778b30691995f2 Mon Sep 17 00:00:00 2001 From: Lennart Hildebrandt Date: Thu, 20 Sep 2012 13:11:36 +0200 Subject: [PATCH 406/430] Fixed typo for mapping type "array" --- en/reference/basic-mapping.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst index 932fdb417..64f670840 100644 --- a/en/reference/basic-mapping.rst +++ b/en/reference/basic-mapping.rst @@ -164,7 +164,7 @@ built-in mapping types: - ``text``: Type that maps an SQL CLOB to a PHP string. - ``object``: Type that maps a SQL CLOB to a PHP object using ``serialize()`` and ``unserialize()`` -- ``array``: Type that maps a SQL CLOB to a PHP object using +- ``array``: Type that maps a SQL CLOB to a PHP array using ``serialize()`` and ``unserialize()`` - ``float``: Type that maps a SQL Float (Double Precision) to a PHP double. *IMPORTANT*: Works only with locale settings that use From 742590d1d752cb56443efe0d8c9f4f3e0f2e8fe0 Mon Sep 17 00:00:00 2001 From: Eduardo Gulias Davis Date: Tue, 2 Oct 2012 12:15:03 +0300 Subject: [PATCH 407/430] [Association-Mapping] - Mising target-entity tag in xml format In the many to many bidirectional xml format the "target-entity" tag is missing and generates a MappingException when not included. --- en/reference/association-mapping.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst index 0b83b7474..24f13f7b9 100644 --- a/en/reference/association-mapping.rst +++ b/en/reference/association-mapping.rst @@ -706,7 +706,7 @@ one is bidirectional. - + @@ -719,7 +719,7 @@ one is bidirectional. - + From 5657e199bd70f3d5dfda4dc1faf4301367e9cb6b Mon Sep 17 00:00:00 2001 From: Per Persson Date: Mon, 15 Oct 2012 17:28:12 +0300 Subject: [PATCH 408/430] Update en/tutorials/getting-started.rst Spelling: UDPATE -> UPDATE --- en/tutorials/getting-started.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/tutorials/getting-started.rst b/en/tutorials/getting-started.rst index fc5228c85..e02f101cb 100644 --- a/en/tutorials/getting-started.rst +++ b/en/tutorials/getting-started.rst @@ -955,7 +955,7 @@ to the database. You might wonder why does this distinction between persist notification and flush exist: Doctrine 2 uses the UnitOfWork -pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one +pattern to aggregate all writes (INSERT, UPDATE, DELETE) into one single transaction, which is executed when flush is called. Using this approach the write-performance is significantly better than in a scenario where updates are done for each entity in From 05b170fe997ad9f854de4f19dd43923199a7fbb6 Mon Sep 17 00:00:00 2001 From: Drak Date: Wed, 17 Oct 2012 20:41:15 +0200 Subject: [PATCH 409/430] Add example for index definition --- en/reference/yaml-mapping.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/en/reference/yaml-mapping.rst b/en/reference/yaml-mapping.rst index da7ccfd16..89745dcb0 100644 --- a/en/reference/yaml-mapping.rst +++ b/en/reference/yaml-mapping.rst @@ -75,6 +75,9 @@ of several common elements: Doctrine\Tests\ORM\Mapping\User: type: entity table: cms_users + indexes: + name_index: + columns: [ name ] id: id: type: integer From e3bbd058f244fc4cfdf8f80c067b32a2f020f5a9 Mon Sep 17 00:00:00 2001 From: Adrien Brault Date: Thu, 18 Oct 2012 18:34:22 +0300 Subject: [PATCH 410/430] Add UUID,CUSTOM generatedValue values to annot ref --- en/reference/annotations-reference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst index 1510d8c97..ebf5a0244 100644 --- a/en/reference/annotations-reference.rst +++ b/en/reference/annotations-reference.rst @@ -285,7 +285,7 @@ Required attributes: - **strategy**: Set the name of the identifier generation strategy. - Valid values are AUTO, SEQUENCE, TABLE, IDENTITY and NONE. + Valid values are AUTO, SEQUENCE, TABLE, IDENTITY, UUID, CUSTOM and NONE. Example: From 95b8e27bc5ec26f7655a04cebba4de6edb71f297 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 23 Oct 2012 15:21:55 +0300 Subject: [PATCH 411/430] Removing removed API description from docs --- en/reference/caching.rst | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/en/reference/caching.rst b/en/reference/caching.rst index 52545d842..a008aa558 100644 --- a/en/reference/caching.rst +++ b/en/reference/caching.rst @@ -276,23 +276,6 @@ driver instance you can use the ``count()`` method. count(); -.. note:: - - In order to use ``deleteByRegex()``, ``deleteByPrefix()``, - ``deleteBySuffix()``, ``deleteAll()``, ``count()`` or ``getIds()`` - you must enable an option for the cache driver to manage your cache - IDs internally. This is necessary because APC, Memcache, etc. don't - have any advanced functionality for fetching and deleting. We add - some functionality on top of the cache drivers to maintain an index - of all the IDs stored in the cache driver so that we can allow more - granular deleting operations. - - :: - - setManageCacheIds(true); - - Namespaces ~~~~~~~~~~ From 9f898b0b90361a14566a71b9dfe9c374e4f3fd43 Mon Sep 17 00:00:00 2001 From: Arnaud Kleinpeter Date: Wed, 7 Nov 2012 22:31:40 +0100 Subject: [PATCH 412/430] Update en/tutorials/getting-started.rst --- en/tutorials/getting-started.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/tutorials/getting-started.rst b/en/tutorials/getting-started.rst index fc5228c85..a1ec74296 100644 --- a/en/tutorials/getting-started.rst +++ b/en/tutorials/getting-started.rst @@ -8,7 +8,7 @@ Getting Started: Code First you :doc:`Model First `, you are modelling your application using tools (for example UML) and generate database schema and PHP code from this model. When you have a :doc:`Database First `, then you already have a database schema - and generate the correspdongin PHP code from it. + and generate the corresponding PHP code from it. Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides transparent persistence for PHP objects. It uses the Data Mapper pattern at From 56eec8979b4674768a21123fc3b5f8817a7ca03e Mon Sep 17 00:00:00 2001 From: Squazic Date: Fri, 9 Nov 2012 11:55:34 -0500 Subject: [PATCH 413/430] Grammar fix --- en/reference/association-mapping.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst index 24f13f7b9..d68ede4b7 100644 --- a/en/reference/association-mapping.rst +++ b/en/reference/association-mapping.rst @@ -1127,7 +1127,7 @@ empty ``ArrayCollection`` in your entities constructor: } } -Now the following code will be working even if the Entity hasn't +Now the following code will work even if the Entity hasn't been associated with an EntityManager yet: .. code-block:: php From 3cee83be2bdc8886c43afadd35a3364c221f6ce0 Mon Sep 17 00:00:00 2001 From: Attila Fulop Date: Sun, 11 Nov 2012 21:19:18 +0200 Subject: [PATCH 414/430] Swapped order of path/Entities at Simplified YAML Driver According to my experience, the path has to be the key, and Namespace prefix the value in the array. --- en/reference/yaml-mapping.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/reference/yaml-mapping.rst b/en/reference/yaml-mapping.rst index 89745dcb0..9a00d08b3 100644 --- a/en/reference/yaml-mapping.rst +++ b/en/reference/yaml-mapping.rst @@ -57,8 +57,8 @@ Configuration of this client works a little bit different: '/path/to/files1', - 'OtherProject\Entities' => '/path/to/files2' + '/path/to/files1' => 'MyProject\Entities', + '/path/to/files2' => 'OtherProject\Entities' ); $driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver($namespaces); $driver->setGlobalBasename('global'); // global.orm.yml From 072a65bd2660629b60f1fc7b0b8ac1aed2dd1460 Mon Sep 17 00:00:00 2001 From: jorns Date: Fri, 16 Nov 2012 11:21:06 +0100 Subject: [PATCH 415/430] Updated @Index lower i to Upper i This fixes the error: [Semantical Error] The annotation "@Doctrine\ORM\Mapping\index" --- en/reference/annotations-reference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/annotations-reference.rst b/en/reference/annotations-reference.rst index ebf5a0244..f705cb034 100644 --- a/en/reference/annotations-reference.rst +++ b/en/reference/annotations-reference.rst @@ -351,7 +351,7 @@ Example: Date: Tue, 20 Nov 2012 05:37:43 +0100 Subject: [PATCH 416/430] Fix wrong date format in example PrePersist method --- en/reference/events.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/events.rst b/en/reference/events.rst index dac8ce557..340a3ba47 100644 --- a/en/reference/events.rst +++ b/en/reference/events.rst @@ -240,7 +240,7 @@ event occurs. /** @PrePersist */ public function doStuffOnPrePersist() { - $this->createdAt = date('Y-m-d H:m:s'); + $this->createdAt = date('Y-m-d H:i:s'); } /** @PrePersist */ From 8717088ed128be45082d1d79f9cfa89d616f7aa0 Mon Sep 17 00:00:00 2001 From: Pascal Borreli Date: Sun, 2 Dec 2012 17:58:15 +0000 Subject: [PATCH 417/430] Fixed typos --- ...value-conversion-using-custom-mapping-types.rst | 2 +- en/cookbook/decorator-pattern.rst | 4 ++-- en/cookbook/entities-in-session.rst | 2 +- en/cookbook/resolve-target-entity-listener.rst | 2 +- en/cookbook/strategy-cookbook-introduction.rst | 2 +- en/cookbook/validation-of-entities.rst | 2 +- en/cookbook/working-with-datetime.rst | 6 +++--- en/reference/association-mapping.rst | 4 ++-- en/reference/basic-mapping.rst | 2 +- en/reference/faq.rst | 12 ++++++------ en/reference/inheritance-mapping.rst | 2 +- en/reference/limitations-and-known-issues.rst | 2 +- en/reference/metadata-drivers.rst | 2 +- en/reference/native-sql.rst | 2 +- en/reference/php-mapping.rst | 8 ++++---- en/reference/tools.rst | 6 +++--- en/reference/unitofwork-associations.rst | 12 ++++++------ en/reference/unitofwork.rst | 4 ++-- en/reference/working-with-objects.rst | 4 ++-- en/tutorials/composite-primary-keys.rst | 4 ++-- en/tutorials/extra-lazy-associations.rst | 4 ++-- en/tutorials/getting-started-models.rst | 2 +- en/tutorials/getting-started.rst | 6 +++--- en/tutorials/ordered-associations.rst | 4 ++-- en/tutorials/working-with-indexed-associations.rst | 14 +++++++------- 25 files changed, 57 insertions(+), 57 deletions(-) diff --git a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst index b20582f53..bc24cd308 100644 --- a/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst +++ b/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst @@ -21,7 +21,7 @@ longitude/latitude pair to represent a geographic location. The entity ---------- -We create a simple entity whith a field ``$point`` which holds a value object +We create a simple entity with a field ``$point`` which holds a value object ``Point`` representing the latitude and longitude of the position. The entity class: diff --git a/en/cookbook/decorator-pattern.rst b/en/cookbook/decorator-pattern.rst index 38bf3ef54..ab9cf8c92 100644 --- a/en/cookbook/decorator-pattern.rst +++ b/en/cookbook/decorator-pattern.rst @@ -4,7 +4,7 @@ Persisting the Decorator Pattern .. sectionauthor:: Chris Woodford This recipe will show you a simple example of how you can use -Doctrine 2 to persist an implementaton of the +Doctrine 2 to persist an implementation of the `Decorator Pattern `_ Component @@ -114,7 +114,7 @@ use a ``MappedSuperclass`` for this. protected $decorates; /** - * intialize the decorator + * initialize the decorator * @param Component $c */ public function __construct(Component $c) diff --git a/en/cookbook/entities-in-session.rst b/en/cookbook/entities-in-session.rst index 48ea12249..2fd771e5d 100644 --- a/en/cookbook/entities-in-session.rst +++ b/en/cookbook/entities-in-session.rst @@ -47,7 +47,7 @@ Entities that are serialized into the session normally contain references to other entities as well. Think of the user entity has a reference to his articles, groups, photos or many other different entities. If you serialize this object into the session then you don't want to serialize the related -entities aswell. This is why you should call ``EntityManager#detach()`` on this +entities as well. This is why you should call ``EntityManager#detach()`` on this object or implement the __sleep() magic method on your entity. .. code-block:: php diff --git a/en/cookbook/resolve-target-entity-listener.rst b/en/cookbook/resolve-target-entity-listener.rst index 0e9947755..9781a31d5 100644 --- a/en/cookbook/resolve-target-entity-listener.rst +++ b/en/cookbook/resolve-target-entity-listener.rst @@ -24,7 +24,7 @@ because they can be used in other systems without each other, but for our application we want to use them together. In this case, we have an ``Invoice`` entity with a relationship to a -non-existant object, an ``InvoiceSubjectInterface``. The goal is to get +non-existent object, an ``InvoiceSubjectInterface``. The goal is to get the ``ResolveTargetEntityListener`` to replace any mention of the interface with a real object that implements that interface. diff --git a/en/cookbook/strategy-cookbook-introduction.rst b/en/cookbook/strategy-cookbook-introduction.rst index 9a635df03..d9934f577 100644 --- a/en/cookbook/strategy-cookbook-introduction.rst +++ b/en/cookbook/strategy-cookbook-introduction.rst @@ -37,7 +37,7 @@ highly uncomfortable because of the following: block / entity. This would tear down any effort of modular programming. -Therefore, we need something thats far more flexible. +Therefore, we need something that's far more flexible. Solution -------- diff --git a/en/cookbook/validation-of-entities.rst b/en/cookbook/validation-of-entities.rst index ef1e1b7ad..a09b218e0 100644 --- a/en/cookbook/validation-of-entities.rst +++ b/en/cookbook/validation-of-entities.rst @@ -81,7 +81,7 @@ In XML Mappings: - + YAML needs some little change yet, to allow multiple lifecycle events for one method, this will happen before Beta 1 though. diff --git a/en/cookbook/working-with-datetime.rst b/en/cookbook/working-with-datetime.rst index c0308f186..fc548dac0 100644 --- a/en/cookbook/working-with-datetime.rst +++ b/en/cookbook/working-with-datetime.rst @@ -66,7 +66,7 @@ The problem is simple. Not a single database vendor saves the timezone, only the However with frequent daylight saving and political timezone changes you can have a UTC offset that moves in different offset directions depending on the real location. -The solution for this dialemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround +The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround that even allows correct date-time handling with timezones: 1. Always convert any DateTime instance to UTC. @@ -75,7 +75,7 @@ that even allows correct date-time handling with timezones: Say we have an application for an international postal company and employees insert events regarding postal-package around the world, in their current timezones. To determine the exact time an event occurred means to save both -the UTC time at the time of the booking and the timezone the event happend in. +the UTC time at the time of the booking and the timezone the event happened in. .. code-block:: php @@ -122,7 +122,7 @@ the UTC time at the time of the booking and the timezone the event happend in. This database type makes sure that every DateTime instance is always saved in UTC, relative to the current timezone that the passed DateTime instance has. To be able to transform these values -back into their real timezone you have to save the timezone in a seperate field of the entity +back into their real timezone you have to save the timezone in a separate field of the entity requiring timezoned datetimes: .. code-block:: php diff --git a/en/reference/association-mapping.rst b/en/reference/association-mapping.rst index 3dbe7f184..3a597f6db 100644 --- a/en/reference/association-mapping.rst +++ b/en/reference/association-mapping.rst @@ -15,9 +15,9 @@ This chapter is split into three different sections. - A list of all the possible association mapping use-cases is given. - :ref:`association_mapping_defaults` are explained that simplify the use-case examples. -- :ref:`collections` are introduced that contain entities in assoactions. +- :ref:`collections` are introduced that contain entities in associations. -To master assocations you should also learn about :doc:`owning and inverse sides of associations ` +To master associations you should also learn about :doc:`owning and inverse sides of associations ` One-To-One, Unidirectional -------------------------- diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst index 64f670840..bb5c128e2 100644 --- a/en/reference/basic-mapping.rst +++ b/en/reference/basic-mapping.rst @@ -18,7 +18,7 @@ object-relational mapping metadata: This manual usually mentions docblock annotations in all the examples that are spread throughout all chapters, however for many examples -alternative YAML and XML examples are given aswell. There are dedicated +alternative YAML and XML examples are given as well. There are dedicated reference chapters for XML and YAML mapping, respectively that explain them in more detail. There is also an Annotation reference chapter. diff --git a/en/reference/faq.rst b/en/reference/faq.rst index 2cc1c24c1..5d57c24f7 100644 --- a/en/reference/faq.rst +++ b/en/reference/faq.rst @@ -4,7 +4,7 @@ Frequently Asked Questions .. note:: This FAQ is a work in progress. We will add lots of questions and not answer them right away just to remember - what is often asked. If you stumble accross an unanswerd question please write a mail to the mailing-list or + what is often asked. If you stumble across an unanswered question please write a mail to the mailing-list or join the #doctrine channel on Freenode IRC. Database Schema @@ -74,7 +74,7 @@ What is wrong when I get an InvalidArgumentException "A new entity was found thr This exception is thrown during ``EntityManager#flush()`` when there exists an object in the identity map that contains a reference to an object that Doctrine does not know about. Say for example you grab -a "User"-entity from the database with a specific id and set a completly new object into one of the associations +a "User"-entity from the database with a specific id and set a completely new object into one of the associations of the User object. If you then call ``EntityManager#flush()`` without letting Doctrine know about this new object using ``EntityManager#persist($newObject)`` you will see this exception. @@ -96,7 +96,7 @@ By definition a One-To-Many association is on the inverse side, that means chang will not be recognized by Doctrine. If you want to perform the equivalent of the clear operation you have to iterate the -collection and set the owning side many-to-one reference to NULL aswell to detach all entities +collection and set the owning side many-to-one reference to NULL as well to detach all entities from the collection. This will trigger the appropriate UPDATE statements on the database. How can I add columns to a many-to-many table? @@ -126,7 +126,7 @@ Why does pagination not work correctly with fetch joins? Pagination in Doctrine uses a LIMIT clause (or vendor equivalent) to restrict the results. However when fetch-joining this is not returning the correct number of results since joining -with a one-to-many or many-to-many association muliplies the number of rows by the number +with a one-to-many or many-to-many association multiplies the number of rows by the number of associated entities. See the previous question for a solution to this task. @@ -142,11 +142,11 @@ Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2. See the documentation chapter on :doc:`inheritance mapping ` for the details. -Why does Doctrine not create proxy objects for my inheritance hierachy? +Why does Doctrine not create proxy objects for my inheritance hierarchy? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you set a many-to-one or one-to-one association target-entity to any parent class of -an inheritance hierachy Doctrine does not know what PHP class the foreign is actually of. +an inheritance hierarchy Doctrine does not know what PHP class the foreign is actually of. To find this out it has to execute an SQL query to look this information up in the database. EntityGenerator diff --git a/en/reference/inheritance-mapping.rst b/en/reference/inheritance-mapping.rst index 503223f7f..1bc92f3f3 100644 --- a/en/reference/inheritance-mapping.rst +++ b/en/reference/inheritance-mapping.rst @@ -19,7 +19,7 @@ appear in the middle of an otherwise mapped inheritance hierarchy A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many - assocations are not possible on a mapped superclass at all. + associations are not possible on a mapped superclass at all. Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment. For further support of inheritance, the single or diff --git a/en/reference/limitations-and-known-issues.rst b/en/reference/limitations-and-known-issues.rst index 2d6cedd15..e3eb498a0 100644 --- a/en/reference/limitations-and-known-issues.rst +++ b/en/reference/limitations-and-known-issues.rst @@ -146,7 +146,7 @@ extensions out there that offer support for Nested Set with Doctrine 2: -- `Doctrine2 Hierachical-Structural Behavior `_ +- `Doctrine2 Hierarchical-Structural Behavior `_ - `Doctrine2 NestedSet `_ Known Issues diff --git a/en/reference/metadata-drivers.rst b/en/reference/metadata-drivers.rst index 72ea7ac10..6b9cb31e4 100644 --- a/en/reference/metadata-drivers.rst +++ b/en/reference/metadata-drivers.rst @@ -170,7 +170,7 @@ Getting ClassMetadata Instances ------------------------------- If you want to get the ``ClassMetadata`` instance for an entity in -your project to programatically use some mapping information to +your project to programmatically use some mapping information to generate some HTML or something similar you can retrieve it through the ``ClassMetadataFactory``: diff --git a/en/reference/native-sql.rst b/en/reference/native-sql.rst index 477297485..cf822f2bb 100644 --- a/en/reference/native-sql.rst +++ b/en/reference/native-sql.rst @@ -390,7 +390,7 @@ in your sQL statement: $rsm->addRootEntityFromClassMetadata('MyProject\User', 'u'); $rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' => 'address_id')); -For entites with more columns the builder is very convenient to use. It extends the ``ResultSetMapping`` class +For entities with more columns the builder is very convenient to use. It extends the ``ResultSetMapping`` class and as such has all the functionality of it as well. Currently the ``ResultSetMappingBuilder`` does not support entities with inheritance. diff --git a/en/reference/php-mapping.rst b/en/reference/php-mapping.rst index dafa2c0e1..90f00d9bc 100644 --- a/en/reference/php-mapping.rst +++ b/en/reference/php-mapping.rst @@ -161,10 +161,10 @@ The API of the ClassMetadataBuilder has the following methods with a fluent inte It also has several methods that create builders (which are necessary for advanced mappings): - ``createField($name, $type)`` returns a ``FieldBuilder`` instance -- ``createManyToOne($name, $targetEntity)`` returns an ``AssocationBuilder`` instance -- ``createOneToOne($name, $targetEntity)`` returns an ``AssocationBuilder`` instance -- ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssocationBuilder`` instance -- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssocationBuilder`` instance +- ``createManyToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance +- ``createOneToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance +- ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssociationBuilder`` instance +- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance ClassMetadataInfo API --------------------- diff --git a/en/reference/tools.rst b/en/reference/tools.rst index cc087c24f..99afd35b0 100644 --- a/en/reference/tools.rst +++ b/en/reference/tools.rst @@ -28,7 +28,7 @@ Configuration (PEAR) ~~~~~~~~~~~~~~~~~~~~ Whenever the ``doctrine`` command line tool is invoked, it can -access alls Commands that were registered by developer. There is no +access all Commands that were registered by developer. There is no auto-detection mechanism at work. The Doctrine binary already registers all the commands that currently ship with Doctrine DBAL and ORM. If you want to use additional commands you @@ -300,7 +300,7 @@ to error and we suggest you use code repositories such as GIT or SVN to make backups of your code. It makes sense to generate the entity code if you are using entities as Data -Access Objects only and dont put much additional logic on them. If you are +Access Objects only and don't put much additional logic on them. If you are however putting much more logic on the entities you should refrain from using the entity-generator and code your entities manually. @@ -319,7 +319,7 @@ Convert Mapping Information Convert mapping information between supported formats. This is an **execute one-time** command. It should not be necessary for -you to call this method multiple times, escpecially when using the ``--from-database`` +you to call this method multiple times, especially when using the ``--from-database`` flag. Converting an existing database schema into mapping files only solves about 70-80% diff --git a/en/reference/unitofwork-associations.rst b/en/reference/unitofwork-associations.rst index 954567ec9..da3cc9dc8 100644 --- a/en/reference/unitofwork-associations.rst +++ b/en/reference/unitofwork-associations.rst @@ -8,7 +8,7 @@ following general rules apply: - Relationships may be bidirectional or unidirectional. - A bidirectional relationship has both an owning side and an inverse side - A unidirectional relationship only has an owning side. -- Doctrine will **only** check the owning side of an assocation for changes. +- Doctrine will **only** check the owning side of an association for changes. Bidirectional Associations -------------------------- @@ -22,16 +22,16 @@ The following rules apply to **bidirectional** associations: OneToOne, ManyToOne, or ManyToMany mapping declaration. The inversedBy attribute contains the name of the association-field on the inverse-side. -- ManyToOne is always the owning side of a bidirectional assocation. -- OneToMany is always the inverse side of a bidirectional assocation. -- The owning side of a OneToOne assocation is the entity with the table +- ManyToOne is always the owning side of a bidirectional association. +- OneToMany is always the inverse side of a bidirectional association. +- The owning side of a OneToOne association is the entity with the table containing the foreign key. -- You can pick the owning side of a many-to-many assocation yourself. +- You can pick the owning side of a many-to-many association yourself. Important concepts ------------------ -**Doctrine will only check the owning side of an assocation for changes.** +**Doctrine will only check the owning side of an association for changes.** To fully understand this, remember how bidirectional associations are maintained in the object world. There are 2 references on each diff --git a/en/reference/unitofwork.rst b/en/reference/unitofwork.rst index abe7543e7..48f1029c6 100644 --- a/en/reference/unitofwork.rst +++ b/en/reference/unitofwork.rst @@ -12,7 +12,7 @@ its UnitOfWork. The array holding all the entity references is two-levels deep and has the keys "root entity name" and "id". Since Doctrine allows composite keys the id is a sorted, serialized version of all the key columns. -This allows Doctrine room for optimizations. If you call the EntiyManager and +This allows Doctrine room for optimizations. If you call the EntityManager and ask for an entity with a specific ID twice, it will return the same instance: .. code-block:: php @@ -117,7 +117,7 @@ that consume new memory. Now whenever you call ``EntityManager#flush`` Doctrine will iterate over the Identity Map and for each object compares the original property and association values with the values that are currently set on the object. If changes are -detected then the object is qeued for an SQL UPDATE operation. Only the fields +detected then the object is queued for an SQL UPDATE operation. Only the fields that actually changed are updated. This process has an obvious performance impact. The larger the size of the diff --git a/en/reference/working-with-objects.rst b/en/reference/working-with-objects.rst index e1ee8ebbe..31d44f275 100644 --- a/en/reference/working-with-objects.rst +++ b/en/reference/working-with-objects.rst @@ -474,9 +474,9 @@ operations. Effects of Database and UnitOfWork being Out-Of-Sync ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -As soon as you begin to change the state ofentities, call persist or remove the +As soon as you begin to change the state of entities, call persist or remove the contents of the UnitOfWork and the database will drive out of sync. They can -only be sychronized by calling ``EntityManager#flush()``. This section +only be synchronized by calling ``EntityManager#flush()``. This section describes the effects of database and UnitOfWork being out of sync. - Entities that are scheduled for removal can still be queried from the database. diff --git a/en/tutorials/composite-primary-keys.rst b/en/tutorials/composite-primary-keys.rst index 0dadb245e..dcf8bc687 100644 --- a/en/tutorials/composite-primary-keys.rst +++ b/en/tutorials/composite-primary-keys.rst @@ -112,7 +112,7 @@ and to ``year`` to the related entities. .. note:: - This example shows how you can nicely solve the requirement for exisiting + This example shows how you can nicely solve the requirement for existing values before ``EntityManager#persist()``: By adding them as mandatory values for the constructor. Identity through foreign Entities @@ -135,7 +135,7 @@ of one or many parent entities. The semantics of mapping identity through foreign entities are easy: - Only allowed on Many-To-One or One-To-One associations. -- Plug an ``@Id`` annotation onto every assocation. +- Plug an ``@Id`` annotation onto every association. - Set an attribute ``association-key`` with the field name of the association in XML. - Set a key ``associationKey:`` with the field name of the association in YAML. diff --git a/en/tutorials/extra-lazy-associations.rst b/en/tutorials/extra-lazy-associations.rst index d6ed40d3d..59fc440a0 100644 --- a/en/tutorials/extra-lazy-associations.rst +++ b/en/tutorials/extra-lazy-associations.rst @@ -3,8 +3,8 @@ Extra Lazy Associations In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundreds of comments. -In Doctrine 2.0 if you accessed an association it would always get loaded completly into memory. This -can lead to pretty serious performance problems, if your associations contain several hundrets or thousands +In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This +can lead to pretty serious performance problems, if your associations contain several hundreds or thousands of entities. With Doctrine 2.1 a feature called **Extra Lazy** is introduced for associations. Associations diff --git a/en/tutorials/getting-started-models.rst b/en/tutorials/getting-started-models.rst index 4bf3cec16..e844b4d68 100644 --- a/en/tutorials/getting-started-models.rst +++ b/en/tutorials/getting-started-models.rst @@ -8,7 +8,7 @@ Getting Started: Model First you :doc:`Model First `, you are modelling your application using tools (for example UML) and generate database schema and PHP code from this model. When you have a :doc:`Database First `, then you already have a database schema - and generate the correspdongin PHP code from it. + and generate the corresponding PHP code from it. .. note:: diff --git a/en/tutorials/getting-started.rst b/en/tutorials/getting-started.rst index dc6ae8904..e6bd26397 100644 --- a/en/tutorials/getting-started.rst +++ b/en/tutorials/getting-started.rst @@ -1348,7 +1348,7 @@ Entity Repositories For now we have not discussed how to separate the Doctrine query logic from your model. In Doctrine 1 there was the concept of ``Doctrine_Table`` instances for this -seperation. The similar concept in Doctrine2 is called Entity Repositories, integrating +separation. The similar concept in Doctrine2 is called Entity Repositories, integrating the `repository pattern `_ at the heart of Doctrine. Every Entity uses a default repository by default and offers a bunch of convenience @@ -1380,7 +1380,7 @@ Compared to DQL these query methods are falling short of functionality very fast Doctrine offers you a convenient way to extend the functionalities of the default ``EntityRepository`` and put all the specialized DQL query logic on it. For this you have to create a subclass of ``Doctrine\ORM\EntityRepository``, in our case a ``BugRepository`` and group all -the previoiusly discussed query functionality in it: +the previously discussed query functionality in it: .. code-block:: php @@ -1428,7 +1428,7 @@ the previoiusly discussed query functionality in it: } } -Dont forget to add a `require_once` call for this class to the bootstrap.php +Don't forget to add a `require_once` call for this class to the bootstrap.php To be able to use this query logic through ``$this->getEntityManager()->getRepository('Bug')`` we have to adjust the metadata slightly. diff --git a/en/tutorials/ordered-associations.rst b/en/tutorials/ordered-associations.rst index 1f84ea0e6..e2a48ffcb 100644 --- a/en/tutorials/ordered-associations.rst +++ b/en/tutorials/ordered-associations.rst @@ -1,5 +1,5 @@ -Ordering To-Many Assocations ----------------------------- +Ordering To-Many Associations +----------------------------- There are use-cases when you'll want to sort collections when they are retrieved from the database. In userland you do this as long as you diff --git a/en/tutorials/working-with-indexed-associations.rst b/en/tutorials/working-with-indexed-associations.rst index ab48fea86..3536a34f8 100644 --- a/en/tutorials/working-with-indexed-associations.rst +++ b/en/tutorials/working-with-indexed-associations.rst @@ -1,5 +1,5 @@ -Working with Indexed Assocations -================================ +Working with Indexed Associations +================================= .. note:: @@ -22,10 +22,10 @@ As an example we will design a simple stock exchange list view. The domain consi and ``Market`` where each Stock has a symbol and is traded on a single market. Instead of having a numerical list of stocks traded on a market they will be indexed by their symbol, which is unique across all markets. -Mapping Indexed Assocations -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Mapping Indexed Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can map indexed assocations by adding: +You can map indexed associations by adding: * ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation. * ``index-by`` attribute to any ```` or ```` xml element. @@ -139,7 +139,7 @@ The code and mappings for the Market entity looks like this: indexBy: symbol Inside the ``addStock()`` method you can see how we directly set the key of the association to the symbol, -so that we can work with the indexed assocation directly after invoking ``addStock()``. Inside ``getStock($symbol)`` +so that we can work with the indexed association directly after invoking ``addStock()``. Inside ``getStock($symbol)`` we pick a stock traded on the particular market by symbol. If this stock doesn't exist an exception is thrown. The ``Stock`` entity doesn't contain any special instructions that are new, but for completeness @@ -294,5 +294,5 @@ Outlook into the Future For the inverse side of a many-to-many associations there will be a way to persist the keys and the order as a third and fourth parameter into the join table. This feature is discussed in `DDC-213 `_ -This feature cannot be implemeted for One-To-Many associations, because they are never the owning side. +This feature cannot be implemented for One-To-Many associations, because they are never the owning side. From ca1c6498eca707fe7ad656848553dc056134ca81 Mon Sep 17 00:00:00 2001 From: Mykola Zyk Date: Fri, 7 Dec 2012 19:44:47 +0200 Subject: [PATCH 418/430] Update en/reference/query-builder.rst Adding note for $append parameter of QueryBuilder::add() method. --- en/reference/query-builder.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst index 6470ad656..0741f8e8e 100644 --- a/en/reference/query-builder.rst +++ b/en/reference/query-builder.rst @@ -500,7 +500,8 @@ of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and a string or any instance of ``Doctrine\ORM\Query\Expr\*`` - ``$append``: Optional flag (default=false) if the ``$dqlPart`` should override all previously defined items in ``$dqlPartName`` or - not + not (no effect on the ``where`` and ``having`` DQL query parts, + which always override all previously defined items) - From 4c1759eecb0f058a92019490259261d733633876 Mon Sep 17 00:00:00 2001 From: Peter Hoffmann Date: Thu, 13 Dec 2012 17:53:44 +0100 Subject: [PATCH 419/430] Keyname was obviously changed in (latest) 2.3.1, cost me some hours to figure out, NOT cool! --- en/reference/basic-mapping.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst index bb5c128e2..dd10ae777 100644 --- a/en/reference/basic-mapping.rst +++ b/en/reference/basic-mapping.rst @@ -287,7 +287,7 @@ list: - ``type``: (optional, defaults to 'string') The mapping type to use for the column. -- ``name``: (optional, defaults to field name) The name of the +- ``column``: (optional, defaults to field name) The name of the column in the database. - ``length``: (optional, default 255) The length of the column in the database. (Applies only if a string-valued column is used). From e17fc6c4745b713603d75ef81ee18b0dab523742 Mon Sep 17 00:00:00 2001 From: Tom Graham Date: Thu, 20 Dec 2012 20:00:50 +0000 Subject: [PATCH 420/430] Added missing "new" keyword for event listener --- en/reference/events.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en/reference/events.rst b/en/reference/events.rst index 423497a31..a6b75ad12 100644 --- a/en/reference/events.rst +++ b/en/reference/events.rst @@ -354,7 +354,7 @@ EventManager that is passed to the EntityManager factory: addEventListener(array(Events::preUpdate), MyEventListener()); + $eventManager->addEventListener(array(Events::preUpdate), new MyEventListener()); $eventManager->addEventSubscriber(new MyEventSubscriber()); $entityManager = EntityManager::create($dbOpts, $config, $eventManager); @@ -365,7 +365,7 @@ EntityManager was created: .. code-block:: php getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener()); + $entityManager->getEventManager()->addEventListener(array(Events::preUpdate), new MyEventListener()); $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); .. _reference-events-implementing-listeners: From d8b94b052774b8a3f445d0ffc9ec3760bc53017b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 23 Dec 2012 13:05:54 +0100 Subject: [PATCH 421/430] Some text refactorings in tutorial --- en/tutorials/getting-started.rst | 41 ++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/en/tutorials/getting-started.rst b/en/tutorials/getting-started.rst index e6bd26397..d031e5d29 100644 --- a/en/tutorials/getting-started.rst +++ b/en/tutorials/getting-started.rst @@ -11,7 +11,7 @@ Getting Started: Code First and generate the corresponding PHP code from it. Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides -transparent persistence for PHP objects. It uses the Data Mapper pattern at +transparent persistence for PHP objects. It uses the Data Mapper pattern at the heart of this project, aiming for a complete separation of the domain/business logic from the persistence in a relational database management system. The benefit of Doctrine for the programmer is the ability to focus @@ -74,29 +74,39 @@ requirements to be: Setup Project ------------- -Make sure you get Doctrine from PEAR by calling the following commands -on your CLI: +Create a new empty folder for this tutorial project, for example +``doctrine2-tutorial`` and create a new file ``composer.json`` with +the following contents: :: - $ pear channel-discover pear.doctrine-project.org - $ pear install --alldeps doctrine/DoctrineORM + { + "require": { + "doctrine/orm": "2.*", + "symfony/console": "2.*", + "symfony/yaml": "2.*" + }, + "autoload": { + "psr-0": {"": "entities"} + } + } -This should install the packages DoctrineCommon, DoctrineDBAL, DoctrineORM, -SymfonyConsole and SymfonyYAML. - -Now create a new directory for this tutorial project: +Install Doctrine using the Composer Dependency Management tool, by calling: :: - $ mkdir project - $ cd project + $ composer install -You can prepare the directory structure so that in the end it looks like: +This ill install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM, +Symfony YAML and Symfony Console. Both Symfony dependencies are optional +but will be used in this tutorial. + +You can prepare the directory structure: :: project + |-- composer.json |-- config | |-- xml | `-- yaml @@ -105,9 +115,10 @@ You can prepare the directory structure so that in the end it looks like: A first prototype ----------------- -A first simplified design for this domain model might look like the -following set of classes. Put them into `entities/Bug.php`, -`entities/Product.php` and `entities/User.php` respectively. +We start with a simplified design for the bug tracker domain, by creating three +classes ``Bug``, ``Product`` and ``User`` and putting them into +`entities/Bug.php`, `entities/Product.php` and `entities/User.php` +respectively. .. code-block:: php From b0a24c8baa9b04cb306f401b9c0704a51c33f13e Mon Sep 17 00:00:00 2001 From: Aaron Muylaert Date: Fri, 4 Jan 2013 11:29:34 +0100 Subject: [PATCH 422/430] Update en/reference/query-builder.rst Fix syntax error in one of the orderBy examples. --- en/reference/query-builder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst index 6470ad656..d7d695a9a 100644 --- a/en/reference/query-builder.rst +++ b/en/reference/query-builder.rst @@ -208,7 +208,7 @@ allowed. Binding parameters can simply be achieved as follows: $qb->select('u') ->from('User u') ->where('u.id = ?1') - ->orderBy('u.name ASC'); + ->orderBy('u.name', 'ASC'); ->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100 You are not forced to enumerate your placeholders as the From 47043a54a535941f5cdcdacb4a9dd2bb5de887c1 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Jan 2013 09:37:45 +0100 Subject: [PATCH 423/430] Update en/reference/filters.rst --- en/reference/filters.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/en/reference/filters.rst b/en/reference/filters.rst index 3f3ab8492..34485b087 100644 --- a/en/reference/filters.rst +++ b/en/reference/filters.rst @@ -23,6 +23,11 @@ illustrate how the filter feature works. A filter class must extend the base method. The method receives the ``ClassMetadata`` of the filtered entity and the table alias of the SQL table of the entity. +.. note:: + + In the case of joined or single table inheritance, you always get passed the ClassMetadata of the + inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters. + Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()``. Only parameters set via this function can be used in filters. The ``SQLFilter#getParameter()`` function takes care of the From 939ca1b85f949b7e50a7b3d7b4136bd42e7aff3e Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 13 Jan 2013 15:08:25 -0200 Subject: [PATCH 424/430] Docs for IDENTITY() composite primary key --- en/reference/dql-doctrine-query-language.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index 2ad6f5faf..e4cc59d81 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -424,6 +424,12 @@ Get all users visible on a given website that have chosen certain gender: createQuery('SELECT u FROM User u WHERE u.gender IN (SELECT IDENTITY(agl.gender) FROM Site s JOIN s.activeGenderList agl WHERE s.id = ?1)'); +IDENTITY() DQL Function when the association has a composite primary key: + +.. code-block:: php + + createQuery('SELECT IDENTITY(c.location, 'latitude') AS latitude, IDENTITY(c.location, 'longitude') AS longitude FROM Checkpoint c WHERE c.user = ?1'); Partial Object Syntax ^^^^^^^^^^^^^^^^^^^^^ @@ -547,7 +553,7 @@ The following functions are supported in SELECT, WHERE and HAVING clauses: -- IDENTITY(single\_association\_path\_expression) - Retrieve the foreign key column of association of the owning side +- IDENTITY(single\_association\_path\_expression [, fieldMapping]) - Retrieve the foreign key column of association of the owning side - ABS(arithmetic\_expression) - CONCAT(str1, str2) - CURRENT\_DATE() - Return the current date From 6a23e6be96ad43555f077dcf22fdbc84a5c8e4bc Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 13 Jan 2013 15:56:11 -0200 Subject: [PATCH 425/430] =?UTF-8?q?Docs=20for=20=E2=80=9CNEW=E2=80=9D=20Op?= =?UTF-8?q?erator=20Syntax?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- en/reference/dql-doctrine-query-language.rst | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/en/reference/dql-doctrine-query-language.rst b/en/reference/dql-doctrine-query-language.rst index 2ad6f5faf..a6ebe8b56 100644 --- a/en/reference/dql-doctrine-query-language.rst +++ b/en/reference/dql-doctrine-query-language.rst @@ -451,6 +451,44 @@ You use the partial syntax when joining as well: $query = $em->createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a'); $users = $query->getResult(); // array of partially loaded CmsUser objects +"NEW" Operator Syntax +^^^^^^^^^^^^^^^^^^^^^ + +Using the ``NEW`` operator you can construct DTOs from queries. + +- When using ``SELECT NEW`` you don't need to specify a mapped entity. +- You can specify any PHP class, it's only require that you have a matching constructor in your class. +- This approach involves determining exactly which columns you really need, + and instantiating data-transfer object that containing a constructor with those arguments. + + +If you want to select data-transfer objects you should create a class: + +.. code-block:: php + + createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city) FROM Customer c JOIN c.email e JOIN c.address a'); + $users = $query->getResult(); // array of CustomerDTO + +.. code-block:: php + + createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city, SUM(o.value)) FROM Customer c JOIN c.email e JOIN c.address a JOIN c.orders o GROUP BY c'); + $users = $query->getResult(); // array of CustomerDTO + Using INDEX BY ~~~~~~~~~~~~~~ From 16533299b0ad35d43e13ec99d59ec14729b14095 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Mon, 14 Jan 2013 18:47:18 -0600 Subject: [PATCH 426/430] Update en/reference/installation.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove reference to download page as it does not exist anymore. We are no longer maintaining packages for download in any proprietary Doctrine repository. --- en/reference/installation.rst | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/en/reference/installation.rst b/en/reference/installation.rst index 43cb28e70..48cdd1350 100644 --- a/en/reference/installation.rst +++ b/en/reference/installation.rst @@ -59,16 +59,6 @@ it with the following command. $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.2.2 -Package Download ----------------- - -You can also use Doctrine 2 by downloading the latest release -package from -`the download page `_. - -See the configuration section on how to configure and bootstrap a -downloaded version of Doctrine. - GitHub ------ From b369158dc674065f36a9b3fd242829ae606c01e7 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Mon, 14 Jan 2013 20:08:25 -0600 Subject: [PATCH 427/430] Upgrade theme. --- en/_theme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/_theme b/en/_theme index bd047f830..ca8838fa1 160000 --- a/en/_theme +++ b/en/_theme @@ -1 +1 @@ -Subproject commit bd047f830839db82678dfed84616b1dc623d3481 +Subproject commit ca8838fa1c96d43b1910ba337c14112c3128f439 From 27ac88fe28169fdee81c5d26edfb720febad93a8 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Mon, 14 Jan 2013 20:12:20 -0600 Subject: [PATCH 428/430] Update theme again. --- en/_theme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/_theme b/en/_theme index ca8838fa1..5aa024c92 160000 --- a/en/_theme +++ b/en/_theme @@ -1 +1 @@ -Subproject commit ca8838fa1c96d43b1910ba337c14112c3128f439 +Subproject commit 5aa024c9284228ed76c7a9aa785229cf19f396e3 From ec23961c7d8dfe2941dcd1e42801a9f79ca43dcd Mon Sep 17 00:00:00 2001 From: Juti Noppornpitak Date: Wed, 16 Jan 2013 15:48:15 -0500 Subject: [PATCH 429/430] Update en/cookbook/decorator-pattern.rst --- en/cookbook/decorator-pattern.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/en/cookbook/decorator-pattern.rst b/en/cookbook/decorator-pattern.rst index ab9cf8c92..49d13b719 100644 --- a/en/cookbook/decorator-pattern.rst +++ b/en/cookbook/decorator-pattern.rst @@ -39,7 +39,7 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``. */ protected $id; - /** @Column(type="string", nullable="true") */ + /** @Column(type="string", nullable=true) */ protected $name; /** @@ -119,7 +119,7 @@ use a ``MappedSuperclass`` for this. */ public function __construct(Component $c) { - $this->setDecorates($c); + $this->setDecorates($c); } /** @@ -191,7 +191,7 @@ of the getSpecial() method to its return value. class ConcreteDecorator extends Decorator { - /** @Column(type="string", nullable="true") */ + /** @Column(type="string", nullable=true) */ protected $special; /** From f5ba83cae5da6c9bc61b0bc9f90cbec28b86bbed Mon Sep 17 00:00:00 2001 From: Peter Kruithof Date: Mon, 21 Jan 2013 15:03:46 +0100 Subject: [PATCH 430/430] Documented `guid` mapping type refs #456a756febb258b52092fa2640c77bb8400114fa --- en/reference/basic-mapping.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/en/reference/basic-mapping.rst b/en/reference/basic-mapping.rst index bb5c128e2..fbd2305cf 100644 --- a/en/reference/basic-mapping.rst +++ b/en/reference/basic-mapping.rst @@ -169,6 +169,8 @@ built-in mapping types: - ``float``: Type that maps a SQL Float (Double Precision) to a PHP double. *IMPORTANT*: Works only with locale settings that use decimal points as separator. +- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to + varchar but uses a specific type if the platform supports it. .. note::