1
0
mirror of synced 2025-02-09 00:39:25 +03:00

Merge remote-tracking branch 'schmittjoh/ValueObjects'

This commit is contained in:
Johannes M. Schmitt 2013-11-02 11:20:44 +01:00
commit d4e6618b28
303 changed files with 10525 additions and 4866 deletions

4
.coveralls.yml Normal file
View File

@ -0,0 +1,4 @@
# for php-coveralls
service_name: travis-ci
src_dir: lib
coverage_clover: build/logs/clover.xml

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ lib/Doctrine/DBAL
.project
.idea
vendor/
composer.lock

View File

@ -5,9 +5,6 @@ php:
- 5.4
- 5.5
matrix:
  allow_failures:
    - php: 5.5
env:
- DB=mysql
- DB=pgsql
@ -19,6 +16,9 @@ before_script:
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests_tmp;' -U postgres; fi"
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
- composer install --prefer-source
- composer install --prefer-dist --dev
script: phpunit --configuration tests/travis/$DB.travis.xml
script: phpunit --configuration tests/travis/$DB.travis.xml
after_script:
- php vendor/bin/coveralls -v

View File

@ -5,6 +5,11 @@ Master: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?bra
2.2: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.2)](http://travis-ci.org/doctrine/doctrine2)
2.1: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.1.x)](http://travis-ci.org/doctrine/doctrine2)
Master: [![Coverage Status](https://coveralls.io/repos/doctrine/doctrine2/badge.png?branch=master)](https://coveralls.io/r/doctrine/doctrine2?branch=master)
[![Latest Stable Version](https://poser.pugx.org/doctrine/orm/v/stable.png)](https://packagist.org/packages/doctrine/orm) [![Total Downloads](https://poser.pugx.org/doctrine/orm/downloads.png)](https://packagist.org/packages/doctrine/orm)
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ 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),

View File

@ -1,5 +1,27 @@
# Upgrade to 2.5
## BC BREAK: NamingStrategy has a new method ``embeddedFieldToColumnName($propertyName, $embeddedColumnName)``
This method generates the column name for fields of embedded objects. If you implement your custom NamingStrategy, you
now also need to implement this new method.
# Upgrade to 2.4
## BC BREAK: Compatibility Bugfix in PersistentCollection#matching()
In Doctrine 2.3 it was possible to use the new ``matching($criteria)``
functionality by adding constraints for assocations based on ID:
Criteria::expr()->eq('association', $assocation->getId());
This functionality does not work on InMemory collections however, because
in memory criteria compares object values based on reference.
As of 2.4 the above code will throw an exception. You need to change
offending code to pass the ``$assocation`` reference directly:
Criteria::expr()->eq('association', $assocation);
## Composer is now the default autoloader
The test suite now runs with composer autoloading. Support for PEAR, and tarball autoloading is deprecated.
@ -11,6 +33,23 @@ Before 2.4 the postFlush and onFlush events were only called when there were
actually entities that changed. Now these events are called no matter if there
are entities in the UoW or changes are found.
## Parenthesis are now considered in arithmetic expression
Before 2.4 parenthesis are not considered in arithmetic primary expression.
That's conceptually wrong, since it might result in wrong values. For example:
The DQL:
SELECT 100 / ( 2 * 2 ) FROM MyEntity
Before 2.4 it generates the SQL:
SELECT 100 / 2 * 2 FROM my_entity
Now parenthesis are considered, the previous DQL will generate:
SELECT 100 / (2 * 2) FROM my_entity
# Upgrade to 2.3
## EntityManager#find() not calls EntityRepository#find() anymore
@ -126,7 +165,7 @@ from 2.0 have to configure the annotation driver if they don't use `Configuratio
$config->setMetadataDriverImpl($driver);
## Scalar mappings can now be ommitted from DQL result
## Scalar mappings can now be omitted from DQL result
You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore.
Example:
@ -307,7 +346,7 @@ them for batch updates like SchemaTool and other commands. However the
annotations driver being a default driver does not really help that much
anyways.
Therefore we decided to break backwards compability in this issue and drop
Therefore we decided to break backwards compatibility in this issue and drop
the support for Annotations as Default Driver and require our users to
specify the driver explicitly (which allows us to ask for the path to all
entities).
@ -366,7 +405,7 @@ apologize for the inconvenience.
## Default Property for Field Mappings
The "default" option for database column defaults has been removed. If desired, database column defaults can
be implemented by using the columnDefinition attribute of the @Column annotation (or the approriate XML and YAML equivalents).
be implemented by using the columnDefinition attribute of the @Column annotation (or the appropriate XML and YAML equivalents).
Prefer PHP default values, if possible.
## Selecting Partial Objects
@ -451,7 +490,7 @@ With new required method AbstractTask::buildDocumentation, its implementation de
* "doctrine schema-tool --drop" now always drops the complete database instead of
only those tables defined by the current database model. The previous method had
problems when foreign keys of orphaned tables pointed to tables that were schedulded
problems when foreign keys of orphaned tables pointed to tables that were scheduled
for deletion.
* Use "doctrine schema-tool --update" to get a save incremental update for your
database schema without deleting any unused tables, sequences or foreign keys.

View File

@ -13,32 +13,47 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
use Symfony\Component\Console\Helper\HelperSet;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
(@include_once __DIR__ . '/../vendor/autoload.php') || @include_once __DIR__ . '/../../../autoload.php';
$configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php';
$helperSet = null;
$commands = array();
if (file_exists($configFile)) {
if ( ! is_readable($configFile)) {
trigger_error(
'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR
);
$directories = array(getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config');
$configFile = null;
foreach ($directories as $directory) {
$configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php';
if (file_exists($configFile)) {
break;
}
}
require $configFile;
if ( ! file_exists($configFile)) {
ConsoleRunner::printCliConfigTemplate();
exit(1);
}
if ( ! is_readable($configFile)) {
echo 'Configuration file [' . $configFile . '] does not have read permission.' . "\n";
exit(1);
}
$commands = array();
$helperSet = require $configFile;
if ( ! ($helperSet instanceof HelperSet)) {
foreach ($GLOBALS as $helperSetCandidate) {
if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) {
if ($helperSetCandidate instanceof HelperSet) {
$helperSet = $helperSetCandidate;
break;
}
}
}
$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet, $commands);

View File

@ -1,11 +1,3 @@
# Project Name
project.name=DoctrineORM
# Dependency minimum versions
dependencies.common=2.2.0beta1
dependencies.dbal=2.2.0beta1
dependencies.sfconsole=2.0.0
# Version class and file
project.version_class = Doctrine\ORM\Version
project.version_class = Doctrine\\ORM\\Version
project.version_file = lib/Doctrine/ORM/Version.php

199
build.xml
View File

@ -1,114 +1,101 @@
<?xml version="1.0"?>
<project name="DoctrineORM" default="build" basedir=".">
<taskdef classname="phing.tasks.ext.d51PearPkg2Task" name="d51pearpkg2" />
<import file="${project.basedir}/lib/vendor/doctrine-build-common/packaging.xml" />
<property file="build.properties" />
<!--
Fileset for artifacts shared across all distributed packages.
-->
<fileset id="shared-artifacts" dir=".">
<include name="LICENSE"/>
<include name="UPGRADE*" />
<include name="doctrine-mapping.xsd" />
</fileset>
<!--
Fileset for command line scripts
-->
<fileset id="bin-scripts" dir="./bin">
<include name="doctrine"/>
<include name="doctrine-pear.php"/>
<include name="doctrine.bat"/>
</fileset>
<!--
Fileset for the sources of the Doctrine Common dependency.
-->
<fileset id="common-sources" dir="./lib/vendor/doctrine-common/lib">
<include name="Doctrine/Common/**"/>
</fileset>
<!--
Fileset for the sources of the Doctrine DBAL dependency.
-->
<fileset id="dbal-sources" dir="./lib/vendor/doctrine-dbal/lib">
<include name="Doctrine/DBAL/**"/>
</fileset>
<!--
Fileset for the sources of the Doctrine ORM.
-->
<fileset id="orm-sources" dir="./lib">
<include name="Doctrine/ORM/**"/>
</fileset>
<!--
Fileset for source of the Symfony YAML and Console components.
-->
<fileset id="symfony-sources" dir="./lib/vendor">
<include name="Symfony/Component/**"/>
<exclude name="**/.git/**" />
</fileset>
<!--
Builds ORM package, preparing it for distribution.
-->
<target name="copy-files" depends="prepare">
<copy todir="${build.dir}/${project.name}-${version}">
<fileset refid="shared-artifacts"/>
</copy>
<copy todir="${build.dir}/${project.name}-${version}">
<fileset refid="common-sources"/>
<fileset refid="dbal-sources"/>
<fileset refid="orm-sources"/>
</copy>
<copy todir="${build.dir}/${project.name}-${version}/Doctrine">
<fileset refid="symfony-sources"/>
</copy>
<copy todir="${build.dir}/${project.name}-${version}/bin">
<fileset refid="bin-scripts"/>
</copy>
<target name="php">
<exec executable="which" outputproperty="php_executable">
<arg value="php" />
</exec>
</target>
<!--
Builds distributable PEAR packages.
-->
<target name="define-pear-package" depends="copy-files">
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/${project.name}-${version}">
<name>DoctrineORM</name>
<summary>Doctrine Object Relational Mapper</summary>
<channel>pear.doctrine-project.org</channel>
<description>The Doctrine ORM package is the primary package containing the object relational mapper.</description>
<lead user="jwage" name="Jonathan H. Wage" email="jonwage@gmail.com" />
<lead user="guilhermeblanco" name="Guilherme Blanco" email="guilhermeblanco@gmail.com" />
<lead user="romanb" name="Roman Borschel" email="roman@code-factory.org" />
<lead user="beberlei" name="Benjamin Eberlei" email="kontakt@beberlei.de" />
<license>LGPL</license>
<version release="${pear.version}" api="${pear.version}" />
<stability release="${pear.stability}" api="${pear.stability}" />
<notes>-</notes>
<dependencies>
<php minimum_version="5.3.0" />
<pear minimum_version="1.6.0" recommended_version="1.6.1" />
<package name="DoctrineCommon" channel="pear.doctrine-project.org" minimum_version="${dependencies.common}" />
<package name="DoctrineDBAL" channel="pear.doctrine-project.org" minimum_version="${dependencies.dbal}" />
<package name="Console" channel="pear.symfony.com" minimum_version="2.0.0" />
<package name="Yaml" channel="pear.symfony.com" minimum_version="2.0.0" />
</dependencies>
<dirroles key="bin">script</dirroles>
<ignore>Doctrine/Common/</ignore>
<ignore>Doctrine/DBAL/</ignore>
<ignore>Symfony/Component/Yaml/</ignore>
<ignore>Symfony/Component/Console/</ignore>
<release>
<install as="doctrine" name="bin/doctrine" />
<install as="doctrine.php" name="bin/doctrine-pear.php" />
<install as="doctrine.bat" name="bin/doctrine.bat" />
</release>
<replacement path="bin/doctrine" type="pear-config" from="@php_bin@" to="php_bin" />
<replacement path="bin/doctrine.bat" type="pear-config" from="@bin_dir@" to="bin_dir" />
</d51pearpkg2>
<target name="prepare">
<mkdir dir="build" />
</target>
<target name="build" depends="check-git-checkout-clean,prepare,php,composer">
<exec executable="${php_executable}">
<arg value="build/composer.phar" />
<arg value="archive" />
<arg value="--dir=build" />
</exec>
</target>
<target name="composer" depends="php,composer-check,composer-download">
<exec executable="${php_executable}">
<arg value="build/composer.phar" />
<arg value="install" />
</exec>
</target>
<target name="composer-check" depends="prepare">
<available file="build/composer.phar" property="composer.present"/>
</target>
<target name="composer-download" unless="composer.present">
<exec executable="wget">
<arg value="-Obuild/composer.phar" />
<arg value="http://getcomposer.org/composer.phar" />
</exec>
</target>
<target name="make-release" depends="check-git-checkout-clean,prepare,php">
<replace file="${project.version_file}" token="-DEV" value="" failOnNoReplacements="true" />
<exec executable="${php_executable}" outputproperty="doctrine.current_version" failonerror="true">
<arg value="-r" />
<arg value="require_once '${project.version_file}';echo ${project.version_class}::VERSION;" />
</exec>
<exec executable="${php_executable}" outputproperty="doctrine.next_version" failonerror="true">
<arg value="-r" />
<arg value="$parts = explode('.', str_ireplace(array('-DEV', '-ALPHA', '-BETA'), '', '${doctrine.current_version}'));
if (count($parts) != 3) {
throw new \InvalidArgumentException('Version is assumed in format x.y.z, ${doctrine.current_version} given');
}
$parts[2]++;
echo implode('.', $parts);
" />
</exec>
<git-commit file="${project.version_file}" message="Release ${doctrine.current_version}" />
<git-tag version="${doctrine.current_version}" />
<replace file="${project.version_file}" token="${doctrine.current_version}" value="${doctrine.next_version}-DEV" />
<git-commit file="${project.version_file}" message="Bump version to ${doctrine.next_version}" />
</target>
<target name="check-git-checkout-clean">
<exec executable="git" failonerror="true">
<arg value="diff-index" />
<arg value="--quiet" />
<arg value="HEAD" />
</exec>
</target>
<macrodef name="git-commit">
<attribute name="file" default="NOT SET"/>
<attribute name="message" default="NOT SET"/>
<sequential>
<exec executable="git">
<arg value="add" />
<arg value="@{file}" />
</exec>
<exec executable="git">
<arg value="commit" />
<arg value="-m" />
<arg value="@{message}" />
</exec>
</sequential>
</macrodef>
<macrodef name="git-tag">
<attribute name="version" default="NOT SET" />
<sequential>
<exec executable="git">
<arg value="tag" />
<arg value="-m" />
<arg value="v@{version}" />
<arg value="v@{version}" />
</exec>
</sequential>
</macrodef>
</project>

View File

@ -15,9 +15,14 @@
"require": {
"php": ">=5.3.2",
"ext-pdo": "*",
"doctrine/dbal": ">=2.4-dev,<2.5-dev",
"doctrine/collections": "~1.1",
"doctrine/dbal": ">=2.5-dev,<2.6-dev",
"symfony/console": "2.*"
},
"require-dev": {
"symfony/yaml": "2.1",
"satooshi/php-coveralls": "dev-master"
},
"suggest": {
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},
@ -27,7 +32,10 @@
"bin": ["bin/doctrine", "bin/doctrine.php"],
"extra": {
"branch-alias": {
"dev-master": "2.4.x-dev"
"dev-master": "2.5.x-dev"
}
},
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp", "*coveralls.yml"]
}
}

507
composer.lock generated
View File

@ -1,507 +0,0 @@
{
"hash": "eff8840dfb1a83e6e1aef32b8031ac7c",
"packages": [
{
"name": "doctrine/annotations",
"version": "v1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/annotations.git",
"reference": "v1.0"
},
"dist": {
"type": "zip",
"url": "https://github.com/doctrine/annotations/archive/v1.0.zip",
"reference": "v1.0",
"shasum": ""
},
"require": {
"doctrine/lexer": "1.*",
"php": ">=5.3.2"
},
"require-dev": {
"doctrine/cache": "1.*"
},
"time": "2013-01-12 19:23:32",
"type": "library",
"autoload": {
"psr-0": {
"Doctrine\\Common\\Annotations\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com",
"homepage": "http://www.jwage.com/"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com",
"homepage": "http://www.instaclick.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh",
"role": "Developer of wrapped JMSSerializerBundle"
}
],
"description": "Docblock Annotations Parser",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"annotations",
"docblock",
"parser"
]
},
{
"name": "doctrine/cache",
"version": "v1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "v1.0"
},
"dist": {
"type": "zip",
"url": "https://github.com/doctrine/cache/archive/v1.0.zip",
"reference": "v1.0",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"time": "2013-01-10 22:43:46",
"type": "library",
"autoload": {
"psr-0": {
"Doctrine\\Common\\Cache\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com",
"homepage": "http://www.jwage.com/"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com",
"homepage": "http://www.instaclick.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh",
"role": "Developer of wrapped JMSSerializerBundle"
}
],
"description": "Caching library offering an object-oriented API for many cache backends",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"cache",
"caching"
]
},
{
"name": "doctrine/collections",
"version": "v1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/collections.git",
"reference": "v1.0"
},
"dist": {
"type": "zip",
"url": "https://github.com/doctrine/collections/archive/v1.0.zip",
"reference": "v1.0",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"time": "2013-01-12 16:36:50",
"type": "library",
"autoload": {
"psr-0": {
"Doctrine\\Common\\Collections\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com",
"homepage": "http://www.jwage.com/"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com",
"homepage": "http://www.instaclick.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh",
"role": "Developer of wrapped JMSSerializerBundle"
}
],
"description": "Collections Abstraction library",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"array",
"collections",
"iterator"
]
},
{
"name": "doctrine/common",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/doctrine/common",
"reference": "53859ae1c84ccf1a5aa58c8379c69cd9adedf03a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/common/zipball/53859ae1c84ccf1a5aa58c8379c69cd9adedf03a",
"reference": "53859ae1c84ccf1a5aa58c8379c69cd9adedf03a",
"shasum": ""
},
"require": {
"doctrine/annotations": "1.*",
"doctrine/cache": "1.*",
"doctrine/collections": "1.*",
"doctrine/inflector": "1.*",
"doctrine/lexer": "1.*",
"php": ">=5.3.2"
},
"time": "2013-01-29 12:48:56",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4.x-dev"
}
},
"autoload": {
"psr-0": {
"Doctrine\\Common\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com",
"homepage": "http://www.jwage.com/"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com",
"homepage": "http://www.instaclick.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh",
"role": "Developer of wrapped JMSSerializerBundle"
}
],
"description": "Common Library for Doctrine projects",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"annotations",
"collections",
"eventmanager",
"persistence",
"spl"
]
},
{
"name": "doctrine/dbal",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal",
"reference": "eb6ee9a86421ba534d7c5514b190d1d06b30d4b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/eb6ee9a86421ba534d7c5514b190d1d06b30d4b1",
"reference": "eb6ee9a86421ba534d7c5514b190d1d06b30d4b1",
"shasum": ""
},
"require": {
"doctrine/common": "2.4.x-dev",
"php": ">=5.3.2"
},
"require-dev": {
"symfony/console": "2.*"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
},
"time": "2013-02-09 23:28:29",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4.x-dev"
}
},
"autoload": {
"psr-0": {
"Doctrine\\DBAL\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com",
"homepage": "http://www.jwage.com/"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com",
"homepage": "http://www.instaclick.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
}
],
"description": "Database Abstraction Layer",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"database",
"dbal",
"persistence",
"queryobject"
]
},
{
"name": "doctrine/inflector",
"version": "v1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/inflector.git",
"reference": "v1.0"
},
"dist": {
"type": "zip",
"url": "https://github.com/doctrine/inflector/archive/v1.0.zip",
"reference": "v1.0",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"time": "2013-01-10 21:49:15",
"type": "library",
"autoload": {
"psr-0": {
"Doctrine\\Common\\Inflector\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com",
"homepage": "http://www.jwage.com/"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com",
"homepage": "http://www.instaclick.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh",
"role": "Developer of wrapped JMSSerializerBundle"
}
],
"description": "Common String Manipulations with regard to casing and singular/plural rules.",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"inflection",
"pluarlize",
"singuarlize",
"string"
]
},
{
"name": "doctrine/lexer",
"version": "v1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/lexer.git",
"reference": "v1.0"
},
"dist": {
"type": "zip",
"url": "https://github.com/doctrine/lexer/archive/v1.0.zip",
"reference": "v1.0",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"time": "2013-01-12 18:59:04",
"type": "library",
"autoload": {
"psr-0": {
"Doctrine\\Common\\Lexer\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com",
"homepage": "http://www.instaclick.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh",
"role": "Developer of wrapped JMSSerializerBundle"
}
],
"description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"lexer",
"parser"
]
},
{
"name": "symfony/console",
"version": "dev-master",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console",
"reference": "f65e34d058f0990a724f78e8d091dc0a20e439ac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/f65e34d058f0990a724f78e8d091dc0a20e439ac",
"reference": "f65e34d058f0990a724f78e8d091dc0a20e439ac",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"time": "2013-01-31 21:39:01",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Console\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com"
}
],
"packages-dev": null,
"aliases": [
],
"minimum-stability": "dev",
"stability-flags": [
]
}

View File

@ -192,3 +192,10 @@ latex_documents = [
# If false, no module index is generated.
#latex_use_modindex = True
primary_domain = "dcorm"
def linkcode_resolve(domain, info):
if domain == 'dcorm':
return 'http://'
return None

View File

@ -0,0 +1,97 @@
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:
.. code-block:: php
<?php
namespace My\Project\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* My custom datatype.
*/
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.
}
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.
}
public function getName()
{
return self::MYTYPE; // modify to match your constant name
}
}
The following assumptions are applied to mapping types by the ORM:
- 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\Types\Type#addType($name, $className)``
method. See the following example:
.. code-block:: php
<?php
// in bootstrapping code
// ...
use Doctrine\DBAL\Types\Type;
// ...
// Register my type
Type::addType('mytype', 'My\Project\Types\MyType');
To convert the underlying database type of your
new "mytype" directly into an instance of ``MyType`` when performing
schema operations, the type has to be registered with the database
platform as well:
.. code-block:: php
<?php
$conn = $em->getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype');
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 the new type can be used when mapping columns:
.. code-block:: php
<?php
class MyPersistentClass
{
/** @Column(type="mytype") */
private $field;
}

View File

@ -112,7 +112,7 @@ The ``Paginate::count(Query $query)`` looks like:
}
It clones the query, resets the limit clause first and max results
and registers the ``CountSqlWalker`` customer tree walker which
and registers the ``CountSqlWalker`` custom tree walker which
will modify the AST to execute a count query. The walkers
implementation is:

View File

@ -64,5 +64,5 @@ object or implement the __sleep() magic method on your entity.
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.
during ``EntityManager#flush()`` anymore in this request.

View File

@ -22,21 +22,21 @@ implement the ``NotifyPropertyChanged`` interface from the
.. code-block:: php
<?php
use Doctrine\Common\NotifyPropertyChanged,
Doctrine\Common\PropertyChangedListener;
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $_listeners = array();
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener) {
$this->_listeners[] = $listener;
$this->listeners[] = $listener;
}
/** Notifies listeners of a change. */
protected function _onPropertyChanged($propName, $oldValue, $newValue) {
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
protected function onPropertyChanged($propName, $oldValue, $newValue) {
if ($this->listeners) {
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
@ -44,7 +44,7 @@ implement the ``NotifyPropertyChanged`` interface from the
}
Then, in each property setter of concrete, derived domain classes,
you need to invoke \_onPropertyChanged as follows to notify
you need to invoke onPropertyChanged as follows to notify
listeners:
.. code-block:: php
@ -58,7 +58,7 @@ listeners:
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->_onPropertyChanged('data', $this->data, $data);
$this->onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}

View File

@ -40,6 +40,7 @@ A Customer entity
.. code-block:: php
<?php
// src/Acme/AppModule/Entity/Customer.php
namespace Acme\AppModule\Entity;
@ -62,6 +63,7 @@ An Invoice entity
.. code-block:: php
<?php
// src/Acme/InvoiceModule/Entity/Invoice.php
namespace Acme\InvoiceModule\Entity;
@ -88,6 +90,7 @@ An InvoiceSubjectInterface
.. code-block:: php
<?php
// src/Acme/InvoiceModule/Model/InvoiceSubjectInterface.php
namespace Acme\InvoiceModule\Model;
@ -112,18 +115,19 @@ An InvoiceSubjectInterface
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::
the targetEntity resolution will occur reliably:
.. code-block:: php
$evm = new \Doctrine\Common\EventManager;
<?php
$evm = new \Doctrine\Common\EventManager;
$rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener;
$rtel->addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface',
'Acme\\CustomerModule\\Entity\\Customer', array());
// Adds a target-entity class
$rtel->addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface', 'Acme\\CustomerModule\\Entity\\Customer', array());
// Add the ResolveTargetEntityListener
$evm->addEventSubscriber($rtel);
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);

View File

@ -14,7 +14,7 @@ Doctrine ORM don't panic. You can get help from different sources:
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
- The `Doctrine Mailing List <http://groups.google.com/group/doctrine-user>`_
- Internet Relay Chat (IRC) in `#doctrine on Freenode <irc://irc.freenode.net/doctrine>`_
- Internet Relay Chat (IRC) in #doctrine on Freenode
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
@ -26,20 +26,10 @@ Getting Started
---------------
* **Tutorial**:
:doc:`Code First <tutorials/getting-started>` |
:doc:`Model First <tutorials/getting-started-models>` |
:doc:`Database First <tutorials/getting-started-database>`
* **Introduction**:
:doc:`In 10 quick steps <tutorials/in-ten-quick-steps>` |
:doc:`Architecture <reference/architecture>`
:doc:`Getting Started with Doctrine <tutorials/getting-started>`
* **Setup**:
:doc:`Installation <reference/installation>` |
:doc:`Configuration <reference/configuration>` |
:doc:`Tools <reference/tools>`
* :doc:`Limitations and knowns issues <reference/limitations-and-known-issues>`
:doc:`Installation & Configuration <reference/configuration>`
Mapping Objects onto a Database
-------------------------------
@ -75,25 +65,29 @@ Working with Objects
Advanced Topics
---------------
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
* :doc:`NamingStrategy <reference/namingstrategy>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
* :doc:`Best Practices <reference/best-practices>`
* :doc:`Metadata Drivers <reference/metadata-drivers>`
* :doc:`Architecture <reference/architecture>`
* :doc:`Advanced Configuration <reference/advanced-configuration>`
* :doc:`Limitations and knowns issues <reference/limitations-and-known-issues>`
* :doc:`Commandline Tools <reference/tools>`
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
* :doc:`NamingStrategy <reference/namingstrategy>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
* :doc:`Best Practices <reference/best-practices>`
* :doc:`Metadata Drivers <reference/metadata-drivers>`
Tutorials
---------
* :doc:`Indexed associations <tutorials/working-with-indexed-associations>`
* :doc:`Extra Lazy Associations <tutorials/extra-lazy-associations>`
* :doc:`Composite Primary Keys <tutorials/composite-primary-keys>`
* :doc:`Ordered associations <tutorials/ordered-associations>`
* :doc:`Pagination <tutorials/pagination>`
* :doc:`Override Field/Association Mappings In Subclasses <tutorials/override-field-association-mappings-in-subclasses>`
* :doc:`Indexed associations <tutorials/working-with-indexed-associations>`
* :doc:`Extra Lazy Associations <tutorials/extra-lazy-associations>`
* :doc:`Composite Primary Keys <tutorials/composite-primary-keys>`
* :doc:`Ordered associations <tutorials/ordered-associations>`
* :doc:`Pagination <tutorials/pagination>`
* :doc:`Override Field/Association Mappings In Subclasses <tutorials/override-field-association-mappings-in-subclasses>`
Cookbook
--------
@ -113,7 +107,8 @@ Cookbook
:doc:`Using Wakeup Or Clone <cookbook/implementing-wakeup-or-clone>` |
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
:doc:`Validation <cookbook/validation-of-entities>` |
:doc:`Entities in the Session <cookbook/entities-in-session>`
:doc:`Entities in the Session <cookbook/entities-in-session>` |
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
* **Integration into Frameworks/Libraries**
:doc:`CodeIgniter <cookbook/integrating-with-codeigniter>`
@ -125,3 +120,4 @@ Cookbook
:doc:`MySQL Enums <cookbook/mysql-enums>`
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`
.. include:: toc.rst

View File

@ -0,0 +1,468 @@
Advanced Configuration
======================
The configuration of the EntityManager requires a
``Doctrine\ORM\Configuration`` instance as well as some database
connection parameters. This example shows all the potential
steps of configuration.
.. code-block:: php
<?php
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration;
// ...
if ($applicationMode == "development") {
$cache = new \Doctrine\Common\Cache\ArrayCache;
} else {
$cache = new \Doctrine\Common\Cache\ApcCache;
}
$config = new Configuration;
$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(false);
}
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
);
$em = EntityManager::create($connectionOptions, $config);
.. note::
Do not use Doctrine without a metadata and query cache!
Doctrine is 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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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 can be configured with a factory method on
the ``Doctrine\ORM\Configuration``:
.. code-block:: php
<?php
$driverImpl = $config->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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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 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
per-request basis.
Query Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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 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
per-request basis.
SQL Logger (***Optional***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy classes can either be generated manually through the Doctrine
Console or automatically at runtime by Doctrine. The configuration
option that controls this behavior is:
.. code-block:: php
<?php
$config->setAutoGenerateProxyClasses($mode);
Possible values for ``$mode`` are:
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_NEVER``
Never autogenerate a proxy. You will need to generate the proxies
manually, for this use the Doctrine Console like so:
.. code-block:: php
$ ./doctrine orm:generate-proxies
When you do this in a development environment,
be aware that you may get class/file not found errors if certain proxies
are not yet generated. You may also get 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.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_ALWAYS``
Always generates a new proxy in every request and writes it to disk.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Generate the proxy class when the proxy file does not exist.
This strategy causes a file exists call whenever any proxy is
used the first time in a request.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_EVAL``
Generate the proxy classes and evaluate them on the fly via eval(),
avoiding writing the proxies to disk.
This strategy is only sane for development.
In a production environment, it is highly recommended to use
AUTOGENERATE_NEVER to allow for optimal performances. The other
options are interesting in development environment.
Before v2.4, ``setAutoGenerateProxyClasses`` would accept a boolean
value. This is still possible, ``FALSE`` being equivalent to
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
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:
.. code-block:: php
<?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
~~~~~~~~~~~~~~~~~~~~~~~~
In a production environment, it is highly recommended to use
``AUTOGENERATE_NEVER`` to allow for optimal performances.
However you will be required to generate the proxies manually
using the Doctrine Console:
.. code-block:: php
$ ./doctrine orm:generate-proxies
The other options are interesting in development environment:
- ``AUTOGENERATE_ALWAYS`` will require you to create and configure
a proxy directory. Proxies will be generated and written to file
on each request, so any modification to your code will be acknowledged.
- ``AUTOGENERATE_FILE_NOT_EXISTS`` will not overwrite an existing
proxy file. If your code changes, you will need to regenerate the
proxies manually.
- ``AUTOGENERATE_EVAL`` will regenerate each proxy on each request,
but without writing them to disk.
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
<?php
use Doctrine\ORM\Proxy\Autoloader;
$proxyDir = "/path/to/proxies";
$proxyNamespace = "MyProxies";
Autoloader::register($proxyDir, $proxyNamespace);
If you want to execute additional logic to intercept the proxy file not found
state you can pass a closure as the third argument. It will be called with
the arguments proxydir, namespace and className when the proxy file could not
be found.
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:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\DriverChain;
$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.
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
<?php
$config->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
Setting up the Console
----------------------
Doctrine uses the Symfony Console component for generating the command
line interface. You can take a look at the ``vendor/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
<?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();

View File

@ -1,6 +1,32 @@
Annotations Reference
=====================
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.
In this chapter a reference of every Doctrine 2 Annotation is given
with short explanations on their context and usage.
@ -21,6 +47,7 @@ Index
- :ref:`@Id <annref_id>`
- :ref:`@InheritanceType <annref_inheritancetype>`
- :ref:`@JoinColumn <annref_joincolumn>`
- :ref:`@JoinColumns <annref_joincolumns>`
- :ref:`@JoinTable <annref_jointable>`
- :ref:`@ManyToOne <annref_manytoone>`
- :ref:`@ManyToMany <annref_manytomany>`

View File

@ -1,12 +1,11 @@
Association Mapping
===================
This chapter introduces association mappings which are used to explain
references between objects and are mapped to a relational database using
foreign keys.
This chapter explains mapping associations between objects.
Instead of working with the foreign keys directly you will always work with
references to objects:
Instead of working with foreign keys in your code, you will always work with
references to objects instead and Doctrine will convert those references
to foreign keys internally.
- 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
@ -17,15 +16,89 @@ This chapter is split into three different sections.
- :ref:`association_mapping_defaults` are explained that simplify the use-case examples.
- :ref:`collections` are introduced that contain entities in associations.
To master associations you should also learn about :doc:`owning and inverse sides of associations <unitofwork-associations>`
To gain a full understanding of associations you should also read about :doc:`owning and
inverse sides of associations <unitofwork-associations>`
Many-To-One, Unidirectional
---------------------------
A many-to-one association is the most common association between objects.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
**/
private $address;
}
/** @Entity **/
class Address
{
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<many-to-one field="address" target-entity="Address">
<join-column name="address_id" referenced-column-name="id" />
</many-to-one>
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
.. 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
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-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.
Here is an example of a one-to-one association with a ``Product`` entity that
references one ``Shipping`` entity. The ``Shipping`` does not reference back to
the ``Product`` so that the reference is said to be unidirectional, in one
direction only.
.. configuration-block::
@ -83,6 +156,7 @@ Generated MySQL Schema:
CREATE TABLE Product (
id INT AUTO_INCREMENT NOT NULL,
shipping_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipping_id),
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Shipping (
@ -183,7 +257,7 @@ relation, the table ``Cart``.
One-To-One, Self-referencing
----------------------------
You can easily have self referencing one-to-one relationships like
You can define a self-referencing one-to-one relationships like
below.
.. code-block:: php
@ -217,6 +291,102 @@ With the generated MySQL Schema:
) ENGINE = InnoDB;
ALTER TABLE Student ADD FOREIGN KEY (mentor_id) REFERENCES Student(id);
One-To-Many, Bidirectional
--------------------------
A one-to-many association has to be bidirectional, unless you are using an
additional join-table. This is necessary, because of the foreign key
in a one-to-many association being defined on the "many" side. Doctrine
needs a many-to-one association that defines the mapping of this
foreign key.
This bidirectional mapping requires the ``mappedBy`` attribute on the
``OneToMany`` association and the ``inversedBy`` attribute on the ``ManyToOne``
association.
.. configuration-block::
.. code-block:: php
<?php
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity **/
class Product
{
// ...
/**
* @OneToMany(targetEntity="Feature", mappedBy="product")
**/
private $features;
// ...
public function __construct() {
$this->features = new ArrayCollection();
}
}
/** @Entity **/
class Feature
{
// ...
/**
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
**/
private $product;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="Product">
<one-to-many field="features" target-entity="Feature" mapped-by="product" />
</entity>
<entity name="Feature">
<many-to-one field="product" target-entity="Product" inversed-by="features">
<join-column name="product_id" referenced-column-name="id" />
</many-to-one>
</entity>
</doctrine-mapping>
.. 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.
Generated MySQL Schema:
.. code-block:: 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, Unidirectional with Join Table
-------------------------------------------
@ -225,12 +395,6 @@ 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.
.. 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::
@ -325,171 +489,6 @@ 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
---------------------------
You can easily implement a many-to-one unidirectional association
with the following:
.. configuration-block::
.. code-block:: php
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
**/
private $address;
}
/** @Entity **/
class Address
{
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<many-to-one field="address" target-entity="Address">
<join-column name="address_id" referenced-column-name="id" />
</many-to-one>
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
.. 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
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:
.. configuration-block::
.. code-block:: php
<?php
/** @Entity **/
class Product
{
// ...
/**
* @OneToMany(targetEntity="Feature", mappedBy="product")
**/
private $features;
// ...
public function __construct() {
$this->features = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/** @Entity **/
class Feature
{
// ...
/**
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
**/
private $product;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="Product">
<one-to-many field="features" target-entity="Feature" mapped-by="product" />
</entity>
<entity name="Feature">
<many-to-one field="product" target-entity="Product" inversed-by="features">
<join-column name="product_id" referenced-column-name="id" />
</many-to-one>
</entity>
</doctrine-mapping>
.. 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.
Generated MySQL Schema:
.. code-block:: 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
-----------------------------
@ -755,8 +754,8 @@ one is bidirectional.
The MySQL schema is exactly the same as for the Many-To-Many
uni-directional case above.
Picking Owning and Inverse Side
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Owning and Inverse Side on a ManyToMany association
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For Many-To-Many associations you can chose which entity is the
owning and which the inverse side. There is a very simple semantic
@ -868,11 +867,9 @@ Generated MySQL Schema:
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:
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:
::
@ -972,8 +969,7 @@ similar defaults. As an example, consider this mapping:
groups:
targetEntity: Group
This is essentially the same as the following, more verbose,
mapping:
This is essentially the same as the following, more verbose, mapping:
.. configuration-block::
@ -1042,73 +1038,28 @@ minimum.
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.
Unfortunately, PHP arrays, while being great for many things, are missing
features that make them suitable for lazy loading in the context of an ORM.
This is why in all the examples of many-valued associations in this manual we
will make use of a ``Collection`` interface and its
default implementation ``ArrayCollection`` that are both defined in the
``Doctrine\Common\Collections`` namespace. A collection implements
the PHP interfaces ``ArrayAccess``, ``Traversable`` and ``Countable``.
.. warning::
.. note::
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.
Therefore using this class in your model and elsewhere
does not introduce a coupling to the ORM.
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
<?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:
You should always initialize the collections of your ``@OneToMany``
and ``@ManyToMany`` associations in the constructor of your entities:
.. code-block:: php
@ -1132,13 +1083,12 @@ empty ``ArrayCollection`` in your entities constructor:
}
}
Now the following code will work even if the Entity hasn't
The following code will then work even if the Entity hasn't
been associated with an EntityManager yet:
.. code-block:: php
<?php
$group = $entityManager->find('Group', $groupId);
$group = new Group();
$user = new User();
$user->getGroups()->add($group);

View File

@ -1,77 +1,72 @@
Basic Mapping
=============
This chapter explains the basic mapping of objects and properties.
Mapping of associations will be covered in the next chapter
"Association Mapping".
This guide explains the basic mapping of entities and properties.
After working through this guide you should know:
Mapping Drivers
---------------
- How to create PHP objects that can be saved to the database with Doctrine;
- How to configure the mapping between columns on tables and properties on
entities;
- What Doctrine mapping types are;
- Defining primary keys and how identifiers are generated by Doctrine;
- How quoting of reserved symbols works in Doctrine.
Doctrine provides several different ways for specifying
object-relational mapping metadata:
Mapping of associations will be covered in the next chapter on
:doc:`Association Mapping <association-mapping>`.
Guide Assumptions
-----------------
- Docblock Annotations
- XML
- YAML
You should have already :doc:`installed and configure <configuration>`
Doctrine.
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 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.
Creating Classes for the Database
---------------------------------
Every PHP object that you want to save in the database using Doctrine
is called an "Entity". The term "Entity" describes objects
that have an identity over many independent requests. This identity is
usually achieved by assigning a unique identifier to an entity.
In this tutorial the following ``Message`` PHP class will serve as the
example Entity:
.. code-block:: php
<?php
class Message
{
private $id;
private $text;
private $postedAt;
}
Because Doctrine is a generic library, it only knows about your
entities because you will describe their existence and structure using
mapping metadata, which is configuration that tells Doctrine how your
entity should be stored in the database. The documentation will often
speak of "mapping something", which means writing the mapping metadata
that describes your entity.
Doctrine provides several different ways to specify object-relational
mapping metadata:
- :doc:`Docblock Annotations <annotations-reference>`
- :doc:`XML <xml-mapping>`
- :doc:`YAML <yaml-mapping>`
- :doc:`PHP code <php-mapping>`
This manual will usually show mapping metadata via docblock annotations, though
many examples also show the equivalent configuration in YAML and XML.
.. note::
If you're wondering which mapping driver gives the best
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
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.
All metadata drivers perform equally. 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. If you're not using a metadata cache (not
recommended!) then the XML driver is the fastest.
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.
Marking our ``Message`` class as an entity for Doctrine is straightforward:
.. configuration-block::
@ -79,7 +74,7 @@ to be designated as an entity. This can be done through the
<?php
/** @Entity */
class MyPersistentClass
class Message
{
//...
}
@ -87,20 +82,20 @@ to be designated as an entity. This can be done through the
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<entity name="Message">
<!-- ... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
Message:
type: entity
# ...
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:
With no additional information, Doctrine expects the entity to be saved
into a table with the same name as the class in our case ``Message``.
You can change this by configuring information about the table:
.. configuration-block::
@ -109,9 +104,9 @@ name as the class name. In order to change that, you can use the
<?php
/**
* @Entity
* @Table(name="my_persistent_class")
* @Table(name="message")
*/
class MyPersistentClass
class Message
{
//...
}
@ -119,51 +114,131 @@ name as the class name. In order to change that, you can use the
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass" table="my_persistent_class">
<entity name="Message" table="message">
<!-- ... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
Message:
type: entity
table: my_persistent_class
table: message
# ...
Now instances of MyPersistentClass will be persisted into a table
named ``my_persistent_class``.
Now the class ``Message`` will be saved and fetched from the table ``message``.
Property Mapping
----------------
The next step after marking a PHP class as an entity is mapping its properties
to columns in a table.
To configure a property use the ``@Column`` docblock annotation. The ``type``
attribute specifies the :ref:`Doctrine Mapping Type <reference-mapping-types>`
to use for the field. If the type is not specified, ``string`` is used as the
default.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
class Message
{
/** @Column(type="integer") */
private $id;
/** @Column(length=140) */
private $text;
/** @Column(type="datetime", name="posted_at") */
private $postedAt;
}
.. code-block:: xml
<doctrine-mapping>
<entity name="Message">
<field name="id" type="integer" />
<field name="text" length="140" />
<field name="postedAt" column="posted_at" type="datetime" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
type: entity
fields:
id:
type: integer
text:
length: 140
postedAt:
type: datetime
name: posted_at
When we don't explicitly specify a column name via the ``name`` option, Doctrine
assumes the field name is also the column name. This means that:
* the ``id`` property will map to the column ``id`` using the type ``integer``;
* the ``text`` property will map to the column ``text`` with the default mapping type ``string``;
* the ``postedAt`` property will map to the ``posted_at`` column with the ``datetime`` type.
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.)
- ``columnDefinition``: (optional) Allows to define a custom
DDL snippet that is used to create the column. Warning: This normally
confuses the SchemaTool to always detect the column as changed.
- ``options``: (optional) Key-value pairs of options that get passed
to the underlying database platform when generating DDL statements.
.. _reference-mapping-types:
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.
The ``type`` option used in the ``@Column`` accepts any of the existing
Doctrine types or even your own custom types. A Doctrine type defines
the conversion between PHP and SQL types, independent from the database vendor
you are using. All Mapping Types that ship with Doctrine are fully portable
between the supported database systems.
For example, the Doctrine Mapping Type ``string`` defines the
mapping from a PHP string to an SQL VARCHAR (or VARCHAR2 etc.
As an example, the Doctrine Mapping Type ``string`` defines the
mapping from a PHP string to a 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.
- ``string``: Type that maps a SQL VARCHAR to a PHP string.
- ``integer``: Type that maps a 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 string.
- ``date``: Type that maps an SQL DATETIME to a PHP DateTime
- ``boolean``: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
- ``decimal``: Type that maps a SQL DECIMAL to a PHP string.
- ``date``: Type that maps a 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
- ``time``: Type that maps a SQL TIME to a PHP DateTime object.
- ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object.
- ``datetimetz``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP
- ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object with timezone.
- ``text``: Type that maps an SQL CLOB to a PHP string.
- ``text``: Type that maps a 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 array using
@ -178,20 +253,16 @@ built-in mapping types:
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.
- ``blob``: Type that maps an SQL BLOB to a PHP resource stream
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream
A cookbook article shows how to define :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`.
.. note::
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.
.. 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.
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::
@ -206,303 +277,42 @@ built-in mapping types:
on working with datetimes that gives hints for implementing
multi timezone applications.
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:
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
class MyPersistentClass
{
/** @Column(type="integer") */
private $id;
/** @Column(length=50) */
private $name; // type defaults to string
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<field name="id" type="integer" />
<field name="name" length="50" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
fields:
id:
type: integer
name:
length: 50
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:
.. configuration-block::
.. code-block:: php
<?php
/** @Column(name="db_name") */
private $name;
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<field name="name" column="db_name" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
fields:
name:
length: 50
column: db_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.
- ``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).
- ``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.)
.. _reference-basic-mapping-custom-mapping-types:
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:
.. code-block:: php
<?php
namespace My\Project\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* My custom datatype.
*/
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.
}
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.
}
public function getName()
{
return self::MYTYPE; // modify to match your constant name
}
}
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\Types\Type#addType($name, $className)``
method. See the following example:
.. code-block:: php
<?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:
.. code-block:: php
<?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:
.. code-block:: php
<?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.
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
--------------------------
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:
Every entity class must have an identifier/primary key. You can select
the field that serves as the identifier with the ``@Id``
annotation.
.. configuration-block::
.. code-block:: php
<?php
class MyPersistentClass
{
/** @Id @Column(type="integer") */
private $id;
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<id name="id" type="integer" />
<field name="name" length="50" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
id:
id:
type: integer
fields:
name:
length: 50
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:
.. configuration-block::
.. code-block:: php
<?php
class MyPersistentClass
class Message
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
private $id;
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<entity name="Message">
<id name="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="name" length="50" />
<!-- -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
Message:
type: entity
id:
id:
@ -510,15 +320,12 @@ like this:
generator:
strategy: AUTO
fields:
name:
length: 50
# fields here
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.
In most cases using the automatic generator strategy (``@GeneratedValue``) is
what you want. It defaults to the identifier generation mechanism your current
database vendor prefers: AUTO_INCREMENT with MySQL, SERIAL with PostgreSQL,
Sequences with Oracle and so on.
Identifier Generation Strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -526,12 +333,11 @@ 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 specify the
identifier generation strategy more explicitly, which allows to
identifier generation strategy more explicitly, which allows you 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. The preferred strategies
are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle
@ -564,30 +370,31 @@ besides specifying the sequence's name:
.. code-block:: php
<?php
class User
class Message
{
/**
* @Id
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100)
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<entity name="Message">
<id name="id" type="integer">
<generator strategy="SEQUENCE" />
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
<sequence-generator sequence-name="message_seq" allocation-size="100" initial-value="1" />
</id>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
Message:
type: entity
id:
id:
@ -595,7 +402,7 @@ besides specifying the sequence's name:
generator:
strategy: SEQUENCE
sequenceGenerator:
sequenceName: tablename_seq
sequenceName: message_seq
allocationSize: 100
initialValue: 1
@ -635,25 +442,22 @@ need to access the sequence once to generate the identifiers for
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.
with Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
one column. Some restrictions exist opposed to using a single identifier in
this case: The use of the ``@GeneratedValue`` annotation is not supported,
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.
More details on composite primary keys are discussed in a :doc:`dedicated tutorial
<../tutorials/composite-primary-keys>`.
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:
Sometimes it is necessary to quote a column or table name because of reserved
word conflicts. Doctrine does not quote identifiers automatically, because it
leads to more problems then it would solve. Quoting tables and column names
needs to be done explicitly using ticks in the definition.
.. code-block:: php
@ -666,18 +470,26 @@ according to the used database platform.
.. warning::
Identifier Quoting is not supported for join column
names or discriminator column names.
Identifier Quoting does not work for join column names or discriminator
column names unless you are using a custom ``QuoteStrategy``.
.. warning::
.. _reference-basic-mapping-custom-mapping-types:
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).
.. versionadded: 2.3
For more control over column quoting the ``Doctrine\ORM\Mapping\QuoteStrategy`` interface
was introduced in 2.3. It is invoked for every column, table, alias and other
SQL names. You can implement the QuoteStrategy and set it by calling
``Doctrine\ORM\Configuration#setQuoteStrategy()``.
.. versionadded: 2.4
The ANSI Quote Strategy was added, which assumes quoting is not necessary for any SQL name.
You can use it with the following code:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\AnsiQuoteStrategy;
$configuration->setQuoteStrategy(new AnsiQuoteStrategy());

View File

@ -106,7 +106,7 @@ 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 <http://redis.io/>`_. Also check
`here <https://github.com/nicolasff/phpredis/>`_ for how you can use
`A PHP extension for Redis <https://github.com/nicolasff/phpredis/>`_ for how you can use
and install Redis PHP extension.
Below is a simple example of how you could use the Redis cache
@ -211,49 +211,6 @@ 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.
.. code-block:: php
<?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.
.. code-block:: php
<?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.
.. code-block:: php
<?php
$deleted = $cacheDriver->deleteByPrefix('users_');
By Suffix
^^^^^^^^^
Just like we did above with the prefix you can do the same with a
suffix.
.. code-block:: php
<?php
$deleted = $cacheDriver->deleteBySuffix('_my_account');
All
^^^
@ -265,17 +222,6 @@ 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.
.. code-block:: php
<?php
echo $cacheDriver->count();
Namespaces
~~~~~~~~~~

View File

@ -1,77 +1,36 @@
Configuration
=============
Installation and Configuration
==============================
Bootstrapping Doctrine is a relatively simple procedure that
roughly exists of four steps:
Doctrine can be installed with `Composer <http://www.getcomposer.org>`_. For
older versions we still have `PEAR packages
<http://pear.doctrine-project.org>`_.
- `Installation <reference/installation>`
- Making sure Doctrine class files can be loaded on demand.
- Obtaining an EntityManager instance.
- Optional: Configuration of the Console Tool
Define the following requirement in your ``composer.json`` file:
::
{
"require": {
"doctrine/orm": "*"
}
}
Then call ``composer install`` from your command line. If you don't know
how Composer works, check out their `Getting Started
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
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
<?php
// Include Composer Autoload
// bootstrap.php
// Include Composer Autoload (relative to project root).
require_once "vendor/autoload.php";
Skip the rest of this section unless you are using another installation method.
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 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 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.
Tarball Download
^^^^^^^^^^^^^^^^
.. code-block:: php
<?php
// test.php
require 'Doctrine/ORM/Tools/Setup.php';
$lib = "/path/to/doctrine2-orm/lib";
Doctrine\ORM\Tools\Setup::registerAutoloadDirectory($lib);
Additional Symfony Components
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
--------------------------
@ -79,20 +38,16 @@ 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.
Quick Configuration Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
<?php
// bootstrap.php
require_once "vendor/autoload.php";
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
$paths = array("/path/to/entities-or-mapping-files");
$paths = array("/path/to/entity-files");
$isDevMode = false;
// the connection configuration
@ -104,456 +59,83 @@ setup methods:
);
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$em = EntityManager::create($dbParams, $config);
$entityManager = EntityManager::create($dbParams, $config);
// or if you prefer yaml or xml
Or if you prefer XML:
.. code-block:: php
<?php
$paths = array("/path/to/xml-mappings");
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
Or if you prefer YAML:
.. code-block:: php
<?php
$paths = array("/path/to/yml-mappings");
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
These setup commands make several assumptions:
Inside the ``Setup`` methods several assumptions are made:
- 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), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument.
- If `$devMode` is false, set ``setAutoGenerateProxyClasses(false)``
- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
- If `$isDevMode` 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 `$isDevMode` is false, set then proxy classes have to be explicitly created through the command line.
- If third argument `$proxyDir` is not set, use the systems temporary directory.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced
Configuration <reference/advanced-configuration>` section.
.. note::
You can learn more about the connection configuration in the
You can learn more about the database connection configuration in the
`Doctrine DBAL connection configuration reference <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
Full Configuration Example
~~~~~~~~~~~~~~~~~~~~~~~~~~
Setting up the Commandline Tool
-------------------------------
The configuration of the EntityManager requires a
``Doctrine\ORM\Configuration`` instance as well as some database
connection parameters. This example shows all the potential
steps of configuration.
Doctrine ships with a number of command line tools that are very helpful
during development. You can call this command from the Composer binary
directory:
.. code-block:: sh
$ php vendor/bin/doctrine
You need to register your applications EntityManager to the console tool
to make use of the tasks by creating a ``cli-config.php`` file with the
following content:
On Doctrine 2.4 and above:
.. code-block:: php
<?php
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration;
// ...
if ($applicationMode == "development") {
$cache = new \Doctrine\Common\Cache\ArrayCache;
} else {
$cache = new \Doctrine\Common\Cache\ApcCache;
}
$config = new Configuration;
$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(false);
}
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
);
$em = EntityManager::create($connectionOptions, $config);
use Doctrine\ORM\Tools\Console\ConsoleRunner;
.. note::
// replace with file to your own project bootstrap
require_once 'bootstrap.php';
Do not use Doctrine without a metadata and query cache!
Doctrine is 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.
// replace with mechanism to retrieve EntityManager in your app
$entityManager = GetEntityManager();
Configuration Options
---------------------
return ConsoleRunner::createHelperSet($entityManager);
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
Proxy Directory (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On Doctrine 2.3 and below:
.. code-block:: php
<?php
$config->setProxyDir($dir);
$config->getProxyDir();
// cli-config.php
require_once 'my_bootstrap.php';
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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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 can be configured with a factory method on
the ``Doctrine\ORM\Configuration``:
.. code-block:: php
<?php
$driverImpl = $config->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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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 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
per-request basis.
Query Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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 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
per-request basis.
SQL Logger (***Optional***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?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``.
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:
.. code-block:: php
<?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
Console or automatically by Doctrine. The configuration option that
controls this behavior is:
.. code-block:: php
<?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 Console to (re)generate the
proxy classes like so:
.. code-block:: php
$ ./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
<?php
use Doctrine\ORM\Proxy\Autoloader;
$proxyDir = "/path/to/proxies";
$proxyNamespace = "MyProxies";
Autoloader::register($proxyDir, $proxyNamespace);
If you want to execute additional logic to intercept the proxy file not found
state you can pass a closure as the third argument. It will be called with
the arguments proxydir, namespace and className when the proxy file could not
be found.
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:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\DriverChain;
$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.
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
<?php
$config->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
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.
If you installed Doctrine 2 through Composer, then the Doctrine command is
available to you in the bin-dir, by default at ``vendor/bin/doctrine-orm``.
In general the required code looks like this:
.. code-block:: php
<?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();
// Any way to access the EntityManager from your application
$em = GetMyEntityManager();
$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)
));

View File

@ -89,9 +89,8 @@ 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.
See the sub-section on `Functions, Operators, Aggregates`_ for
more information.
Joins
~~~~~
@ -428,13 +427,23 @@ Get all users visible on a given website that have chosen certain gender:
<?php
$query = $em->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:
.. versionadded:: 2.4
Starting with 2.4, the IDENTITY() DQL function also works for composite primary keys:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT IDENTITY(c.location, 'latitude') AS latitude, IDENTITY(c.location, 'longitude') AS longitude FROM Checkpoint c WHERE c.user = ?1');
Joins between entities without associations were not possible until version
2.4, where you can generate an arbitrary join with the following syntax:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u FROM User u JOIN Blacklist b WITH u.email = b.email');
Partial Object Syntax
^^^^^^^^^^^^^^^^^^^^^
@ -464,14 +473,15 @@ You use the partial syntax when joining as well:
"NEW" Operator Syntax
^^^^^^^^^^^^^^^^^^^^^
Using the ``NEW`` operator you can construct DTOs from queries.
.. versionadded:: 2.4
Using the ``NEW`` operator you can construct Data Transfer Objects (DTOs) directly from DQL 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.
- You can specify any PHP class, it's only require that the constructor of this class matches the ``NEW`` statement.
- 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
@ -910,7 +920,7 @@ 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
mode specifies a particular way in which a SQL result set is
transformed. Each hydration mode has its own dedicated method on
the Query class. Here they are:
@ -1188,7 +1198,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 <batch-processing>`_ section on details how
the `Batch Processing <batch-processing.html>`_ section on details how
to iterate large result sets.
Functions
@ -1291,7 +1301,7 @@ userland:
Query Cache (DQL Query Only)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Parsing a DQL query and converting it into an SQL query against the
Parsing a DQL query and converting it into a 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
@ -1582,7 +1592,7 @@ Scalar and Type Expressions
.. code-block:: php
ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression | BooleanPrimary | CaseExpression | InstanceOfExpression
StringExpression ::= StringPrimary | "(" Subselect ")"
StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
BooleanExpression ::= BooleanPrimary | "(" Subselect ")"
BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter
@ -1631,7 +1641,7 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS
InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
InstanceOfParameter ::= AbstractSchemaName | InputParameter
LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="

View File

@ -2,7 +2,9 @@ Events
======
Doctrine 2 features a lightweight event system that is part of the
Common package.
Common package. Doctrine uses it to dispatch system events, mainly
:ref:`lifecycle events <reference-events-lifecycle-events>`.
You can also use it for your own custom events.
The Event System
----------------
@ -18,47 +20,47 @@ manager.
$evm = new EventManager();
Now we can add some event listeners to the ``$evm``. Let's create a
``EventTest`` class to play around with.
``TestEvent`` class to play around with.
.. code-block:: php
<?php
class EventTest
class TestEvent
{
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);
$test = new TestEvent($evm);
Events can be dispatched by using the ``dispatchEvent()`` method.
.. code-block:: php
<?php
$evm->dispatchEvent(EventTest::preFoo);
$evm->dispatchEvent(EventTest::postFoo);
$evm->dispatchEvent(TestEvent::preFoo);
$evm->dispatchEvent(TestEvent::postFoo);
You can easily remove a listener with the ``removeEventListener()``
method.
@ -80,22 +82,28 @@ array of events it should be subscribed to.
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
{
public $preFooInvoked = false;
public function preFoo()
{
$this->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
.. note::
The array to return in the ``getSubscribedEvents`` method is a simple array
with the values being the event names. The subscriber must have a method
that is named exactly like the event.
Now when you dispatch an event, any event subscribers will be
notified for that event.
.. code-block:: php
@ -125,13 +133,14 @@ 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.
corresponding constant's value. If the constant's name and value differ
it contradicts the intention of using the constant and makes your code
harder to maintain.
An example for a correct notation can be found in the example
``EventTest`` above.
``TestEvent`` above.
.. _reference-events-lifecycle-events:
Lifecycle Events
----------------
@ -149,7 +158,7 @@ the life-time of their registered entities.
- prePersist - The prePersist event occurs for a given entity
before the respective EntityManager persist operation for that
entity is executed. It should be noted that this event is only triggered on
*initial* persist of an entity
*initial* persist of an entity (i.e. it does not trigger on future updates).
- 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
@ -163,8 +172,8 @@ the life-time of their registered entities.
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).
- preFlush - The preFlush event occurs at the very beginning of a flush
(annotations/xml/yaml). This event is not a lifecycle callback.
- 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
@ -173,7 +182,7 @@ the life-time of their registered entities.
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.
work. This event is not a lifecycle callback.
.. warning::
@ -195,12 +204,14 @@ 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 receives some kind of ``EventArgs``.
- 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.
called when the event is triggered. As of v2.4 they receive some kind
of ``EventArgs`` instance.
- Lifecycle Event Listeners and Subscribers are classes with specific callback
methods that receives some kind of ``EventArgs`` instance.
The EventArgs instance received by the listener gives access to the entity,
EntityManager and other relevant data.
.. note::
@ -214,52 +225,53 @@ listeners:
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.
Lifecycle Callbacks are defined on an entity class. They allow you to
trigger callbacks whenever an instance of that entity class experiences
a relevant lifecycle event. More than one callback can be defined for each
lifecycle event. Lifecycle Callbacks are best used for simple operations
specific to a particular entity class's lifecycle.
.. code-block:: php
<?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:i: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()
{
@ -267,8 +279,9 @@ event occurs.
}
}
Note that when using annotations you have to apply the
@HasLifecycleCallbacks marker annotation on the entity class.
Note that the methods set as lifecycle callbacks need to be public and,
when using these 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.
@ -282,58 +295,69 @@ can do it with the following.
name:
type: string(50)
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
postPersist: [ doStuffOnPostPersist ]
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersist ]
postPersist: [ doStuffOnPostPersist ]
In YAML the ``key`` of the lifecycleCallbacks entry is the event that you
are triggering on and the value is the method (or methods) to call. The allowed
event types are the ones listed in the previous Lifecycle Events section.
XML would look something like this:
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
<entity name="User">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
You just need to make sure a public ``doStuffOnPrePersist()`` and
``doStuffOnPostPersist()`` method is defined on your ``User``
model.
In XML the ``type`` of the lifecycle-callback entry is the event that you
are triggering on and the ``method`` is the method to call. The allowed event
types are the ones listed in the previous Lifecycle Events section.
When using YAML or XML you need to remember to create public methods to match the
callback names you defined. E.g. in these examples ``doStuffOnPrePersist()``,
``doOtherStuffOnPrePersist()`` and ``doStuffOnPostPersist()`` methods need to be
defined on your ``User`` model.
.. code-block:: php
<?php
// ...
class User
{
// ...
public function doStuffOnPrePersist()
{
// ...
}
public function doOtherStuffOnPrePersist()
{
// ...
}
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.
Lifecycle Callbacks Event Argument
-----------------------------------
@ -360,18 +384,80 @@ With the additional argument you have access to the
}
}
Listening to Lifecycle Events
-----------------------------
Listening and subscribing 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 behaviors between different entity
classes, yet require much more detailed knowledge about the inner
sit at a level above the entities and allow you to implement re-usable
behaviors across different entity classes.
Note that they 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
For event subscribers, there are no surprises. They declare the
lifecycle events in their ``getSubscribedEvents`` method and provide
public methods that expect the relevant arguments.
A lifecycle event listener looks like the following:
.. code-block:: php
<?php
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
class MyEventListener
{
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
// perhaps you only want to act on some "Product" entity
if ($entity instanceof Product) {
// do something with the Product
}
}
}
A lifecycle event subscriber may looks like this:
.. code-block:: php
<?php
use Doctrine\ORM\Events;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
class MyEventSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
Events::postUpdate,
);
}
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
// perhaps you only want to act on some "Product" entity
if ($entity instanceof Product) {
// do something with the Product
}
}
.. note::
Lifecycle events are triggered for all entities. It is the responsibility
of the listeners and subscribers to check if the entity is of a type
it wants to handle.
To register an event listener or subscriber, you have to hook it into the
EventManager that is passed to the EntityManager factory:
.. code-block:: php
@ -380,7 +466,7 @@ EventManager that is passed to the EntityManager factory:
$eventManager = new EventManager();
$eventManager->addEventListener(array(Events::preUpdate), new MyEventListener());
$eventManager->addEventSubscriber(new MyEventSubscriber());
$entityManager = EntityManager::create($dbOpts, $config, $eventManager);
You can also retrieve the event manager instance after the
@ -406,8 +492,8 @@ 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.
that (prior to version 2.4) you do not have access to the
EntityManager or UnitOfWork APIs inside these events.
prePersist
~~~~~~~~~~
@ -431,10 +517,10 @@ 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
- Doctrine will not recognize changes made to relations in a prePersist
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
@ -451,8 +537,8 @@ called during a flush operation.
preFlush
~~~~~~~~
``preFlush`` is called at ``EntityManager#flush()`` before
anything else. ``EntityManager#flush()`` can be called safely
``preFlush`` is called at ``EntityManager#flush()`` before
anything else. ``EntityManager#flush()`` can be called safely
inside its listeners.
.. code-block:: php
@ -497,25 +583,25 @@ mentioned sets. See this example:
{
$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) {
}
}
}
@ -523,20 +609,20 @@ mentioned sets. See this example:
The following restrictions apply to the onFlush event:
- If you create and persist a new entity in "onFlush", then
- 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
affected entity. This can be done by calling
``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``.
postFlush
~~~~~~~~~
``postFlush`` is called at the end of ``EntityManager#flush()``. ``EntityManager#flush()`` can be
called safely inside its listeners.
``postFlush`` is called at the end of ``EntityManager#flush()``.
``EntityManager#flush()`` can **NOT** be called safely inside its listeners.
.. code-block:: php
@ -615,7 +701,7 @@ lifecycle callback when there are expensive validations to call:
}
}
}
private function validateCreditCard($no)
{
// throw an exception to interrupt flush event. Transaction will be rolled back.
@ -629,7 +715,8 @@ Restrictions for this event:
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.
the event to modify primitive field values, e.g. use
``$eventArgs->setNewValue($field, $value);`` as in the Alice to Bob example above.
- 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
@ -655,9 +742,9 @@ Entity listeners
.. versionadded:: 2.4
An entity listeners is a lifecycle listener classes used for an entity.
An entity listener is a lifecycle listener class used for an entity.
- The entity listeners mapping may be applied to an entity class or mapped superclass.
- The entity listener's mapping may be applied to an entity class or mapped superclass.
- An entity listener is defined by mapping the entity class with the corresponding mapping.
.. configuration-block::
@ -699,9 +786,10 @@ An ``Entity Listener`` could be any class, by default it should be a class with
- Different from :ref:`reference-events-implementing-listeners` an ``Entity Listener`` is invoked just to the specified entity
- An entity listener method receives two arguments, the entity instance and the lifecycle event.
- A callback method could be defined by naming convention or specifying a method mapping.
- When the listener mapping is not given the parser will lookup for methods that match with the naming convention.
- When the listener mapping is given the parser won't lookup for any naming convention.
- The callback method can be defined by naming convention or specifying a method mapping.
- When a listener mapping is not given the parser will use the naming convention to look for a matching method,
e.g. it will look for a public ``preUpdate()`` method if you are listening to the ``preUpdate`` event.
- When a listener mapping is given the parser will not look for any methods using the naming convention.
.. code-block:: php
@ -714,8 +802,8 @@ An ``Entity Listener`` could be any class, by default it should be a class with
}
}
To define a specific event listener method
you should map the listener method using the event type mapping.
To define a specific event listener method (one that does not follow the naming convention)
you need to map the listener method using the event type mapping:
.. configuration-block::
@ -788,14 +876,14 @@ you should map the listener method using the event type mapping.
postRemove: [postRemoveHandler]
preRemove: [preRemoveHandler]
# ....
Entity listeners resolver
~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine invoke the listener resolver to get the listener instance.
Doctrine invokes the listener resolver to get the listener instance.
- An resolver allows you register a specific ``Entity Listener`` instance.
- A resolver allows you register a specific entity listener instance.
- You can also implement your own resolver by extending ``Doctrine\ORM\Mapping\DefaultEntityListenerResolver`` or implementing ``Doctrine\ORM\Mapping\EntityListenerResolver``
Specifying an entity listener instance :
@ -863,12 +951,12 @@ process and manipulate the instance.
.. code-block:: php
<?php
$test = new EventTest();
$test = new TestEvent();
$metadataFactory = $em->getMetadataFactory();
$evm = $em->getEventManager();
$evm->addEventListener(Events::loadClassMetadata, $test);
class EventTest
class TestEvent
{
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
{

View File

@ -143,11 +143,11 @@ See the documentation chapter on :doc:`inheritance mapping <inheritance-mapping>
the details.
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 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.
To find this out it has to execute a SQL query to look this information up in the database.
EntityGenerator
---------------
@ -162,7 +162,7 @@ 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.
Just from the details of the discriminator map the EntityGenerator cannot guess the inheritance hierarchy.
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.

View File

@ -7,7 +7,7 @@ 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).
The filter functionality works on SQL level. Whether an SQL query is generated
The filter functionality works on SQL level. Whether a 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.

View File

@ -260,6 +260,7 @@ 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.
.. _inheritence_mapping_overrides:
Overrides
---------

View File

@ -1,18 +1,5 @@
Installation
============
Doctrine was installable in many different ways, however `Composer <http://www.getcomposer.org>`_ turned out to be one of the best things for PHP in a long time.
This is why we moved all installation to use Composer only.
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 <configuration>`.
The installation chapter has moved to `Installation and Configuration
<reference/configuration>`_.

View File

@ -1,17 +1,21 @@
Native SQL
==========
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
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.
With ``NativeQuery`` you can execute native SELECT SQL statements
and map the results to Doctrine entities or any other result format
supported by Doctrine.
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.
In order to make this mapping possible, you need to describe
to Doctrine what columns in the result map to which entity property.
This description is represented by a ``ResultSetMapping`` object.
With this feature you can map arbitrary SQL code to objects, such as highly
vendor-optimized SQL or stored-procedures.
Writing ``ResultSetMapping`` from scratch is complex, but there is a convenience
wrapper around it called a ``ResultSetMappingBuilder``. It can generate
the mappings for you based on Entities and even generates the ``SELECT``
clause based on this information for you.
.. note::
@ -25,14 +29,75 @@ 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
``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.
Once you obtained an instance of a ``NativeQuery``, you can bind parameters to
it with the same API that ``Query`` has and execute it.
.. code-block:: php
<?php
use Doctrine\ORM\Query\ResultSetMapping;
$rsm = new ResultSetMapping();
// build rsm here
$query = $entityManager->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
ResultSetMappingBuilder
-----------------------
An easy start into ResultSet mapping is the ``ResultSetMappingBuilder`` object.
This has several benefits:
- The builder takes care of automatically updating your ``ResultSetMapping``
when the fields or associations change on the metadata of an entity.
- You can generate the required ``SELECT`` expression for a builder
by converting it to a string.
- The API is much simpler than the usual ``ResultSetMapping`` API.
One downside is that the builder API does not yet support entities
with inheritance hierachies.
.. code-block:: php
<?php
use Doctrine\ORM\Query\ResultSetMappingBuilder;
$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($entityManager);
$rsm->addRootEntityFromClassMetadata('MyProject\User', 'u');
$rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' => 'address_id'));
The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well.
.. versionadded:: 2.4
Starting with Doctrine ORM 2.4 you can generate the ``SELECT`` clause
from a ``ResultSetMappingBuilder``. You can either cast the builder
object to ``(string)`` and the DQL aliases are used as SQL table aliases
or use the ``generateSelectClause($tableAliases)`` method and pass
a mapping from DQL alias (key) to SQL alias (value)
.. code-block:: php
<?php
$selectClause = $builder->generateSelectClause(array(
'u' => 't1',
'g' => 't2'
));
$sql = "SELECT " . $selectClause . " FROM users t1 JOIN groups t2 ON t1.group_id = t2.id";
The ResultSetMapping
--------------------
@ -136,7 +201,7 @@ joined entity result.
Field results
~~~~~~~~~~~~~
A field result describes the mapping of a single column in an SQL
A field result describes the mapping of a single column in a 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
@ -165,7 +230,7 @@ column should be set.
Scalar results
~~~~~~~~~~~~~~
A scalar result describes the mapping of a single column in an SQL
A scalar result describes the mapping of a single column in a 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
@ -190,7 +255,7 @@ 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
A meta result describes a single column in a 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
@ -203,18 +268,21 @@ detail:
/**
* Adds a meta column (foreign key or discriminator column) to the result set.
*
* @param string $alias
* @param string $columnAlias
* @param string $columnName
* @param string $alias
* @param string $columnAlias
* @param string $columnName
* @param boolean $isIdentifierColumn
*/
public function addMetaResult($alias, $columnAlias, $columnName)
public function addMetaResult($alias, $columnAlias, $columnName, $isIdentifierColumn = false)
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
discriminator column) always belongs 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.
The fourth parameter should be set to true in case the primary key
of the entity is the foreign key you're adding.
Discriminator Column
~~~~~~~~~~~~~~~~~~~~
@ -366,35 +434,6 @@ 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
<?php
use Doctrine\ORM\Query\ResultSetMappingBuilder;
$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($entityManager);
$rsm->addRootEntityFromClassMetadata('MyProject\User', 'u');
$rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' => 'address_id'));
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.
Named Native Query
------------------
@ -543,12 +582,12 @@ it represents the name of a defined @SqlResultSetMapping.
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).
- Each field of the entity is bound to a 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
- ``__CLASS__`` is an alias for the mapped class
In the above example,
@ -564,7 +603,6 @@ Let's now see an implicit declaration of the property / column.
<?php
namespace MyProject\Model;
<?php
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
@ -648,7 +686,6 @@ followed by a dot ("."), followed by the name or the field or property of the pr
<?php
namespace MyProject\Model;
<?php
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
@ -766,7 +803,6 @@ you can use the resultClass attribute instead of resultSetMapping:
<?php
namespace MyProject\Model;
<?php
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
@ -814,7 +850,6 @@ You actually can even mix, entities and scalar returns in the same native query
<?php
namespace MyProject\Model;
<?php
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
@ -867,4 +902,4 @@ You actually can even mix, entities and scalar returns in the same native query
name: mappingCount
columnResult:
count:
name: count
name: count

View File

@ -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
@ -222,7 +222,7 @@ alternative syntax is available:
$qb->select('u')
->from('User u')
->where('u.id = :identifier')
->orderBy('u.name ASC');
->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
@ -433,6 +433,9 @@ complete list of supported helper methods available:
// Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%'))
public function like($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->notLike('u.firstname', $qb->expr()->literal('Gui%'))
public function notLike($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->between('u.id', '1', '10')
public function between($val, $x, $y); // Returns Expr\Func
@ -445,8 +448,8 @@ complete list of supported helper methods available:
// 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)
public function substr($x, $from, $len); // Returns Expr\Func
// Example - $qb->expr()->substring('u.firstname', 0, 1)
public function substring($x, $from, $len); // Returns Expr\Func
// Example - $qb->expr()->lower('u.firstname')
public function lower($x); // Returns Expr\Func

View File

@ -4,30 +4,29 @@ Tools
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.
The Doctrine Console is a Command Line Interface tool for simplifying common
administration tasks during the development of a project that uses Doctrine 2.
Take a look at the :doc:`Configuration <configuration>` for more
information how to setup the console command.
Take a look at the :doc:`Installation and Configuration <configuration>`
chapter for more information how to setup the console command.
Getting Help
~~~~~~~~~~~~
Display Help Information
~~~~~~~~~~~~~~~~~~~~~~~~
Type ``php vendor/bin/doctrine-orm`` on the command line and you should see an
Type ``php vendor/bin/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:
.. code-block:: php
$> php vendor/bin/doctrine-orm orm:generate-entities --help
$> php vendor/bin/doctrine orm:generate-entities --help
Configuration
~~~~~~~~~~~~~
Whenever the ``doctrine-orm`` command line tool is invoked, it can
Whenever the ``doctrine`` command line tool is invoked, it can
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
@ -132,6 +131,20 @@ The following Commands are currently available:
update the database schema of EntityManager Storage Connection or
generate the SQL output.
For these commands are also available aliases:
- ``orm:convert:d1-schema`` is alias for ``orm:convert-d1-schema``.
- ``orm:convert:mapping`` is alias for ``orm:convert-mapping``.
- ``orm:generate:entities`` is alias for ``orm:generate-entities``.
- ``orm:generate:proxies`` is alias for ``orm:generate-proxies``.
- ``orm:generate:repositories`` is alias for ``orm:generate-repositories``.
.. note::
Console also supports auto completion, for example, instead of
``orm:clear-cache:query`` you can use just ``o:c:q``.
Database Schema Generation
--------------------------

View File

@ -70,7 +70,6 @@ looks like this:
$em->getConnection()->commit();
} catch (Exception $e) {
$em->getConnection()->rollback();
$em->close();
throw $e;
}
@ -81,14 +80,12 @@ 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:
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, in addition to the obvious code reduction. An example that is
functionally equivalent to the previously shown code looks as follows:
.. code-block:: php
@ -104,8 +101,8 @@ previously shown code looks as follows:
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).
commit and rolls back the transaction when an
exception occurs.
Exception Handling
~~~~~~~~~~~~~~~~~~
@ -182,7 +179,7 @@ example we'll use an integer.
// ...
}
Alternatively a datetime type can be used (which maps to an SQL
Alternatively a datetime type can be used (which maps to a SQL
timestamp or datetime):
.. code-block:: php

View File

@ -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 queued for an SQL UPDATE operation. Only the fields
detected then the object is queued for a SQL UPDATE operation. Only the fields
that actually changed are updated.
This process has an obvious performance impact. The larger the size of the
@ -157,7 +157,7 @@ wishes to be hydrated. Default result-types include:
- 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:
algorithm-wise. It can build results with for example:
- Single table selects
- Joins with n:1 or 1:n cardinality, grouping belonging to the same parent.

View File

@ -2,27 +2,25 @@ 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
important to understand three main things:
object-oriented PHP code using references to other objects or
collections of objects.
Changes to associations in your code are not synchronized to the
database directly, only when calling ``EntityManager#flush()``.
There are other concepts you should know about when working
with associations in Doctrine:
- The :doc:`concept of owning and inverse sides <unitofwork-associations>`
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 :ref:`persistent fields <architecture_persistent_fields>` have to be instances of the
- When a bidirectional assocation is updated, Doctrine only checks
on one of both sides for these changes. This is called the :doc:`owning side <unitofwork-associations>`
of the association.
- A property with a reference to many entities has to be instances of the
``Doctrine\Common\Collections\Collection`` interface.
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
----------------------------
@ -44,10 +42,6 @@ information about its type and if it's the owning or inverse side.
* 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;
@ -55,10 +49,6 @@ information about its type and if it's the owning or inverse side.
* 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;
@ -93,7 +83,7 @@ information about its type and if it's the owning or inverse side.
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*
* @ManyToOne(targetEntity="User", inversedBy="authoredComments")
* @ManyToOne(targetEntity="User", inversedBy="commentsAuthored")
*/
private $author;
}
@ -474,6 +464,7 @@ removed from the system:
.. code-block:: php
<?php
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() AS $comment) {
@ -630,7 +621,7 @@ large collections.
$criteria = Criteria::create()
->where(Criteria::expr()->eq("birthday", "1982-02-17"))
->orderBy(array("username" => "ASC"))
->orderBy(array("username" => Criteria::ASC))
->setFirstResult(0)
->setMaxResults(20)
;
@ -689,7 +680,7 @@ interchangeably, independent of in-memory or sql-backed collections.
*/
public function setMaxResults($maxResults);
public function getOrderings();
public function getWhereExpresion();
public function getWhereExpression();
public function getFirstResult();
public function getMaxResults();
}

View File

@ -19,8 +19,6 @@ In order to work, this requires certain conventions:
convention and you are not forced to do this. You can change the
file extension easily enough.
-
.. code-block:: php
<?php
@ -47,9 +45,9 @@ 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.
- File Extension is .orm.yml
- Filenames are shortened, "MyProject\\Entities\\User" will become User.orm.yml
- You can add a global file and add multiple entities in this file.
Configuration of this client works a little bit different:
@ -116,4 +114,22 @@ of several common elements:
Be aware that class-names specified in the YAML files should be
fully qualified.
Reference
~~~~~~~~~~~~~~~~~~~~~~
Unique Constraints
------------------
It is possible to define unique constraints by the following declaration:
.. code-block:: yaml
# ECommerceProduct.orm.yml
ECommerceProduct:
type: entity
fields:
# definition of some fields
uniqueConstraints:
search_idx:
columns: [ name, email ]

View File

@ -8,12 +8,14 @@ Tutorials
:maxdepth: 1
tutorials/getting-started
tutorials/getting-started-database
tutorials/getting-started-models
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/in-ten-quick-steps
tutorials/override-field-association-mappings-in-subclasses
tutorials/pagination.rst
Reference Guide
---------------
@ -22,9 +24,9 @@ Reference Guide
:maxdepth: 1
:numbered:
reference/introduction
reference/architecture
reference/configuration
reference/installation
reference/configuration.rst
reference/faq
reference/basic-mapping
reference/association-mapping
@ -51,9 +53,9 @@ Reference Guide
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination.rst
reference/filters.rst
reference/namingstrategy.rst
reference/advanced-configuration.rst
Cookbook
@ -63,6 +65,7 @@ Cookbook
:maxdepth: 1
cookbook/aggregate-fields
cookbook/custom-mapping-types
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
@ -70,6 +73,7 @@ Cookbook
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/integrating-with-codeigniter
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities

View File

@ -19,7 +19,7 @@ the ID fields have to have their values set before you call ``EntityManager#pers
Primitive Types only
~~~~~~~~~~~~~~~~~~~~
Even in version 2.0 you can have composite keys as long as they only consist of the primative types
Even in version 2.0 you can have composite keys as long as they only consist of the primitive 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:
@ -129,7 +129,7 @@ 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
- Address object of a Person, the primary key of the address 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.

File diff suppressed because it is too large Load Diff

View File

@ -1,296 +0,0 @@
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 Composer.
For more information take a look at the :doc:`Installation help
<../reference/introduction>`.
1. Allows you to map PHP Objects to database tables
---------------------------------------------------
.. code-block:: php
<?php
class Post
{
protected $id;
protected $title;
protected $body;
}
::
mysql> 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
<?php
/** @Entity **/
class Post
{
/** @Id @GeneratedValue @Column(type="integer") **/
protected $id;
/** @Column(type="string") **/
protected $title;
/** @Column(type="text") **/
protected $body;
}
.. code-block:: yaml
Post:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
fields:
title:
type: string
body:
type: text
.. code-block:: xml
<?xml version="1.0" ?>
<doctrine-mapping>
<entity name="Post">
<id name="id type="integer">
<generator strategy="AUTO" />
</id>
<field name="title" type="string" />
<field name="body" type="text" />
</entity>
</doctrine-mapping>
3. Object References map to Foreign keys
----------------------------------------
.. code-block:: php
<?php
/** @Entity **/
class Post
{
// .. previous code
/**
* @ManyToOne(targetEntity="User")
**/
protected $author;
public function __construct(User $user)
{
$this->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
<?php
use Doctrine\Common\Collections\ArrayCollection;
class Post
{
// .. previous code
/**
* @OneToMany(targetEntity="Comment", mappedBy="post",
* cascade={"persist"})
**/
protected $comments;
public function __construct(User $author)
{
$this->author = $author;
$this->comments = 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
<?php
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
require_once "vendor/autoload.php";
$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
<?php
$entityManager->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
<?php
$entityManager->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
<?php
$post = $entityManager->find("Post", $id);
9. ..or through a Repository
----------------------------
.. code-block:: php
<?php
$authorRepository = $entityManager->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
<?php
// all posts and their comment count
$dql = "SELECT p, count(c.id) AS comments " .
"FROM Post p JOIN p.comments GROUP BY p";
$results = $entityManager->createQuery($dql)->getResult();

View File

@ -12,32 +12,51 @@ collection.
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
can specify the ``@OrderBy`` in the following way:
.. code-block:: php
.. configuration-block::
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @OrderBy({"name" = "ASC"})
**/
private $groups;
}
.. code-block:: php
.. code-block:: xml
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @OrderBy({"name" = "ASC"})
**/
private $groups;
}
<doctrine-mapping>
<entity name="User">
<many-to-many field="groups" target-entity="Group">
<order-by>
<order-by-field name="name" direction="ASC" />
</order-by>
</many-to-many>
</entity>
</doctrine-mapping>
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<many-to-many field="groups" target-entity="Group">
<order-by>
<order-by-field name="name" direction="ASC" />
</order-by>
</many-to-many>
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToMany:
groups:
orderBy: { 'name': 'ASC' }
targetEntity: Group
joinTable:
name: users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
The DQL Snippet in OrderBy is only allowed to consist of
unqualified, unquoted field names and of an optional ASC/DESC

View File

@ -87,4 +87,4 @@ The case for just extending a class would be just the same but:
// ...
}
Overriding is also supported via XML and YAML.
Overriding is also supported via XML and YAML (:ref:`examples <inheritence_mapping_overrides>`).

View File

@ -37,6 +37,7 @@
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-detach" type="orm:emptyType" minOccurs="0"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
@ -86,8 +87,11 @@
</xs:complexType>
<xs:complexType name="named-native-query">
<xs:sequence>
<xs:element name="query" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="query" type="xs:string" use="required"/>
<xs:attribute name="result-class" type="xs:string" />
<xs:attribute name="result-set-mapping" type="xs:string" />
</xs:complexType>
@ -95,14 +99,14 @@
<xs:complexType name="named-native-queries">
<xs:sequence>
<xs:element name="named-native-query" type="orm:named-native-query" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="entity-listener">
<xs:sequence>
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="0" maxOccurs="unbounded"/>
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="class" type="xs:string"/>
</xs:complexType>
@ -127,20 +131,24 @@
<xs:element name="field-result" type="orm:field-result" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="entity-class" type="xs:string" use="required" />
<xs:attribute name="discriminator-column" type="xs:string" use="optional" />
</xs:complexType>
<xs:complexType name="sql-result-set-mapping">
<xs:sequence>
<xs:element name="entity-result" type="orm:entity-result" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="column-result" type="orm:column-result" minOccurs="0" maxOccurs="unbounded" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="entity-result" type="orm:entity-result"/>
<xs:element name="column-result" type="orm:column-result"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
<xs:complexType name="sql-result-set-mappings">
<xs:sequence>
<xs:element name="sql-result-set-mapping" type="orm:sql-result-set-mapping" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:complexType>
@ -155,6 +163,7 @@
<xs:element name="entity-listeners" type="orm:entity-listeners" minOccurs="0" maxOccurs="1" />
<xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" />
<xs:element name="named-native-queries" type="orm:named-native-queries" minOccurs="0" maxOccurs="1" />
<xs:element name="sql-result-set-mappings" type="orm:sql-result-set-mappings" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="field" type="orm:field" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/>
@ -221,6 +230,7 @@
<xs:simpleType name="generator-strategy">
<xs:restriction base="xs:token">
<xs:enumeration value="NONE"/>
<xs:enumeration value="TABLE"/>
<xs:enumeration value="SEQUENCE"/>
<xs:enumeration value="IDENTITY"/>
@ -345,6 +355,7 @@
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="type" type="xs:NMTOKEN" />
<xs:attribute name="column" type="xs:NMTOKEN" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="association-key" type="xs:boolean" default="false" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:anyAttribute namespace="##other"/>
@ -379,7 +390,7 @@
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="name" type="xs:NMTOKEN" use="optional" />
<xs:attribute name="referenced-column-name" type="xs:NMTOKEN" use="optional" default="id" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="true" />
@ -504,7 +515,7 @@
<xs:complexType name="association-overrides">
<xs:sequence>
<xs:element name="association-override" type="orm:association-override" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:complexType>
@ -520,16 +531,33 @@
<xs:complexType name="attribute-overrides">
<xs:sequence>
<xs:element name="attribute-override" type="orm:attribute-override" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="attribute-override">
<xs:sequence>
<xs:element name="field" type="orm:field" minOccurs="1" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:element name="field" type="orm:attribute-override-field" minOccurs="1" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
</xs:complexType>
<xs:complexType name="attribute-override-field">
<xs:sequence>
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="type" type="xs:NMTOKEN" default="string" />
<xs:attribute name="column" type="xs:NMTOKEN" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="false" />
<xs:attribute name="version" type="xs:boolean" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:attribute name="precision" type="xs:integer" use="optional" />
<xs:attribute name="scale" type="xs:integer" use="optional" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
</xs:schema>

View File

@ -284,6 +284,10 @@ abstract class AbstractQuery
}
}
if ($value instanceof Mapping\ClassMetadata) {
return $value->name;
}
return $value;
}
@ -305,7 +309,7 @@ abstract class AbstractQuery
/**
* Allows to translate entity namespaces to full qualified names.
*
* @param EntityManager $em
* @param Query\ResultSetMapping $rsm
*
* @return void
*/
@ -386,7 +390,7 @@ abstract class AbstractQuery
}
/**
* Defines a cache driver to be used for caching result sets and implictly enables caching.
* Defines a cache driver to be used for caching result sets and implicitly enables caching.
*
* @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
*
@ -605,7 +609,12 @@ abstract class AbstractQuery
*/
public function getOneOrNullResult($hydrationMode = null)
{
$result = $this->execute(null, $hydrationMode);
try {
$result = $this->execute(null, $hydrationMode);
} catch (NoResultException $e) {
return null;
}
if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
return null;
@ -697,6 +706,18 @@ abstract class AbstractQuery
return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
}
/**
* Check if the query has a hint
*
* @param string $name The name of the hint
*
* @return bool False if the query does not have any hint
*/
public function hasHint($name)
{
return isset($this->_hints[$name]);
}
/**
* Return the key value map of query hints that are currently set.
*
@ -783,7 +804,7 @@ abstract class AbstractQuery
return $stmt;
}
$data = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);

View File

@ -19,20 +19,22 @@
namespace Doctrine\ORM;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Mapping\DefaultQuoteStrategy;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
use Doctrine\ORM\Mapping\EntityListenerResolver;
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
use Doctrine\ORM\Mapping\DefaultQuoteStrategy;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Mapping\EntityListenerResolver;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory;
/**
* Configuration container for all configuration options of Doctrine.
@ -88,7 +90,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Sets a boolean flag that indicates whether proxy classes should always be regenerated
* during each script execution.
*
* @param boolean $bool
* @param boolean|int $bool Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory
*
* @return void
*/
@ -779,4 +781,28 @@ class Configuration extends \Doctrine\DBAL\Configuration
return $this->_attributes['entityListenerResolver'];
}
/**
* Set the entity repository factory.
*
* @since 2.4
* @param \Doctrine\ORM\Repository\RepositoryFactory $repositoryFactory
*/
public function setRepositoryFactory(RepositoryFactory $repositoryFactory)
{
$this->_attributes['repositoryFactory'] = $repositoryFactory;
}
/**
* Get the entity repository factory.
*
* @since 2.4
* @return \Doctrine\ORM\Repository\RepositoryFactory
*/
public function getRepositoryFactory()
{
return isset($this->_attributes['repositoryFactory'])
? $this->_attributes['repositoryFactory']
: new DefaultRepositoryFactory();
}
}

View File

@ -0,0 +1,271 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Decorator;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Common\Persistence\ObjectManagerDecorator;
/**
* Base class for EntityManager decorators
*
* @since 2.4
* @author Lars Strojny <lars@strojny.net
*/
abstract class EntityManagerDecorator extends ObjectManagerDecorator implements EntityManagerInterface
{
/**
* @var EntityManagerInterface
*/
protected $wrapped;
/**
* @param EntityManagerInterface $wrapped
*/
public function __construct(EntityManagerInterface $wrapped)
{
$this->wrapped = $wrapped;
}
/**
* {@inheritdoc}
*/
public function getConnection()
{
return $this->wrapped->getConnection();
}
/**
* {@inheritdoc}
*/
public function getExpressionBuilder()
{
return $this->wrapped->getExpressionBuilder();
}
/**
* {@inheritdoc}
*/
public function beginTransaction()
{
return $this->wrapped->beginTransaction();
}
/**
* {@inheritdoc}
*/
public function transactional($func)
{
return $this->wrapped->transactional($func);
}
/**
* {@inheritdoc}
*/
public function commit()
{
return $this->wrapped->commit();
}
/**
* {@inheritdoc}
*/
public function rollback()
{
return $this->wrapped->rollback();
}
/**
* {@inheritdoc}
*/
public function createQuery($dql = '')
{
return $this->wrapped->createQuery($dql);
}
/**
* {@inheritdoc}
*/
public function createNamedQuery($name)
{
return $this->wrapped->createNamedQuery($name);
}
/**
* {@inheritdoc}
*/
public function createNativeQuery($sql, ResultSetMapping $rsm)
{
return $this->wrapped->createNativeQuery($sql, $rsm);
}
/**
* {@inheritdoc}
*/
public function createNamedNativeQuery($name)
{
return $this->wrapped->createNamedNativeQuery($name);
}
/**
* {@inheritdoc}
*/
public function createQueryBuilder()
{
return $this->wrapped->createQueryBuilder();
}
/**
* {@inheritdoc}
*/
public function getReference($entityName, $id)
{
return $this->wrapped->getReference($entityName, $id);
}
/**
* {@inheritdoc}
*/
public function getPartialReference($entityName, $identifier)
{
return $this->wrapped->getPartialReference($entityName, $identifier);
}
/**
* {@inheritdoc}
*/
public function close()
{
return $this->wrapped->close();
}
/**
* {@inheritdoc}
*/
public function copy($entity, $deep = false)
{
return $this->wrapped->copy($entity, $deep);
}
/**
* {@inheritdoc}
*/
public function lock($entity, $lockMode, $lockVersion = null)
{
return $this->wrapped->lock($entity, $lockMode, $lockVersion);
}
/**
* {@inheritdoc}
*/
public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion = null)
{
return $this->wrapped->find($entityName, $id, $lockMode, $lockVersion);
}
/**
* {@inheritdoc}
*/
public function flush($entity = null)
{
return $this->wrapped->flush($entity);
}
/**
* {@inheritdoc}
*/
public function getEventManager()
{
return $this->wrapped->getEventManager();
}
/**
* {@inheritdoc}
*/
public function getConfiguration()
{
return $this->wrapped->getConfiguration();
}
/**
* {@inheritdoc}
*/
public function isOpen()
{
return $this->wrapped->isOpen();
}
/**
* {@inheritdoc}
*/
public function getUnitOfWork()
{
return $this->wrapped->getUnitOfWork();
}
/**
* {@inheritdoc}
*/
public function getHydrator($hydrationMode)
{
return $this->wrapped->getHydrator($hydrationMode);
}
/**
* {@inheritdoc}
*/
public function newHydrator($hydrationMode)
{
return $this->wrapped->newHydrator($hydrationMode);
}
/**
* {@inheritdoc}
*/
public function getProxyFactory()
{
return $this->wrapped->getProxyFactory();
}
/**
* {@inheritdoc}
*/
public function getFilters()
{
return $this->wrapped->getFilters();
}
/**
* {@inheritdoc}
*/
public function isFiltersStateClean()
{
return $this->wrapped->isFiltersStateClean();
}
/**
* {@inheritdoc}
*/
public function hasFilters()
{
return $this->wrapped->hasFilters();
}
}

View File

@ -21,11 +21,8 @@ namespace Doctrine\ORM;
use Exception;
use Doctrine\Common\EventManager;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\FilterCollection;
@ -34,13 +31,35 @@ use Doctrine\Common\Util\ClassUtils;
/**
* The EntityManager is the central access point to ORM functionality.
*
* It is a facade to all different ORM subsystems such as UnitOfWork,
* Query Language and Repository API. Instantiation is done through
* the static create() method. The quickest way to obtain a fully
* configured EntityManager is:
*
* use Doctrine\ORM\Tools\Setup;
* use Doctrine\ORM\EntityManager;
*
* $paths = array('/path/to/entity/mapping/files');
*
* $config = Setup::createAnnotationMetadataConfiguration($paths);
* $dbParams = array('driver' => 'pdo_sqlite', 'memory' => true);
* $entityManager = EntityManager::create($dbParams, $config);
*
* For more information see
* {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html}
*
* You should never attempt to inherit from the EntityManager: Inheritance
* is not a valid extension point for the EntityManager. Instead you
* should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator}
* and wrap your entity manager in a decorator.
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class EntityManager implements ObjectManager
/* final */class EntityManager implements EntityManagerInterface
{
/**
* The used Configuration.
@ -63,13 +82,6 @@ class EntityManager implements ObjectManager
*/
private $metadataFactory;
/**
* The EntityRepository instances.
*
* @var array
*/
private $repositories = array();
/**
* The UnitOfWork used to coordinate object-level transactions.
*
@ -84,13 +96,6 @@ class EntityManager implements ObjectManager
*/
private $eventManager;
/**
* The maintained (cached) hydrators. One instance per type.
*
* @var array
*/
private $hydrators = array();
/**
* The proxy factory used to create dynamic proxies.
*
@ -98,6 +103,13 @@ class EntityManager implements ObjectManager
*/
private $proxyFactory;
/**
* The repository factory used to create dynamic repositories.
*
* @var \Doctrine\ORM\Repository\RepositoryFactory
*/
private $repositoryFactory;
/**
* The expression builder instance used to generate query expressions.
*
@ -129,9 +141,9 @@ class EntityManager implements ObjectManager
*/
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
{
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
@ -139,8 +151,9 @@ class EntityManager implements ObjectManager
$this->metadataFactory->setEntityManager($this);
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory(
$this->repositoryFactory = $config->getRepositoryFactory();
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory(
$this,
$config->getProxyDir(),
$config->getProxyNamespace(),
@ -286,7 +299,7 @@ class EntityManager implements ObjectManager
*
* @return \Doctrine\ORM\Query
*/
public function createQuery($dql = "")
public function createQuery($dql = '')
{
$query = new Query($this);
@ -736,28 +749,11 @@ class EntityManager implements ObjectManager
*
* @param string $entityName The name of the entity.
*
* @return EntityRepository The repository class.
* @return \Doctrine\ORM\EntityRepository The repository class.
*/
public function getRepository($entityName)
{
$entityName = ltrim($entityName, '\\');
if (isset($this->repositories[$entityName])) {
return $this->repositories[$entityName];
}
$metadata = $this->getClassMetadata($entityName);
$repositoryClassName = $metadata->customRepositoryClassName;
if ($repositoryClassName === null) {
$repositoryClassName = $this->config->getDefaultRepositoryClassName();
}
$repository = new $repositoryClassName($this, $metadata);
$this->repositories[$entityName] = $repository;
return $repository;
return $this->repositoryFactory->getRepository($this, $entityName);
}
/**
@ -834,17 +830,15 @@ class EntityManager implements ObjectManager
* This method caches the hydrator instances which is used for all queries that don't
* selectively iterate over the result.
*
* @deprecated
*
* @param int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
public function getHydrator($hydrationMode)
{
if ( ! isset($this->hydrators[$hydrationMode])) {
$this->hydrators[$hydrationMode] = $this->newHydrator($hydrationMode);
}
return $this->hydrators[$hydrationMode];
return $this->newHydrator($hydrationMode);
}
/**

View File

@ -0,0 +1,60 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION); HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE); ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Query\ResultSetMapping;
/**
* EntityManager interface
*
* @since 2.4
* @author Lars Strojny <lars@strojny.net
*/
interface EntityManagerInterface extends ObjectManager
{
public function getConnection();
public function getExpressionBuilder();
public function beginTransaction();
public function transactional($func);
public function commit();
public function rollback();
public function createQuery($dql = '');
public function createNamedQuery($name);
public function createNativeQuery($sql, ResultSetMapping $rsm);
public function createNamedNativeQuery($name);
public function createQueryBuilder();
public function getReference($entityName, $id);
public function getPartialReference($entityName, $identifier);
public function close();
public function copy($entity, $deep = false);
public function lock($entity, $lockMode, $lockVersion = null);
public function getEventManager();
public function getConfiguration();
public function isOpen();
public function getUnitOfWork();
public function getHydrator($hydrationMode);
public function newHydrator($hydrationMode);
public function getProxyFactory();
public function getFilters();
public function isFiltersStateClean();
public function hasFilters();
}

View File

@ -75,14 +75,15 @@ class EntityRepository implements ObjectRepository, Selectable
* Creates a new QueryBuilder instance that is prepopulated for this entity name.
*
* @param string $alias
* @param string $indexBy The index for the from.
*
* @return QueryBuilder
*/
public function createQueryBuilder($alias)
public function createQueryBuilder($alias, $indexBy = null)
{
return $this->_em->createQueryBuilder()
->select($alias)
->from($this->_entityName, $alias);
->from($this->_entityName, $alias, $indexBy);
}
/**

View File

@ -91,7 +91,7 @@ class ListenersInvoker
*
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param object $entity The Entity on which the event occured.
* @param object $entity The Entity on which the event occurred.
* @param \Doctrine\Common\EventArgs $event The Event args.
* @param integer $invoke Bitmask to invoke listeners.
*/

View File

@ -24,7 +24,7 @@ use Doctrine\ORM\EntityManager;
/**
* Provides event arguments for the onClear event.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.org
* @since 2.0
* @author Roman Borschel <roman@code-factory.de>

View File

@ -25,7 +25,7 @@ use Doctrine\ORM\EntityManager;
/**
* Provides event arguments for the preFlush event.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.org
* @since 2.0
* @author Roman Borschel <roman@code-factory.de>
@ -38,9 +38,6 @@ class OnFlushEventArgs extends EventArgs
*/
private $em;
//private $entitiesToPersist = array();
//private $entitiesToRemove = array();
/**
* Constructor.
*
@ -61,25 +58,4 @@ class OnFlushEventArgs extends EventArgs
return $this->em;
}
/*
public function addEntityToPersist($entity)
{
}
public function addEntityToRemove($entity)
{
}
public function addEntityToUpdate($entity)
{
}
public function getEntitiesToPersist()
{
return $this->_entitiesToPersist;
}
*/
}

View File

@ -24,7 +24,7 @@ use Doctrine\Common\EventArgs;
/**
* Provides event arguments for the postFlush event.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.org
* @since 2.0
* @author Daniel Freudenberger <df@rebuy.de>

View File

@ -25,7 +25,7 @@ use Doctrine\ORM\EntityManager;
/**
* Provides event arguments for the preFlush event.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.com
* @since 2.0
* @author Roman Borschel <roman@code-factory.de>

View File

@ -122,7 +122,7 @@ final class Events
/**
* The preFlush event occurs when the EntityManager#flush() operation is invoked,
* but before any changes to managed entites have been calculated. This event is
* but before any changes to managed entities have been calculated. This event is
* always raised right after EntityManager#flush() call.
*/
const preFlush = 'preFlush';

View File

@ -23,7 +23,7 @@ use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMException;
/**
* Special generator for application-assigned identifiers (doesnt really generate anything).
* Special generator for application-assigned identifiers (doesn't really generate anything).
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>

View File

@ -38,9 +38,9 @@ class BigIntegerIdentityGenerator extends AbstractIdGenerator
/**
* Constructor.
*
* @param string|null $seqName The name of the sequence to pass to lastInsertId()
* to obtain the last generated identifier within the current
* database session/connection, if any.
* @param string|null $sequenceName The name of the sequence to pass to lastInsertId()
* to obtain the last generated identifier within the current
* database session/connection, if any.
*/
public function __construct($sequenceName = null)
{

View File

@ -38,9 +38,9 @@ class IdentityGenerator extends AbstractIdGenerator
/**
* Constructor.
*
* @param string|null $seqName The name of the sequence to pass to lastInsertId()
* to obtain the last generated identifier within the current
* database session/connection, if any.
* @param string|null $sequenceName The name of the sequence to pass to lastInsertId()
* to obtain the last generated identifier within the current
* database session/connection, if any.
*/
public function __construct($sequenceName = null)
{

View File

@ -168,7 +168,7 @@ abstract class AbstractHydrator
}
/**
* Excutes one-time preparation tasks, once each time hydration is started
* Executes one-time preparation tasks, once each time hydration is started
* through {@link hydrateAll} or {@link iterate()}.
*
* @return void
@ -178,7 +178,7 @@ abstract class AbstractHydrator
}
/**
* Excutes one-time cleanup tasks at the end of a hydration that was initiated
* Executes one-time cleanup tasks at the end of a hydration that was initiated
* through {@link hydrateAll} or {@link iterate()}.
*
* @return void
@ -223,7 +223,7 @@ abstract class AbstractHydrator
* Puts the elements of a result row into a new array, grouped by the dql alias
* they belong to. The column names in the result set are mapped to their
* field names during this procedure as well as any necessary conversions on
* the values applied. Scalar values are kept in a specfic key 'scalars'.
* the values applied. Scalar values are kept in a specific key 'scalars'.
*
* @param array $data SQL Result Row.
* @param array &$cache Cache for column to field result information.
@ -321,7 +321,7 @@ abstract class AbstractHydrator
}
// in an inheritance hierarchy the same field could be defined several times.
// We overwrite this value so long we dont have a non-null value, that value we keep.
// We overwrite this value so long we don't have a non-null value, that value we keep.
// Per definition it cannot be that a field is defined several times and has several values.
if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) {
continue;

View File

@ -146,6 +146,7 @@ class ArrayHydrator extends AbstractHydrator
$baseElement =& $this->_resultPointers[$parent];
} else {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
continue;
}
@ -167,6 +168,7 @@ class ArrayHydrator extends AbstractHydrator
if ( ! $indexExists || ! $indexIsValid) {
$element = $data;
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element;
} else {
@ -183,7 +185,10 @@ class ArrayHydrator extends AbstractHydrator
} else {
$oneToOne = true;
if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
if (
( ! isset($nonemptyComponents[$dqlAlias])) &&
( ! isset($baseElement[$relationAlias]))
) {
$baseElement[$relationAlias] = null;
} else if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = $data;
@ -192,10 +197,9 @@ class ArrayHydrator extends AbstractHydrator
$coll =& $baseElement[$relationAlias];
if ($coll !== null) {
if (is_array($coll)) {
$this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
}
} else {
// It's a root result element
@ -204,22 +208,21 @@ class ArrayHydrator extends AbstractHydrator
// if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) {
$result[] = array($entityKey => null);
} else {
$result[] = null;
}
$result[] = $this->_rsm->isMixed
? array($entityKey => null)
: null;
$resultKey = $this->_resultCounter;
++$this->_resultCounter;
continue;
}
// Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $rowData[$dqlAlias];
if ($this->_rsm->isMixed) {
$element = array($entityKey => $element);
}
$element = $this->_rsm->isMixed
? array($entityKey => $rowData[$dqlAlias])
: $rowData[$dqlAlias];
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
@ -227,6 +230,7 @@ class ArrayHydrator extends AbstractHydrator
} else {
$resultKey = $this->_resultCounter;
$result[] = $element;
++$this->_resultCounter;
}
@ -234,11 +238,13 @@ class ArrayHydrator extends AbstractHydrator
} else {
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
$resultKey = $index;
/*if ($this->_rsm->isMixed) {
$result[] =& $result[$index];
++$this->_resultCounter;
}*/
}
$this->updateResultPointer($result, $index, $dqlAlias, false);
}
}
@ -247,11 +253,9 @@ class ArrayHydrator extends AbstractHydrator
if (isset($scalars)) {
if ( ! isset($resultKey) ) {
// this only ever happens when no object is fetched (scalar result only)
if (isset($this->_rsm->indexByMap['scalars'])) {
$resultKey = $row[$this->_rsm->indexByMap['scalars']];
} else {
$resultKey = $this->_resultCounter - 1;
}
$resultKey = isset($this->_rsm->indexByMap['scalars'])
? $row[$this->_rsm->indexByMap['scalars']]
: $this->_resultCounter - 1;
}
foreach ($scalars as $name => $value) {
@ -279,6 +283,12 @@ class ArrayHydrator extends AbstractHydrator
return;
}
if ($oneToOne) {
$this->_resultPointers[$dqlAlias] =& $coll;
return;
}
if ($index !== false) {
$this->_resultPointers[$dqlAlias] =& $coll[$index];
@ -289,12 +299,6 @@ class ArrayHydrator extends AbstractHydrator
return;
}
if ($oneToOne) {
$this->_resultPointers[$dqlAlias] =& $coll;
return;
}
end($coll);
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)];

View File

@ -49,7 +49,7 @@ class HydrationException extends \Doctrine\ORM\ORMException
public static function emptyDiscriminatorValue($dqlAlias)
{
return new self("The DQL alias '" . $dqlAlias . "' contains an entity ".
"of an inheritance hierachy with an empty discriminator value. This means " .
"of an inheritance hierarchy with an empty discriminator value. This means " .
"that the database contains inconsistent data with an empty " .
"discriminator value in a table row."
);

View File

@ -19,6 +19,7 @@
namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\ORM\UnitOfWork;
use PDO;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
@ -94,8 +95,8 @@ class ObjectHydrator extends AbstractHydrator
$this->resultCounter = 0;
if ( ! isset($this->_hints['deferEagerLoad'])) {
$this->_hints['deferEagerLoad'] = true;
if ( ! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) {
$this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true;
}
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
@ -152,7 +153,7 @@ class ObjectHydrator extends AbstractHydrator
*/
protected function cleanup()
{
$eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true;
$eagerLoad = (isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) && $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] == true;
parent::cleanup();

View File

@ -114,9 +114,8 @@ class SimpleObjectHydrator extends AbstractHydrator
}
// Convert field to a valid PHP value
if (isset($cache[$column]['field'])) {
$type = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type']);
$value = $type->convertToPHPValue($value, $this->_platform);
if (isset($cache[$column]['type'])) {
$value = Type::getType($cache[$column]['type'])->convertToPHPValue($value, $this->_platform);
}
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
@ -145,41 +144,36 @@ class SimpleObjectHydrator extends AbstractHydrator
*/
protected function hydrateColumnInfo($entityName, $column)
{
switch (true) {
case (isset($this->_rsm->fieldMappings[$column])):
$class = isset($this->declaringClasses[$column])
? $this->declaringClasses[$column]
: $this->class;
// If class is not part of the inheritance, ignore
if ( ! ($class->name === $entityName || is_subclass_of($entityName, $class->name))) {
return null;
}
if (isset($this->_rsm->fieldMappings[$column])) {
$name = $this->_rsm->fieldMappings[$column];
$class = isset($this->declaringClasses[$column])
? $this->declaringClasses[$column]
: $this->class;
return array(
'class' => $class,
'name' => $this->_rsm->fieldMappings[$column],
'field' => true,
);
// If class is not part of the inheritance, ignore
if ( ! ($class->name === $entityName || is_subclass_of($entityName, $class->name))) {
return null;
}
case (isset($this->_rsm->relationMap[$column])):
$class = isset($this->_rsm->relationMap[$column])
? $this->_rsm->relationMap[$column]
: $this->class;
// If class is not self referencing, ignore
if ( ! ($class === $entityName || is_subclass_of($entityName, $class))) {
return null;
}
// TODO: Decide what to do with associations. It seems original code is incomplete.
// One solution is to load the association, but it might require extra efforts.
return array('name' => $column);
default:
return array(
'name' => $this->_rsm->metaMappings[$column]
);
return array(
'name' => $name,
'type' => $class->fieldMappings[$name]['type']
);
}
if (isset($this->_rsm->metaMappings[$column])) {
return array(
'name' => $this->_rsm->metaMappings[$column],
'type' => (isset($this->_rsm->typeMappings[$column]) ? $this->_rsm->typeMappings[$column] : null)
);
}
// An ObjectHydrator should be used instead of SimpleObjectHydrator
if (isset($this->_rsm->relationMap[$column])) {
throw new \Exception(sprintf('Unable to retrieve association information for column "%s"', $column));
}
return null;
}
}

View File

@ -44,7 +44,7 @@ class SingleScalarHydrator extends AbstractHydrator
}
if ($numRows > 1 || count($data[key($data)]) > 1) {
throw new NonUniqueResultException();
throw new NonUniqueResultException('The query returned multiple rows. Change the query or use a different result function like getScalarResult().');
}
$cache = array();

View File

@ -0,0 +1,97 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* ANSI compliant quote strategy, this strategy does not apply any quote.
* To use this strategy all mapped tables and columns should be ANSI compliant.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class AnsiQuoteStrategy implements QuoteStrategy
{
/**
* {@inheritdoc}
*/
public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform)
{
return $class->fieldMappings[$fieldName]['columnName'];
}
/**
* {@inheritdoc}
*/
public function getTableName(ClassMetadata $class, AbstractPlatform $platform)
{
return $class->table['name'];
}
/**
* {@inheritdoc}
*/
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform)
{
return $definition['sequenceName'];
}
/**
* {@inheritdoc}
*/
public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform)
{
return $joinColumn['name'];
}
/**
* {@inheritdoc}
*/
public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform)
{
return $joinColumn['referencedColumnName'];
}
/**
* {@inheritdoc}
*/
public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform)
{
return $association['joinTable']['name'];
}
/**
* {@inheritdoc}
*/
public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform)
{
return $class->identifier;
}
/**
* {@inheritdoc}
*/
public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ClassMetadata $class = null)
{
return $platform->getSQLResultCasing($columnName . $counter);
}
}

View File

@ -200,7 +200,7 @@ class AssociationBuilder
} else if ($this->type == ClassMetadata::ONE_TO_ONE) {
$cm->mapOneToOne($mapping);
} else {
throw new \InvalidArgumentException("Type should be a ToOne Assocation here");
throw new \InvalidArgumentException("Type should be a ToOne Association here");
}
return $this->builder;
}

View File

@ -25,7 +25,7 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo;
/**
* Builder Object for ClassMetadata
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.com
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
@ -163,7 +163,7 @@ class ClassMetadataBuilder
}
/**
* Sets class as root of a joined table inheritance hierachy.
* Sets class as root of a joined table inheritance hierarchy.
*
* @return ClassMetadataBuilder
*/
@ -175,7 +175,7 @@ class ClassMetadataBuilder
}
/**
* Sets class as root of a single table inheritance hierachy.
* Sets class as root of a single table inheritance hierarchy.
*
* @return ClassMetadataBuilder
*/
@ -207,7 +207,7 @@ class ClassMetadataBuilder
}
/**
* Adds a subclass to this inheritance hierachy.
* Adds a subclass to this inheritance hierarchy.
*
* @param string $name
* @param string $class
@ -319,7 +319,7 @@ class ClassMetadataBuilder
}
/**
* Creates a ManyToOne Assocation Builder.
* Creates a ManyToOne Association Builder.
*
* Note: This method does not add the association, you have to call build() on the AssociationBuilder.
*
@ -361,7 +361,7 @@ class ClassMetadataBuilder
}
/**
* Adds simple inverse one-to-one assocation.
* Adds simple inverse one-to-one association.
*
* @param string $name
* @param string $targetEntity
@ -378,7 +378,7 @@ class ClassMetadataBuilder
}
/**
* Adds simple owning one-to-one assocation.
* Adds simple owning one-to-one association.
*
* @param string $name
* @param string $targetEntity
@ -398,7 +398,7 @@ class ClassMetadataBuilder
}
/**
* Creates a ManyToMany Assocation Builder.
* Creates a ManyToMany Association Builder.
*
* @param string $name
* @param string $targetEntity
@ -418,7 +418,7 @@ class ClassMetadataBuilder
}
/**
* Adds a simple owning many to many assocation.
* Adds a simple owning many to many association.
*
* @param string $name
* @param string $targetEntity
@ -438,7 +438,7 @@ class ClassMetadataBuilder
}
/**
* Adds a simple inverse many to many assocation.
* Adds a simple inverse many to many association.
*
* @param string $name
* @param string $targetEntity
@ -455,7 +455,7 @@ class ClassMetadataBuilder
}
/**
* Creates a one to many assocation builder.
* Creates a one to many association builder.
*
* @param string $name
* @param string $targetEntity
@ -475,7 +475,7 @@ class ClassMetadataBuilder
}
/**
* Adds simple OneToMany assocation.
* Adds simple OneToMany association.
*
* @param string $name
* @param string $targetEntity

View File

@ -22,7 +22,7 @@ namespace Doctrine\ORM\Mapping\Builder;
/**
* Field Builder
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.com
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>

View File

@ -22,7 +22,7 @@ namespace Doctrine\ORM\Mapping\Builder;
/**
* ManyToMany Association Builder
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.com
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>

View File

@ -22,7 +22,7 @@ namespace Doctrine\ORM\Mapping\Builder;
/**
* OneToMany Association Builder
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.com
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>

View File

@ -103,6 +103,10 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
if ( ! empty($parent->customGeneratorDefinition)) {
$class->setCustomGeneratorDefinition($parent->customGeneratorDefinition);
}
if ($parent->isMappedSuperclass) {
$class->setCustomRepositoryClass($parent->customRepositoryClassName);
}
@ -136,6 +140,11 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$this->completeIdGeneratorMapping($class);
}
foreach ($class->embeddedClasses as $property => $embeddableClass) {
$embeddableMetadata = $this->getMetadataFor($embeddableClass);
$class->inlineEmbeddable($property, $embeddableMetadata);
}
if ($parent && $parent->isInheritanceTypeSingleTable()) {
$class->setPrimaryTable($parent->table);
}
@ -171,7 +180,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
$this->wakeupReflection($class, $this->getReflectionService());
$this->validateRuntimeMetadata($class, $parent);
}
@ -193,7 +201,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
$class->validateIdentifier();
$class->validateAssocations();
$class->validateAssociations();
$class->validateLifecycleCallbacks($this->getReflectionService());
// verify inheritance
@ -319,7 +327,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
foreach ($parentClass->associationMappings as $field => $mapping) {
if ($parentClass->isMappedSuperclass) {
if ($mapping['type'] & ClassMetadata::TO_MANY && !$mapping['isOwningSide']) {
throw MappingException::illegalToManyAssocationOnMappedSuperclass($parentClass->name, $field);
throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field);
}
$mapping['sourceEntity'] = $subClass->name;
}
@ -447,7 +455,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$sequenceName = null;
$fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null;
if ($this->targetPlatform instanceof Platforms\PostgreSQLPlatform) {
if ($this->targetPlatform instanceof Platforms\PostgreSqlPlatform) {
$columnName = $class->getSingleIdentifierColumnName();
$quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
$sequenceName = $class->getTableName() . '_' . $columnName . '_seq';

View File

@ -246,6 +246,13 @@ class ClassMetadataInfo implements ClassMetadata
*/
public $isMappedSuperclass = false;
/**
* READ-ONLY: Wheather this class describes the mapping of an embeddable class.
*
* @var boolean
*/
public $isEmbeddedClass = false;
/**
* READ-ONLY: The names of the parent classes (ancestors).
*
@ -260,6 +267,13 @@ class ClassMetadataInfo implements ClassMetadata
*/
public $subClasses = array();
/**
* READ-ONLY: The names of all embedded classes based on properties.
*
* @var array
*/
public $embeddedClasses = array();
/**
* READ-ONLY: The named queries allowed to be called directly from Repository.
*
@ -358,7 +372,7 @@ class ClassMetadataInfo implements ClassMetadata
* - <b>scale</b> (integer, optional, schema-only)
* The scale of a decimal column. Only valid if the column type is decimal.
*
[* - <b>'unique'] (string, optional, schema-only)</b>
* - <b>'unique'</b> (string, optional, schema-only)
* Whether a unique constraint should be generated for the column.
*
* @var array
@ -509,7 +523,7 @@ class ClassMetadataInfo implements ClassMetadata
public $isIdentifierComposite = false;
/**
* READ-ONLY: Flag indicating wheather the identifier/primary key contains at least one foreign key association.
* READ-ONLY: Flag indicating whether the identifier/primary key contains at least one foreign key association.
*
* This flag is necessary because some code blocks require special treatment of this cases.
*
@ -631,7 +645,7 @@ class ClassMetadataInfo implements ClassMetadata
}
/**
* Gets the ReflectionPropertys of the mapped class.
* Gets the ReflectionProperties of the mapped class.
*
* @return array An array of ReflectionProperty instances.
*/
@ -831,6 +845,10 @@ class ClassMetadataInfo implements ClassMetadata
$serialized[] = 'lifecycleCallbacks';
}
if ($this->entityListeners) {
$serialized[] = 'entityListeners';
}
if ($this->namedQueries) {
$serialized[] = 'namedQueries';
}
@ -880,6 +898,15 @@ class ClassMetadataInfo implements ClassMetadata
$this->reflClass = $reflService->getClass($this->name);
foreach ($this->fieldMappings as $field => $mapping) {
if (isset($mapping['declaredField'])) {
$this->reflFields[$field] = new ReflectionEmbeddedProperty(
$reflService->getAccessibleProperty($this->name, $mapping['declaredField']),
$reflService->getAccessibleProperty($this->embeddedClasses[$mapping['declaredField']], $mapping['originalField']),
$this->embeddedClasses[$mapping['declaredField']]
);
continue;
}
$this->reflFields[$field] = isset($mapping['declared'])
? $reflService->getAccessibleProperty($mapping['declared'], $field)
: $reflService->getAccessibleProperty($this->name, $field);
@ -921,8 +948,12 @@ class ClassMetadataInfo implements ClassMetadata
*/
public function validateIdentifier()
{
if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
return;
}
// Verify & complete identifier mapping
if ( ! $this->identifier && ! $this->isMappedSuperclass) {
if ( ! $this->identifier) {
throw MappingException::identifierRequired($this->name);
}
@ -938,7 +969,7 @@ class ClassMetadataInfo implements ClassMetadata
*
* @throws MappingException
*/
public function validateAssocations()
public function validateAssociations()
{
foreach ($this->associationMappings as $mapping) {
if ( ! ClassLoader::classExists($mapping['targetEntity']) ) {
@ -1377,7 +1408,7 @@ class ClassMetadataInfo implements ClassMetadata
}
if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) {
throw MappingException::illegalToManyIdentifierAssoaction($this->name, $mapping['fieldName']);
throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']);
}
// Fetch mode. Default fetch mode to LAZY, if not set.
@ -1437,7 +1468,7 @@ class ClassMetadataInfo implements ClassMetadata
));
}
$uniqueContraintColumns = array();
$uniqueConstraintColumns = array();
foreach ($mapping['joinColumns'] as &$joinColumn) {
if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
if (count($mapping['joinColumns']) == 1) {
@ -1445,7 +1476,7 @@ class ClassMetadataInfo implements ClassMetadata
$joinColumn['unique'] = true;
}
} else {
$uniqueContraintColumns[] = $joinColumn['name'];
$uniqueConstraintColumns[] = $joinColumn['name'];
}
}
@ -1472,12 +1503,12 @@ class ClassMetadataInfo implements ClassMetadata
? $joinColumn['fieldName'] : $joinColumn['name'];
}
if ($uniqueContraintColumns) {
if ($uniqueConstraintColumns) {
if ( ! $this->table) {
throw new RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.");
}
$this->table['uniqueConstraints'][$mapping['fieldName']."_uniq"] = array(
'columns' => $uniqueContraintColumns
'columns' => $uniqueConstraintColumns
);
}
@ -1487,8 +1518,12 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
$mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
if ($mapping['orphanRemoval']) {
unset($mapping['unique']);
}
if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']);
throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']);
}
return $mapping;
@ -1543,15 +1578,18 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
}
$selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity']
&& (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));
if ( ! isset($mapping['joinTable']['joinColumns'])) {
$mapping['joinTable']['joinColumns'] = array(array(
'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity']),
'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null),
'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
'onDelete' => 'CASCADE'));
}
if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
$mapping['joinTable']['inverseJoinColumns'] = array(array(
'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity']),
'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null),
'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
'onDelete' => 'CASCADE'));
}
@ -2051,7 +2089,7 @@ class ClassMetadataInfo implements ClassMetadata
}
/**
* Checks if this entity is the root in any entity-inheritance-hierachy.
* Checks if this entity is the root in any entity-inheritance-hierarchy.
*
* @return bool
*/
@ -2151,9 +2189,8 @@ class ClassMetadataInfo implements ClassMetadata
public function mapField(array $mapping)
{
$this->_validateAndCompleteFieldMapping($mapping);
if (isset($this->fieldMappings[$mapping['fieldName']]) || isset($this->associationMappings[$mapping['fieldName']])) {
throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']);
}
$this->assertFieldNotMapped($mapping['fieldName']);
$this->fieldMappings[$mapping['fieldName']] = $mapping;
}
@ -2305,7 +2342,7 @@ class ClassMetadataInfo implements ClassMetadata
}
$entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']);
$resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\');
$resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass'];
@ -2401,9 +2438,7 @@ class ClassMetadataInfo implements ClassMetadata
{
$sourceFieldName = $assocMapping['fieldName'];
if (isset($this->fieldMappings[$sourceFieldName]) || isset($this->associationMappings[$sourceFieldName])) {
throw MappingException::duplicateFieldMapping($this->name, $sourceFieldName);
}
$this->assertFieldNotMapped($sourceFieldName);
$this->associationMappings[$sourceFieldName] = $assocMapping;
}
@ -2425,9 +2460,9 @@ class ClassMetadataInfo implements ClassMetadata
* lifecycle callbacks and lifecycle listeners.
*
* @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker
*
*
* @param string $lifecycleEvent The lifecycle event.
* @param object $entity The Entity on which the event occured.
* @param object $entity The Entity on which the event occurred.
*
* @return void
*/
@ -2779,8 +2814,12 @@ class ClassMetadataInfo implements ClassMetadata
*/
public function setSequenceGeneratorDefinition(array $definition)
{
if (isset($definition['name']) && $definition['name'] == '`') {
$definition['name'] = trim($definition['name'], '`');
if ( ! isset($definition['sequenceName'])) {
throw MappingException::missingSequenceName($this->name);
}
if ($definition['sequenceName'][0] == '`') {
$definition['sequenceName'] = trim($definition['sequenceName'], '`');
$definition['quoted'] = true;
}
@ -3015,4 +3054,63 @@ class ClassMetadataInfo implements ClassMetadata
return $className;
}
/**
* @param string $name
*
* @return mixed
*/
public function getMetadataValue($name) {
if (isset($this->$name)) {
return $this->$name;
}
return null;
}
/**
* Map Embedded Class
*
* @array $mapping
* @return void
*/
public function mapEmbedded(array $mapping)
{
$this->assertFieldNotMapped($mapping['fieldName']);
$this->embeddedClasses[$mapping['fieldName']] = $this->fullyQualifiedClassName($mapping['class']);
}
/**
* Inline the embeddable class
*
* @param string $property
* @param ClassMetadataInfo $embeddable
*/
public function inlineEmbeddable($property, ClassMetadataInfo $embeddable)
{
foreach ($embeddable->fieldMappings as $fieldMapping) {
$fieldMapping['declaredField'] = $property;
$fieldMapping['originalField'] = $fieldMapping['fieldName'];
$fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName'];
$fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName($property, $fieldMapping['columnName'], $this->reflClass->name, $embeddable->reflClass->name);
$this->mapField($fieldMapping);
}
}
/**
* @param string $fieldName
* @throws MappingException
*/
private function assertFieldNotMapped($fieldName)
{
if (isset($this->fieldMappings[$fieldName]) ||
isset($this->associationMappings[$fieldName]) ||
isset($this->embeddedClasses[$fieldName])) {
throw MappingException::duplicateFieldMapping($this->name, $fieldName);
}
}
}

View File

@ -21,7 +21,7 @@
namespace Doctrine\ORM\Mapping;
/**
* The default DefaultEntityListene
* The default DefaultEntityListener
*
* @since 2.4
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>

View File

@ -50,6 +50,14 @@ class DefaultNamingStrategy implements NamingStrategy
return $propertyName;
}
/**
* {@inheritdoc}
*/
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null)
{
return $propertyName.'_'.$embeddedColumnName;
}
/**
* {@inheritdoc}
*/

View File

@ -127,12 +127,15 @@ class DefaultQuoteStrategy implements QuoteStrategy
*/
public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ClassMetadata $class = null)
{
// Trim the column alias to the maximum identifier length of the platform.
// If the alias is to long, characters are cut off from the beginning.
// And strip non alphanumeric characters
// 1 ) Concatenate column name and counter
// 2 ) Trim the column alias to the maximum identifier length of the platform.
// If the alias is to long, characters are cut off from the beginning.
// 3 ) Strip non alphanumeric characters
// 4 ) Prefix with "_" if the result its numeric
$columnName = $columnName . $counter;
$columnName = substr($columnName, -$platform->getMaxIdentifierLength());
$columnName = preg_replace('/[^A-Za-z0-9_]/', '', $columnName);
$columnName = is_numeric($columnName) ? '_' . $columnName : $columnName;
return $platform->getSQLResultCasing($columnName);
}

View File

@ -85,6 +85,8 @@ class AnnotationDriver extends AbstractAnnotationDriver
$mappedSuperclassAnnot = $classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'];
$metadata->setCustomRepositoryClass($mappedSuperclassAnnot->repositoryClass);
$metadata->isMappedSuperclass = true;
} else if (isset($classAnnotations['Doctrine\ORM\Mapping\Embeddable'])) {
$metadata->isEmbeddedClass = true;
} else {
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
}
@ -247,7 +249,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
$mapping = array();
$mapping['fieldName'] = $property->getName();
// Check for JoinColummn/JoinColumns annotations
// Check for JoinColumn/JoinColumns annotations
$joinColumns = array();
if ($joinColumnAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumn')) {
@ -364,6 +366,9 @@ class AnnotationDriver extends AbstractAnnotationDriver
}
$metadata->mapManyToMany($mapping);
} else if ($embeddedAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Embedded')) {
$mapping['class'] = $embeddedAnnot->class;
$metadata->mapEmbedded($mapping);
}
}
@ -375,7 +380,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
$override = array();
$fieldName = $associationOverride->name;
// Check for JoinColummn/JoinColumns annotations
// Check for JoinColumn/JoinColumns annotations
if ($associationOverride->joinColumns) {
$joinColumns = array();
foreach ($associationOverride->joinColumns as $joinColumn) {
@ -533,6 +538,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
/**
* Parse the given JoinColumn as array
*
* @param JoinColumn $joinColumn
* @return array
*/
private function joinColumnToArray(JoinColumn $joinColumn)

View File

@ -19,12 +19,15 @@
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Common\Util\Inflector;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Mapping\MappingException;
/**
@ -84,248 +87,15 @@ class DatabaseDriver implements MappingDriver
}
/**
* Sets tables manually instead of relying on the reverse engeneering capabilities of SchemaManager.
* Set the namespace for the generated entities.
*
* @param array $entityTables
* @param array $manyToManyTables
* @param string $namespace
*
* @return void
*/
public function setTables($entityTables, $manyToManyTables)
public function setNamespace($namespace)
{
$this->tables = $this->manyToManyTables = $this->classToTableNames = array();
foreach ($entityTables as $table) {
$className = $this->getClassNameForTable($table->getName());
$this->classToTableNames[$className] = $table->getName();
$this->tables[$table->getName()] = $table;
}
foreach ($manyToManyTables as $table) {
$this->manyToManyTables[$table->getName()] = $table;
}
}
/**
* @return void
*
* @throws \Doctrine\ORM\Mapping\MappingException
*/
private function reverseEngineerMappingFromDatabase()
{
if ($this->tables !== null) {
return;
}
$tables = array();
foreach ($this->_sm->listTableNames() as $tableName) {
$tables[$tableName] = $this->_sm->listTableDetails($tableName);
}
$this->tables = $this->manyToManyTables = $this->classToTableNames = array();
foreach ($tables as $tableName => $table) {
/* @var $table \Doctrine\DBAL\Schema\Table */
if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$foreignKeys = $table->getForeignKeys();
} else {
$foreignKeys = array();
}
$allForeignKeyColumns = array();
foreach ($foreignKeys as $foreignKey) {
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
}
if ( ! $table->hasPrimaryKey()) {
throw new MappingException(
"Table " . $table->getName() . " has no primary key. Doctrine does not ".
"support reverse engineering from tables that don't have a primary key."
);
}
$pkColumns = $table->getPrimaryKey()->getColumns();
sort($pkColumns);
sort($allForeignKeyColumns);
if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) {
$this->manyToManyTables[$tableName] = $table;
} else {
// lower-casing is necessary because of Oracle Uppercase Tablenames,
// assumption is lower-case + underscore separated.
$className = $this->getClassNameForTable($tableName);
$this->tables[$tableName] = $table;
$this->classToTableNames[$className] = $tableName;
}
}
}
/**
* {@inheritDoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
$this->reverseEngineerMappingFromDatabase();
if (!isset($this->classToTableNames[$className])) {
throw new \InvalidArgumentException("Unknown class " . $className);
}
$tableName = $this->classToTableNames[$className];
$metadata->name = $className;
$metadata->table['name'] = $tableName;
$columns = $this->tables[$tableName]->getColumns();
$indexes = $this->tables[$tableName]->getIndexes();
try {
$primaryKeyColumns = $this->tables[$tableName]->getPrimaryKey()->getColumns();
} catch(SchemaException $e) {
$primaryKeyColumns = array();
}
if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
} else {
$foreignKeys = array();
}
$allForeignKeyColumns = array();
foreach ($foreignKeys as $foreignKey) {
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
}
$ids = array();
$fieldMappings = array();
foreach ($columns as $column) {
$fieldMapping = array();
if (in_array($column->getName(), $allForeignKeyColumns)) {
continue;
} else if ($primaryKeyColumns && in_array($column->getName(), $primaryKeyColumns)) {
$fieldMapping['id'] = true;
}
$fieldMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $column->getName(), false);
$fieldMapping['columnName'] = $column->getName();
$fieldMapping['type'] = strtolower((string) $column->getType());
if ($column->getType() instanceof \Doctrine\DBAL\Types\StringType) {
$fieldMapping['length'] = $column->getLength();
$fieldMapping['fixed'] = $column->getFixed();
} else if ($column->getType() instanceof \Doctrine\DBAL\Types\IntegerType) {
$fieldMapping['unsigned'] = $column->getUnsigned();
}
$fieldMapping['nullable'] = $column->getNotNull() ? false : true;
if (isset($fieldMapping['id'])) {
$ids[] = $fieldMapping;
} else {
$fieldMappings[] = $fieldMapping;
}
}
if ($ids) {
if (count($ids) == 1) {
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
}
foreach ($ids as $id) {
$metadata->mapField($id);
}
}
foreach ($fieldMappings as $fieldMapping) {
$metadata->mapField($fieldMapping);
}
foreach ($this->manyToManyTables as $manyTable) {
foreach ($manyTable->getForeignKeys() as $foreignKey) {
// foreign key maps to the table of the current entity, many to many association probably exists
if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) {
$myFk = $foreignKey;
$otherFk = null;
foreach ($manyTable->getForeignKeys() as $foreignKey) {
if ($foreignKey != $myFk) {
$otherFk = $foreignKey;
break;
}
}
if (!$otherFk) {
// the definition of this many to many table does not contain
// enough foreign key information to continue reverse engeneering.
continue;
}
$localColumn = current($myFk->getColumns());
$associationMapping = array();
$associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true);
$associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
if (current($manyTable->getColumns())->getName() == $localColumn) {
$associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
$associationMapping['joinTable'] = array(
'name' => strtolower($manyTable->getName()),
'joinColumns' => array(),
'inverseJoinColumns' => array(),
);
$fkCols = $myFk->getForeignColumns();
$cols = $myFk->getColumns();
for ($i = 0; $i < count($cols); $i++) {
$associationMapping['joinTable']['joinColumns'][] = array(
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
);
}
$fkCols = $otherFk->getForeignColumns();
$cols = $otherFk->getColumns();
for ($i = 0; $i < count($cols); $i++) {
$associationMapping['joinTable']['inverseJoinColumns'][] = array(
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
);
}
} else {
$associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
}
$metadata->mapManyToMany($associationMapping);
break;
}
}
}
foreach ($foreignKeys as $foreignKey) {
$foreignTable = $foreignKey->getForeignTableName();
$cols = $foreignKey->getColumns();
$fkCols = $foreignKey->getForeignColumns();
$localColumn = current($cols);
$associationMapping = array();
$associationMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $localColumn, true);
$associationMapping['targetEntity'] = $this->getClassNameForTable($foreignTable);
if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) {
$associationMapping['fieldName'] = $associationMapping['fieldName'] . "2";
}
if ($primaryKeyColumns && in_array($localColumn, $primaryKeyColumns)) {
$associationMapping['id'] = true;
}
for ($i = 0; $i < count($cols); $i++) {
$associationMapping['joinColumns'][] = array(
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
);
}
//Here we need to check if $cols are the same as $primaryKeyColums
if (!array_diff($cols,$primaryKeyColumns)) {
$metadata->mapOneToOne($associationMapping);
} else {
$metadata->mapManyToOne($associationMapping);
}
}
$this->namespace = $namespace;
}
/**
@ -373,6 +143,376 @@ class DatabaseDriver implements MappingDriver
$this->fieldNamesForColumns[$tableName][$columnName] = $fieldName;
}
/**
* Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager.
*
* @param array $entityTables
* @param array $manyToManyTables
*
* @return void
*/
public function setTables($entityTables, $manyToManyTables)
{
$this->tables = $this->manyToManyTables = $this->classToTableNames = array();
foreach ($entityTables as $table) {
$className = $this->getClassNameForTable($table->getName());
$this->classToTableNames[$className] = $table->getName();
$this->tables[$table->getName()] = $table;
}
foreach ($manyToManyTables as $table) {
$this->manyToManyTables[$table->getName()] = $table;
}
}
/**
* {@inheritDoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
$this->reverseEngineerMappingFromDatabase();
if ( ! isset($this->classToTableNames[$className])) {
throw new \InvalidArgumentException("Unknown class " . $className);
}
$tableName = $this->classToTableNames[$className];
$metadata->name = $className;
$metadata->table['name'] = $tableName;
$this->buildIndexes($metadata);
$this->buildFieldMappings($metadata);
$this->buildToOneAssociationMappings($metadata);
foreach ($this->manyToManyTables as $manyTable) {
foreach ($manyTable->getForeignKeys() as $foreignKey) {
// foreign key maps to the table of the current entity, many to many association probably exists
if ( ! (strtolower($tableName) === strtolower($foreignKey->getForeignTableName()))) {
continue;
}
$myFk = $foreignKey;
$otherFk = null;
foreach ($manyTable->getForeignKeys() as $foreignKey) {
if ($foreignKey != $myFk) {
$otherFk = $foreignKey;
break;
}
}
if ( ! $otherFk) {
// the definition of this many to many table does not contain
// enough foreign key information to continue reverse engineering.
continue;
}
$localColumn = current($myFk->getColumns());
$associationMapping = array();
$associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true);
$associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
if (current($manyTable->getColumns())->getName() == $localColumn) {
$associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
$associationMapping['joinTable'] = array(
'name' => strtolower($manyTable->getName()),
'joinColumns' => array(),
'inverseJoinColumns' => array(),
);
$fkCols = $myFk->getForeignColumns();
$cols = $myFk->getColumns();
for ($i = 0; $i < count($cols); $i++) {
$associationMapping['joinTable']['joinColumns'][] = array(
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
);
}
$fkCols = $otherFk->getForeignColumns();
$cols = $otherFk->getColumns();
for ($i = 0; $i < count($cols); $i++) {
$associationMapping['joinTable']['inverseJoinColumns'][] = array(
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
);
}
} else {
$associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
}
$metadata->mapManyToMany($associationMapping);
break;
}
}
}
/**
* @return void
*
* @throws \Doctrine\ORM\Mapping\MappingException
*/
private function reverseEngineerMappingFromDatabase()
{
if ($this->tables !== null) {
return;
}
$tables = array();
foreach ($this->_sm->listTableNames() as $tableName) {
$tables[$tableName] = $this->_sm->listTableDetails($tableName);
}
$this->tables = $this->manyToManyTables = $this->classToTableNames = array();
foreach ($tables as $tableName => $table) {
$foreignKeys = ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints())
? $table->getForeignKeys()
: array();
$allForeignKeyColumns = array();
foreach ($foreignKeys as $foreignKey) {
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
}
if ( ! $table->hasPrimaryKey()) {
throw new MappingException(
"Table " . $table->getName() . " has no primary key. Doctrine does not ".
"support reverse engineering from tables that don't have a primary key."
);
}
$pkColumns = $table->getPrimaryKey()->getColumns();
sort($pkColumns);
sort($allForeignKeyColumns);
if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) {
$this->manyToManyTables[$tableName] = $table;
} else {
// lower-casing is necessary because of Oracle Uppercase Tablenames,
// assumption is lower-case + underscore separated.
$className = $this->getClassNameForTable($tableName);
$this->tables[$tableName] = $table;
$this->classToTableNames[$className] = $tableName;
}
}
}
/**
* Build indexes from a class metadata.
*
* @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata
*/
private function buildIndexes(ClassMetadataInfo $metadata)
{
$tableName = $metadata->table['name'];
$indexes = $this->tables[$tableName]->getIndexes();
foreach($indexes as $index){
if ($index->isPrimary()) {
continue;
}
$indexName = $index->getName();
$indexColumns = $index->getColumns();
$constraintType = $index->isUnique()
? 'uniqueConstraints'
: 'indexes';
$metadata->table[$constraintType][$indexName]['columns'] = $indexColumns;
}
}
/**
* Build field mapping from class metadata.
*
* @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata
*/
private function buildFieldMappings(ClassMetadataInfo $metadata)
{
$tableName = $metadata->table['name'];
$columns = $this->tables[$tableName]->getColumns();
$primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]);
$foreignKeys = $this->getTableForeignKeys($this->tables[$tableName]);
$allForeignKeys = array();
foreach ($foreignKeys as $foreignKey) {
$allForeignKeys = array_merge($allForeignKeys, $foreignKey->getLocalColumns());
}
$ids = array();
$fieldMappings = array();
foreach ($columns as $column) {
if (in_array($column->getName(), $allForeignKeys)) {
continue;
}
$fieldMapping = $this->buildFieldMapping($tableName, $column);
if ($primaryKeys && in_array($column->getName(), $primaryKeys)) {
$fieldMapping['id'] = true;
$ids[] = $fieldMapping;
}
$fieldMappings[] = $fieldMapping;
}
// We need to check for the columns here, because we might have associations as id as well.
if ($ids && count($primaryKeys) == 1) {
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
}
foreach ($fieldMappings as $fieldMapping) {
$metadata->mapField($fieldMapping);
}
}
/**
* Build field mapping from a schema column definition
*
* @param string $tableName
* @param \Doctrine\DBAL\Schema\Column $column
*
* @return array
*/
private function buildFieldMapping($tableName, Column $column)
{
$fieldMapping = array(
'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false),
'columnName' => $column->getName(),
'type' => strtolower((string) $column->getType()),
'nullable' => ( ! $column->getNotNull()),
);
// Type specific elements
switch ($fieldMapping['type']) {
case Type::TARRAY:
case Type::BLOB:
case Type::GUID:
case Type::JSON_ARRAY:
case Type::OBJECT:
case Type::SIMPLE_ARRAY:
case Type::STRING:
case Type::TEXT:
$fieldMapping['length'] = $column->getLength();
$fieldMapping['fixed'] = $column->getFixed();
break;
case Type::DECIMAL:
case Type::FLOAT:
$fieldMapping['precision'] = $column->getPrecision();
$fieldMapping['scale'] = $column->getScale();
break;
case Type::INTEGER:
case Type::BIGINT:
case Type::SMALLINT:
$fieldMapping['unsigned'] = $column->getUnsigned();
break;
}
// Comment
if (($comment = $column->getComment()) !== null) {
$fieldMapping['comment'] = $comment;
}
// Default
if (($default = $column->getDefault()) !== null) {
$fieldMapping['default'] = $default;
}
return $fieldMapping;
}
/**
* Build to one (one to one, many to one) association mapping from class metadata.
*
* @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata
*/
private function buildToOneAssociationMappings(ClassMetadataInfo $metadata)
{
$tableName = $metadata->table['name'];
$primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]);
$foreignKeys = $this->getTableForeignKeys($this->tables[$tableName]);
foreach ($foreignKeys as $foreignKey) {
$foreignTableName = $foreignKey->getForeignTableName();
$fkColumns = $foreignKey->getColumns();
$fkForeignColumns = $foreignKey->getForeignColumns();
$localColumn = current($fkColumns);
$associationMapping = array(
'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true),
'targetEntity' => $this->getClassNameForTable($foreignTableName),
);
if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) {
$associationMapping['fieldName'] .= '2'; // "foo" => "foo2"
}
if ($primaryKeys && in_array($localColumn, $primaryKeys)) {
$associationMapping['id'] = true;
}
for ($i = 0; $i < count($fkColumns); $i++) {
$associationMapping['joinColumns'][] = array(
'name' => $fkColumns[$i],
'referencedColumnName' => $fkForeignColumns[$i],
);
}
// Here we need to check if $fkColumns are the same as $primaryKeys
if ( ! array_diff($fkColumns, $primaryKeys)) {
$metadata->mapOneToOne($associationMapping);
} else {
$metadata->mapManyToOne($associationMapping);
}
}
}
/**
* Retreive schema table definition foreign keys.
*
* @param \Doctrine\DBAL\Schema\Table $table
*
* @return array
*/
private function getTableForeignKeys(Table $table)
{
return ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints())
? $table->getForeignKeys()
: array();
}
/**
* Retreive schema table definition primary keys.
*
* @param \Doctrine\DBAL\Schema\Table $table
*
* @return array
*/
private function getTablePrimaryKeys(Table $table)
{
try {
return $table->getPrimaryKey()->getColumns();
} catch(SchemaException $e) {
// Do nothing
}
return array();
}
/**
* Returns the mapped class name for a table if it exists. Otherwise return "classified" version.
*
@ -412,16 +552,4 @@ class DatabaseDriver implements MappingDriver
}
return Inflector::camelize($columnName);
}
/**
* Set the namespace for the generated entities.
*
* @param string $namespace
*
* @return void
*/
public function setNamespace($namespace)
{
$this->namespace = $namespace;
}
}

View File

@ -19,6 +19,8 @@
require_once __DIR__.'/../Annotation.php';
require_once __DIR__.'/../Entity.php';
require_once __DIR__.'/../Embeddable.php';
require_once __DIR__.'/../Embedded.php';
require_once __DIR__.'/../MappedSuperclass.php';
require_once __DIR__.'/../InheritanceType.php';
require_once __DIR__.'/../DiscriminatorColumn.php';
@ -64,4 +66,4 @@ require_once __DIR__.'/../AssociationOverride.php';
require_once __DIR__.'/../AssociationOverrides.php';
require_once __DIR__.'/../AttributeOverride.php';
require_once __DIR__.'/../AttributeOverrides.php';
require_once __DIR__.'/../EntityListeners.php';
require_once __DIR__.'/../EntityListeners.php';

View File

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping\MappingException;
/**
* XmlDriver is a metadata driver that enables mapping through XML files.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
@ -224,7 +224,7 @@ class XmlDriver extends FileDriver
$metadata->table['options'] = $this->_parseOptions($xmlRoot->options->children());
}
// The mapping assignement is done in 2 times as a bug might occurs on some php/xml lib versions
// The mapping assignment is done in 2 times as a bug might occurs on some php/xml lib versions
// The internal SimpleXmlIterator get resetted, to this generate a duplicate field exception
$mappings = array();
// Evaluate <field ...> mappings
@ -234,6 +234,7 @@ class XmlDriver extends FileDriver
if (isset($mapping['version'])) {
$metadata->setVersionMapping($mapping);
unset($mapping['version']);
}
$metadata->mapField($mapping);
@ -686,7 +687,7 @@ class XmlDriver extends FileDriver
}
if (isset($fieldMapping['version']) && $fieldMapping['version']) {
$mapping['version'] = $fieldMapping['version'];
$mapping['version'] = $this->evaluateBoolean($fieldMapping['version']);
}
if (isset($fieldMapping['column-definition'])) {

View File

@ -264,6 +264,10 @@ class YamlDriver extends FileDriver
$mapping['columnDefinition'] = $idElement['columnDefinition'];
}
if (isset($idElement['options'])) {
$mapping['options'] = $idElement['options'];
}
$metadata->mapField($mapping);
if (isset($idElement['generator'])) {
@ -300,12 +304,14 @@ class YamlDriver extends FileDriver
if (isset($mapping['version'])) {
$metadata->setVersionMapping($mapping);
unset($mapping['version']);
}
$metadata->mapField($mapping);
}
}
// Evaluate oneToOne relationships
if (isset($element['oneToOne'])) {
foreach ($element['oneToOne'] as $name => $oneToOneElement) {

View File

@ -17,8 +17,12 @@
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Persisters;
namespace Doctrine\ORM\Mapping;
class UnionSubclassPersister extends BasicEntityPersister
/**
* @Annotation
* @Target("CLASS")
*/
final class Embeddable implements Annotation
{
}

View File

@ -0,0 +1,32 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class Embedded implements Annotation
{
/**
* @var string
*/
public $class;
}

View File

@ -49,7 +49,7 @@ interface EntityListenerResolver
/**
* Register a entity listener instance.
*
* @return object An entity listener
* @param object $object An entity listener
*/
function register($object);
}

View File

@ -529,7 +529,7 @@ class MappingException extends \Doctrine\ORM\ORMException
*/
public static function cannotVersionIdField($className, $fieldName)
{
return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported.");
return new self("Setting Id field '$fieldName' as versionable in entity class '$className' is not supported.");
}
/**
@ -561,7 +561,7 @@ class MappingException extends \Doctrine\ORM\ORMException
*
* @return MappingException
*/
public static function illegalToManyAssocationOnMappedSuperclass($className, $field)
public static function illegalToManyAssociationOnMappedSuperclass($className, $field)
{
return new self("It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass '".$className."#".$field."'.");
}
@ -632,7 +632,7 @@ class MappingException extends \Doctrine\ORM\ORMException
*
* @return MappingException
*/
public static function illegalInverseIdentifierAssocation($className, $field)
public static function illegalInverseIdentifierAssociation($className, $field)
{
return new self("An inverse association is not allowed to be identifier in '$className#$field'.");
}
@ -643,7 +643,7 @@ class MappingException extends \Doctrine\ORM\ORMException
*
* @return MappingException
*/
public static function illegalToManyIdentifierAssoaction($className, $field)
public static function illegalToManyIdentifierAssociation($className, $field)
{
return new self("Many-to-many or one-to-many associations are not allowed to be identifier in '$className#$field'.");
}
@ -668,8 +668,8 @@ class MappingException extends \Doctrine\ORM\ORMException
{
return new self(
"Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " .
"to be properly mapped in the inheritance hierachy. Alternatively you can make '".$className."' an abstract class " .
"to avoid this exception from occuring."
"to be properly mapped in the inheritance hierarchy. Alternatively you can make '".$className."' an abstract class " .
"to avoid this exception from occurring."
);
}
@ -685,8 +685,8 @@ class MappingException extends \Doctrine\ORM\ORMException
}
/**
* @param string $listenerName
* @param string $className
* @param string $methodName
*
* @return \Doctrine\ORM\Mapping\MappingException
*/
@ -757,4 +757,16 @@ class MappingException extends \Doctrine\ORM\ORMException
$cascades
));
}
/**
* @param string $className
*
* @return MappingException
*/
public static function missingSequenceName($className)
{
return new self(
sprintf('Missing "sequenceName" attribute for sequence id generator definition on class "%s".', $className)
);
}
}

View File

@ -49,6 +49,16 @@ interface NamingStrategy
*/
function propertyToColumnName($propertyName, $className = null);
/**
* Returns a column name for an embedded property.
*
* @param string $propertyName
* @param string $embeddedColumnName
*
* @return string
*/
function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null);
/**
* Returns the default reference column name.
*

View File

@ -0,0 +1,66 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping;
/**
* Acts as a proxy to a nested Property structure, making it look like
* just a single scalar property.
*
* This way value objects "just work" without UnitOfWork, Persisters or Hydrators
* needing any changes.
*
* TODO: Move this class into Common\Reflection
*/
class ReflectionEmbeddedProperty
{
private $parentProperty;
private $childProperty;
private $class;
public function __construct($parentProperty, $childProperty, $class)
{
$this->parentProperty = $parentProperty;
$this->childProperty = $childProperty;
$this->class = $class;
}
public function getValue($object)
{
$embeddedObject = $this->parentProperty->getValue($object);
if ($embeddedObject === null) {
return null;
}
return $this->childProperty->getValue($embeddedObject);
}
public function setValue($object, $value)
{
$embeddedObject = $this->parentProperty->getValue($object);
if ($embeddedObject === null) {
$embeddedObject = new $this->class; // TODO
$this->parentProperty->setValue($object, $embeddedObject);
}
$this->childProperty->setValue($embeddedObject, $value);
}
}

View File

@ -87,6 +87,14 @@ class UnderscoreNamingStrategy implements NamingStrategy
return $this->underscore($propertyName);
}
/**
* {@inheritdoc}
*/
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null)
{
return $this->underscore($propertyName).'_'.$embeddedColumnName;
}
/**
* {@inheritdoc}
*/

View File

@ -176,7 +176,7 @@ class ORMInvalidArgumentException extends \InvalidArgumentException
public static function invalidCompositeIdentifier()
{
return new self("Binding an entity with a composite primary key to a query is not supported. " .
"You should split the parameter into the explicit fields and bind them seperately.");
"You should split the parameter into the explicit fields and bind them separately.");
}
/**

View File

@ -71,8 +71,10 @@ class OptimisticLockException extends ORMException
*
* @return OptimisticLockException
*/
public static function lockFailedVersionMissmatch($entity, $expectedLockVersion, $actualLockVersion)
public static function lockFailedVersionMismatch($entity, $expectedLockVersion, $actualLockVersion)
{
$expectedLockVersion = ($expectedLockVersion instanceof \DateTime) ? $expectedLockVersion->getTimestamp() : $expectedLockVersion;
$actualLockVersion = ($actualLockVersion instanceof \DateTime) ? $actualLockVersion->getTimestamp() : $actualLockVersion;
return new self("The optimistic lock failed, version " . $expectedLockVersion . " was expected, but is actually ".$actualLockVersion, $entity);
}

View File

@ -517,6 +517,18 @@ final class PersistentCollection implements Collection, Selectable
*/
public function get($key)
{
if ( ! $this->initialized
&& $this->association['type'] === Mapping\ClassMetadataInfo::ONE_TO_MANY
&& $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY
&& isset($this->association['indexBy'])
) {
if (!$this->typeClass->isIdentifierComposite && $this->typeClass->isIdentifier($this->association['indexBy'])) {
return $this->em->find($this->typeClass->name, $key);
}
return $this->em->getUnitOfWork()->getCollectionPersister($this->association)->get($this, $key);
}
$this->initialize();
return $this->coll->get($key);
@ -745,6 +757,8 @@ final class PersistentCollection implements Collection, Selectable
*/
public function key()
{
$this->initialize();
return $this->coll->key();
}
@ -753,6 +767,8 @@ final class PersistentCollection implements Collection, Selectable
*/
public function current()
{
$this->initialize();
return $this->coll->current();
}
@ -761,6 +777,8 @@ final class PersistentCollection implements Collection, Selectable
*/
public function next()
{
$this->initialize();
return $this->coll->next();
}
@ -838,27 +856,20 @@ final class PersistentCollection implements Collection, Selectable
*/
public function matching(Criteria $criteria)
{
if ($this->isDirty) {
$this->initialize();
}
if ($this->initialized) {
return $this->coll->matching($criteria);
}
if ($this->association['type'] !== ClassMetadata::ONE_TO_MANY) {
throw new \RuntimeException("Matching Criteria on PersistentCollection only works on OneToMany assocations at the moment.");
throw new \RuntimeException("Matching Criteria on PersistentCollection only works on OneToMany associations at the moment.");
}
// If there are NEW objects we have to check if any of them matches the criteria
$newObjects = array();
if ($this->isDirty) {
$newObjects = $this->coll->matching($criteria)->toArray();
}
$id = $this->em
->getClassMetadata(get_class($this->owner))
->getSingleIdReflectionProperty()
->getValue($this->owner);
$builder = Criteria::expr();
$ownerExpression = $builder->eq($this->backRefFieldName, $id);
$ownerExpression = $builder->eq($this->backRefFieldName, $this->owner);
$expression = $criteria->getWhereExpression();
$expression = $expression ? $builder->andX($expression, $ownerExpression) : $ownerExpression;
@ -866,6 +877,6 @@ final class PersistentCollection implements Collection, Selectable
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']);
return new ArrayCollection(array_merge($persister->loadCriteria($criteria), $newObjects));
return new ArrayCollection($persister->loadCriteria($criteria));
}
}

View File

@ -98,7 +98,7 @@ abstract class AbstractCollectionPersister
*
* @param \Doctrine\ORM\PersistentCollection $coll
*
* @return void
* @return string
*/
abstract protected function getDeleteSQL(PersistentCollection $coll);
@ -108,7 +108,7 @@ abstract class AbstractCollectionPersister
*
* @param \Doctrine\ORM\PersistentCollection $coll
*
* @return void
* @return array
*/
abstract protected function getDeleteSQLParameters(PersistentCollection $coll);

View File

@ -37,7 +37,7 @@ use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
/**
* A BasicEntityPersiter maps an entity to a single table in a relational database.
* A BasicEntityPersister maps an entity to a single table in a relational database.
*
* A persister is always responsible for a single entity type.
*
@ -84,15 +84,16 @@ class BasicEntityPersister
* @var array
*/
static private $comparisonMap = array(
Comparison::EQ => '= %s',
Comparison::IS => 'IS %s',
Comparison::NEQ => '!= %s',
Comparison::GT => '> %s',
Comparison::GTE => '>= %s',
Comparison::LT => '< %s',
Comparison::LTE => '<= %s',
Comparison::IN => 'IN (%s)',
Comparison::NIN => 'NOT IN (%s)',
Comparison::EQ => '= %s',
Comparison::IS => '= %s',
Comparison::NEQ => '!= %s',
Comparison::GT => '> %s',
Comparison::GTE => '>= %s',
Comparison::LT => '< %s',
Comparison::LTE => '<= %s',
Comparison::IN => 'IN (%s)',
Comparison::NIN => 'NOT IN (%s)',
Comparison::CONTAINS => 'LIKE %s',
);
/**
@ -177,8 +178,8 @@ class BasicEntityPersister
protected $selectColumnListSql;
/**
* The JOIN SQL fragement used to eagerly load all many-to-one and one-to-one
* associations configured as FETCH_EAGER, aswell as all inverse one-to-one associations.
* The JOIN SQL fragment used to eagerly load all many-to-one and one-to-one
* associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
*
* @var string
*/
@ -463,7 +464,9 @@ class BasicEntityPersister
$params[] = $this->class->reflFields[$versionField]->getValue($entity);
switch ($versionFieldType) {
case Type::SMALLINT:
case Type::INTEGER:
case Type::BIGINT:
$set[] = $versionColumn . ' = ' . $versionColumn . ' + 1';
break;
@ -499,7 +502,7 @@ class BasicEntityPersister
}
// @Todo this only covers scenarios with no inheritance or of the same level. Is there something
// like self-referential relationship between different levels of an inheritance hierachy? I hope not!
// like self-referential relationship between different levels of an inheritance hierarchy? I hope not!
$selfReferential = ($mapping['targetEntity'] == $mapping['sourceEntity']);
$class = $this->class;
$association = $mapping;
@ -559,13 +562,35 @@ class BasicEntityPersister
*/
public function delete($entity)
{
$class = $this->class;
$em = $this->em;
$identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
$tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
$idColumns = $this->quoteStrategy->getIdentifierColumnNames($this->class, $this->platform);
$tableName = $this->quoteStrategy->getTableName($class, $this->platform);
$idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform);
$id = array_combine($idColumns, $identifier);
$types = array_map(function ($identifier) use ($class, $em) {
if (isset($class->fieldMappings[$identifier])) {
return $class->fieldMappings[$identifier]['type'];
}
$targetMapping = $em->getClassMetadata($class->associationMappings[$identifier]['targetEntity']);
if (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])) {
return $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type'];
}
if (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])) {
return $targetMapping->associationMappings[$targetMapping->identifier[0]]['type'];
}
throw ORMException::unrecognizedField($targetMapping->identifier[0]);
}, $class->identifier);
$this->deleteJoinTableRecords($identifier);
$this->conn->delete($tableName, $id);
$this->conn->delete($tableName, $id, $types);
}
/**
@ -851,7 +876,7 @@ class BasicEntityPersister
$stmt = $this->conn->executeQuery($query, $params, $types);
$hydrator = $this->em->newHydrator(($this->selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT);
return $hydrator->hydrateAll($stmt, $this->rsm, array('deferEagerLoads' => true));
return $hydrator->hydrateAll($stmt, $this->rsm, array(UnitOfWork::HINT_DEFEREAGERLOAD => true));
}
/**
@ -908,7 +933,7 @@ class BasicEntityPersister
$hydrator = $this->em->newHydrator(($this->selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT);
return $hydrator->hydrateAll($stmt, $this->rsm, array('deferEagerLoads' => true));
return $hydrator->hydrateAll($stmt, $this->rsm, array(UnitOfWork::HINT_DEFEREAGERLOAD => true));
}
/**
@ -939,7 +964,7 @@ class BasicEntityPersister
private function loadArrayFromStatement($assoc, $stmt)
{
$rsm = $this->rsm;
$hints = array('deferEagerLoads' => true);
$hints = array(UnitOfWork::HINT_DEFEREAGERLOAD => true);
if (isset($assoc['indexBy'])) {
$rsm = clone ($this->rsm); // this is necessary because the "default rsm" should be changed.
@ -962,8 +987,8 @@ class BasicEntityPersister
{
$rsm = $this->rsm;
$hints = array(
'deferEagerLoads' => true,
'collection' => $coll
UnitOfWork::HINT_DEFEREAGERLOAD => true,
'collection' => $coll
);
if (isset($assoc['indexBy'])) {
@ -1311,16 +1336,22 @@ class BasicEntityPersister
return '';
}
$columnList = array();
$columnList = array();
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
foreach ($assoc['joinColumns'] as $joinColumn) {
$type = null;
$isIdentifier = isset($assoc['id']) && $assoc['id'] === true;
$quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
$resultColumnName = $this->getSQLColumnAlias($joinColumn['name']);
$columnList[] = $this->getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) )
. '.' . $quotedColumn . ' AS ' . $resultColumnName;
$this->rsm->addMetaResult($alias, $resultColumnName, $quotedColumn, isset($assoc['id']) && $assoc['id'] === true);
if (isset($targetClass->fieldNames[$joinColumn['referencedColumnName']])) {
$type = $targetClass->fieldMappings[$targetClass->fieldNames[$joinColumn['referencedColumnName']]]['type'];
}
$this->rsm->addMetaResult($alias, $resultColumnName, $quotedColumn, $isIdentifier, $type);
}
return implode(', ', $columnList);
@ -1560,7 +1591,7 @@ class BasicEntityPersister
return '';
}
$visitor = new SqlExpressionVisitor($this);
$visitor = new SqlExpressionVisitor($this, $this->class);
return $visitor->dispatch($expression);
}
@ -1585,6 +1616,14 @@ class BasicEntityPersister
}
if ($comparison !== null) {
// special case null value handling
if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) {
return $condition . ' IS NULL';
} else if ($comparison === Comparison::NEQ && $value === null) {
return $condition . ' IS NOT NULL';
}
return $condition . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder);
}
@ -1847,16 +1886,7 @@ class BasicEntityPersister
return $value;
}
if ($this->em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
$idValues = $this->em->getUnitOfWork()->getEntityIdentifier($value);
return reset($idValues);
}
$class = $this->em->getClassMetadata(get_class($value));
$idValues = $class->getIdentifierValues($value);
return reset($idValues);
return $this->em->getUnitOfWork()->getSingleIdentifierValue($value);
}
/**
@ -1946,4 +1976,4 @@ class BasicEntityPersister
$sql = implode(' AND ', $filterClauses);
return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL"
}
}
}

View File

@ -184,6 +184,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// Execute inserts on subtables.
// The order doesn't matter because all child tables link to the root table via FK.
foreach ($subTableStmts as $tableName => $stmt) {
/** @var \Doctrine\DBAL\Statement $stmt */
$paramIndex = 1;
$data = isset($insertData[$tableName])
? $insertData[$tableName]
@ -196,7 +197,9 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
}
foreach ($data as $columnName => $value) {
$stmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]);
if (!is_array($id) || !isset($id[$columnName])) {
$stmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]);
}
}
$stmt->execute();
@ -302,31 +305,31 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// INNER JOIN parent tables
foreach ($this->class->parentClasses as $parentClassName) {
$contitions = array();
$conditions = array();
$parentClass = $this->em->getClassMetadata($parentClassName);
$tableAlias = $this->getSQLTableAlias($parentClassName);
$joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
foreach ($identifierColumn as $idColumn) {
$contitions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
$conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
}
$joinSql .= implode(' AND ', $contitions);
$joinSql .= implode(' AND ', $conditions);
}
// OUTER JOIN sub tables
foreach ($this->class->subClasses as $subClassName) {
$contitions = array();
$conditions = array();
$subClass = $this->em->getClassMetadata($subClassName);
$tableAlias = $this->getSQLTableAlias($subClassName);
$joinSql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
foreach ($identifierColumn as $idColumn) {
$contitions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
$conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
}
$joinSql .= implode(' AND ', $contitions);
$joinSql .= implode(' AND ', $conditions);
}
if ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) {

View File

@ -190,22 +190,22 @@ class ManyToManyPersister extends AbstractCollectionPersister
*/
protected function getDeleteSQLParameters(PersistentCollection $coll)
{
$identifier = $this->uow->getEntityIdentifier($coll->getOwner());
$mapping = $coll->getMapping();
$params = array();
$identifier = $this->uow->getEntityIdentifier($coll->getOwner());
// Optimization for single column identifier
if (count($mapping['relationToSourceKeyColumns']) === 1) {
$params[] = array_pop($identifier);
return $params;
return array(reset($identifier));
}
// Composite identifier
$sourceClass = $this->em->getClassMetadata(get_class($coll->getOwner()));
$sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
$params = array();
foreach ($mapping['relationToSourceKeyColumns'] as $srcColumn) {
$params[] = $identifier[$sourceClass->fieldNames[$srcColumn]];
foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) {
$params[] = isset($sourceClass->fieldNames[$refColumnName])
? $identifier[$sourceClass->fieldNames[$refColumnName]]
: $identifier[$sourceClass->getFieldForColumn($columnName)];
}
return $params;
@ -235,7 +235,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
foreach ($joinColumns as $joinColumn) {
$columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
$referencedName = $joinColumn['referencedColumnName'];
$conditions[] = $columnName . ' = ?';
$conditions[] = 't.' . $columnName . ' = ?';
$params[] = ($class->containsForeignIdentifier)
? $id[$class->getFieldForColumn($referencedName)]
: $id[$class->fieldNames[$referencedName]];
@ -361,12 +361,13 @@ class ManyToManyPersister extends AbstractCollectionPersister
$params = array();
foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
$whereClauses[] = $joinTableColumn . ' = ?';
$whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?';
if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
$params[] = ($targetClass->containsForeignIdentifier)
? $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]
: $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
continue;
}
@ -377,9 +378,12 @@ class ManyToManyPersister extends AbstractCollectionPersister
}
if ($addFilters) {
$quotedJoinTable .= ' t';
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
if ($filterSql) {
$quotedJoinTable .= ' t ' . $joinTargetEntitySQL;
$quotedJoinTable .= ' ' . $joinTargetEntitySQL;
$whereClauses[] = $filterSql;
}
}

View File

@ -32,6 +32,24 @@ use Doctrine\ORM\UnitOfWork;
*/
class OneToManyPersister extends AbstractCollectionPersister
{
/**
* {@inheritdoc}
*
* @override
*/
public function get(PersistentCollection $coll, $index)
{
$mapping = $coll->getMapping();
$uow = $this->em->getUnitOfWork();
$persister = $uow->getEntityPersister($mapping['targetEntity']);
if (!isset($mapping['indexBy'])) {
throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections.");
}
return $persister->load(array($mapping['mappedBy'] => $coll->getOwner(), $mapping['indexBy'] => $index), null, null, array(), 0, 1);
}
/**
* Generates the SQL UPDATE that updates a particular row's foreign
* key to null.

View File

@ -0,0 +1,20 @@
<?php
namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\ORMException;
class PersisterException extends ORMException
{
/**
* @return PersisterException
*/
static public function matchingAssocationFieldRequiresObject($class, $associationName)
{
return new self(sprintf(
"Cannot match on %s::%s with a non-object value. Matching objects by id is " .
"not compatible with matching on an in-memory collection, which compares objects by reference.",
$class, $associationName
));
}
}

View File

@ -178,7 +178,7 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
{
// Ensure that the filters are applied to the root entity of the inheritance tree
$targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName);
// we dont care about the $targetTableAlias, in a STI there is only one table.
// we don't care about the $targetTableAlias, in a STI there is only one table.
return parent::generateFilterConditionSQL($targetEntity, $targetTableAlias);
}

View File

@ -19,6 +19,8 @@
namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Common\Collections\Expr\ExpressionVisitor;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\Common\Collections\Expr\Value;
@ -37,12 +39,18 @@ class SqlExpressionVisitor extends ExpressionVisitor
*/
private $persister;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
private $classMetadata;
/**
* @param \Doctrine\ORM\Persisters\BasicEntityPersister $persister
*/
public function __construct(BasicEntityPersister $persister)
public function __construct(BasicEntityPersister $persister, ClassMetadata $classMetadata)
{
$this->persister = $persister;
$this->classMetadata = $classMetadata;
}
/**
@ -57,6 +65,14 @@ class SqlExpressionVisitor extends ExpressionVisitor
$field = $comparison->getField();
$value = $comparison->getValue()->getValue(); // shortcut for walkValue()
if (isset($this->classMetadata->associationMappings[$field]) &&
$value !== null &&
! is_object($value) &&
! in_array($comparison->getOperator(), array(Comparison::IN, Comparison::NIN))) {
throw PersisterException::matchingAssocationFieldRequiresObject($this->classMetadata->name, $field);
}
return $this->persister->getSelectConditionStatementSQL($field, $value, null, $comparison->getOperator());
}

View File

@ -50,9 +50,16 @@ class SqlValueVisitor extends ExpressionVisitor
*/
public function walkComparison(Comparison $comparison)
{
$value = $comparison->getValue()->getValue();
$value = $this->getValueFromComparison($comparison);
$field = $comparison->getField();
$operator = $comparison->getOperator();
if (($operator === Comparison::EQ || $operator === Comparison::IS) && $value === null) {
return;
} else if ($operator === Comparison::NEQ && $value === null) {
return;
}
$this->values[] = $value;
$this->types[] = array($field, $value);
}
@ -92,4 +99,20 @@ class SqlValueVisitor extends ExpressionVisitor
{
return array($this->values, $this->types);
}
/**
* Returns the value from a Comparison. In case of a CONTAINS comparison,
* the value is wrapped in %-signs, because it will be used in a LIKE clause.
*
* @param \Doctrine\Common\Collections\Expr\Comparison $comparison
* @return mixed
*/
protected function getValueFromComparison(Comparison $comparison)
{
$value = $comparison->getValue()->getValue();
return $comparison->getOperator() == Comparison::CONTAINS
? "%{$value}%"
: $value;
}
}

View File

@ -22,7 +22,7 @@ namespace Doctrine\ORM;
/**
* Pessimistic Lock Exception
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.com
* @since 1.0
* @author Benjamin Eberlei <kontakt@beberlei.de>

Some files were not shown because too many files have changed in this diff Show More