Merge remote-tracking branch 'schmittjoh/ValueObjects'
This commit is contained in:
commit
d4e6618b28
4
.coveralls.yml
Normal file
4
.coveralls.yml
Normal file
@ -0,0 +1,4 @@
|
||||
# for php-coveralls
|
||||
service_name: travis-ci
|
||||
src_dir: lib
|
||||
coverage_clover: build/logs/clover.xml
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ lib/Doctrine/DBAL
|
||||
.project
|
||||
.idea
|
||||
vendor/
|
||||
composer.lock
|
||||
|
@ -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
|
||||
|
||||
after_script:
|
||||
- php vendor/bin/coveralls -v
|
||||
|
@ -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),
|
||||
|
47
UPGRADE.md
47
UPGRADE.md
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
199
build.xml
@ -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>
|
||||
|
@ -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
507
composer.lock
generated
@ -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": [
|
||||
|
||||
]
|
||||
}
|
@ -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
|
||||
|
97
docs/en/cookbook/custom-mapping-types.rst
Normal file
97
docs/en/cookbook/custom-mapping-types.rst
Normal 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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
468
docs/en/reference/advanced-configuration.rst
Normal file
468
docs/en/reference/advanced-configuration.rst
Normal 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();
|
||||
|
@ -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>`
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
~~~~~~~~~~
|
||||
|
||||
|
@ -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;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
// ...
|
||||
// replace with file to your own project bootstrap
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
if ($applicationMode == "development") {
|
||||
$cache = new \Doctrine\Common\Cache\ArrayCache;
|
||||
} else {
|
||||
$cache = new \Doctrine\Common\Cache\ApcCache;
|
||||
}
|
||||
// replace with mechanism to retrieve EntityManager in your app
|
||||
$entityManager = GetEntityManager();
|
||||
|
||||
$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');
|
||||
return ConsoleRunner::createHelperSet($entityManager);
|
||||
|
||||
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***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
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)
|
||||
));
|
||||
|
@ -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 ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
|
||||
|
||||
|
@ -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,12 +20,12 @@ 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';
|
||||
@ -50,15 +52,15 @@ Now we can add some event listeners to the ``$evm``. Let's create a
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -95,7 +97,13 @@ array of events it should be subscribed to.
|
||||
$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,7 +172,7 @@ 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).
|
||||
(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
|
||||
@ -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,10 +225,11 @@ 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
|
||||
|
||||
@ -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,9 +295,13 @@ can do it with the following.
|
||||
name:
|
||||
type: string(50)
|
||||
lifecycleCallbacks:
|
||||
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
|
||||
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
|
||||
@ -307,9 +324,14 @@ XML would look something like this:
|
||||
|
||||
</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
|
||||
|
||||
@ -325,15 +347,17 @@ model.
|
||||
// ...
|
||||
}
|
||||
|
||||
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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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::
|
||||
|
||||
@ -793,9 +881,9 @@ you should map the listener method using the event type mapping.
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
---------
|
||||
|
@ -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>`_.
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
--------------------------
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 ]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
@ -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();
|
@ -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
|
||||
{
|
||||
// ...
|
||||
.. code-block:: php
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
* @OrderBy({"name" = "ASC"})
|
||||
**/
|
||||
private $groups;
|
||||
}
|
||||
<?php
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
.. code-block:: xml
|
||||
/**
|
||||
* @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
|
||||
|
@ -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>`).
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
271
lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php
Normal file
271
lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php
Normal 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
60
lib/Doctrine/ORM/EntityManagerInterface.php
Normal file
60
lib/Doctrine/ORM/EntityManagerInterface.php
Normal 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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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';
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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)];
|
||||
|
||||
|
@ -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."
|
||||
);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
97
lib/Doctrine/ORM/Mapping/AnsiQuoteStrategy.php
Normal file
97
lib/Doctrine/ORM/Mapping/AnsiQuoteStrategy.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -2427,7 +2462,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -50,6 +50,14 @@ class DefaultNamingStrategy implements NamingStrategy
|
||||
return $propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null)
|
||||
{
|
||||
return $propertyName.'_'.$embeddedColumnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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'])) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
{
|
||||
}
|
32
lib/Doctrine/ORM/Mapping/Embedded.php
Normal file
32
lib/Doctrine/ORM/Mapping/Embedded.php
Normal 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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
66
lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php
Normal file
66
lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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}
|
||||
*/
|
||||
|
@ -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.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
20
lib/Doctrine/ORM/Persisters/PersisterException.php
Normal file
20
lib/Doctrine/ORM/Persisters/PersisterException.php
Normal 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
|
||||
));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -50,8 +50,15 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user