Merge branch 'master' of git://github.com/doctrine/doctrine2 into ProxyIdentifer
Conflicts: lib/Doctrine/ORM/Proxy/ProxyFactory.php
This commit is contained in:
commit
f47e1feac6
@ -1,6 +1,6 @@
|
||||
# Doctrine 2 ORM
|
||||
|
||||
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides transparent persistence
|
||||
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),
|
||||
inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
|
||||
|
25
UPGRADE_TO_2_1
Normal file
25
UPGRADE_TO_2_1
Normal file
@ -0,0 +1,25 @@
|
||||
This document details all the possible changes that you should investigate when updating
|
||||
your project from Doctrine 2.0.x to 2.1
|
||||
|
||||
## Interface for EntityRepository
|
||||
|
||||
The EntityRepository now has an interface Doctrine\Common\Persistence\ObjectRepository. This means that your classes that override EntityRepository and extend find(), findOneBy() or findBy() must be adjusted to follow this interface.
|
||||
|
||||
## AnnotationReader changes
|
||||
|
||||
The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory the operation of the new reader should be backwards compatible, but it has to be setup differently to work that way:
|
||||
|
||||
// new call to the AnnotationRegistry
|
||||
\Doctrine\Common\Annotations\AnnotationRegistry::registerFile('/doctrine-src/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');
|
||||
|
||||
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
|
||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||
// new code necessary starting here
|
||||
$reader->setIgnoreNotImportedAnnotations(true);
|
||||
$reader->setEnableParsePhpImports(false);
|
||||
$reader = new \Doctrine\Common\Annotations\CachedReader(
|
||||
new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache()
|
||||
);
|
||||
|
||||
This is already done inside the ``$config->newDefaultAnnotationDriver``, so everything should automatically work if you are using this method. You can verify if everything still works by executing a console command such as schema-validate that loads all metadata into memory.
|
||||
|
30
UPGRADE_TO_2_2
Normal file
30
UPGRADE_TO_2_2
Normal file
@ -0,0 +1,30 @@
|
||||
# EntityManager#getPartialReference() creates read-only entity
|
||||
|
||||
Entities returned from EntityManager#getPartialReference() are now marked as read-only if they
|
||||
haven't been in the identity map before. This means objects of this kind never lead to changes
|
||||
in the UnitOfWork.
|
||||
|
||||
# Fields omitted in a partial DQL query or a native query are never updated
|
||||
|
||||
Fields of an entity that are not returned from a partial DQL Query or native SQL query
|
||||
will never be updated through an UPDATE statement.
|
||||
|
||||
# Removed support for onUpdate in @JoinColumn
|
||||
|
||||
The onUpdate foreign key handling makes absolutly no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed.
|
||||
|
||||
# Changes in Annotation Handling
|
||||
|
||||
There have been some changes to the annotation handling in Common 2.2 again, that affect how people with old configurations
|
||||
from 2.0 have to configure the annotation driver if they don't use `Configuration::newDefaultAnnotationDriver()`:
|
||||
|
||||
// Register the ORM Annotations in the AnnotationRegistry
|
||||
AnnotationRegistry::registerFile('path/to/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');
|
||||
|
||||
$reader = new \Doctrine\Common\Annotations\SimpleAnnotationReader();
|
||||
$reader->addNamespace('Doctrine\ORM\Mapping');
|
||||
$reader = new \Doctrine\Common\Annotations\CachedReader($reader, new ArrayCache());
|
||||
|
||||
$driver = new AnnotationDriver($reader, (array)$paths);
|
||||
|
||||
$config->setMetadataDriverImpl($driver);
|
@ -8,6 +8,7 @@ report.dir=reports
|
||||
log.archive.dir=logs
|
||||
project.pirum_dir=
|
||||
project.download_dir=
|
||||
project.xsd_dir=
|
||||
test.phpunit_configuration_file=
|
||||
test.phpunit_generate_coverage=0
|
||||
test.pmd_reports=0
|
||||
|
47
build.xml
47
build.xml
@ -100,10 +100,30 @@
|
||||
</copy>
|
||||
<exec command="sed 's/${version}-DEV/${version}/' ${build.dir}/doctrine-orm/Doctrine/ORM/Version.php > ${build.dir}/doctrine-orm/Doctrine/ORM/Version2.php" passthru="true" />
|
||||
<exec command="mv ${build.dir}/doctrine-orm/Doctrine/ORM/Version2.php ${build.dir}/doctrine-orm/Doctrine/ORM/Version.php" passthru="true" />
|
||||
<delete dir="${build.dir}/doctrine-orm/Doctrine/Symfony/Component/Yaml/.git" includeemptydirs="true"/>
|
||||
<delete dir="${build.dir}/doctrine-orm/Doctrine/Symfony/Component/Console/.git" includeemptydirs="true"/>
|
||||
|
||||
</target>
|
||||
|
||||
<target name="build" depends="test, build-orm"/>
|
||||
|
||||
<target name="package-phar" depends="build-orm">
|
||||
<pharpackage basedir="${build.dir}/doctrine-orm/" destfile="${dist.dir}/doctrine-orm-${version}.phar" clistub="${build.dir}/doctrine-orm/bin/doctrine.php" signature="sha512">
|
||||
<fileset dir="${build.dir}/doctrine-orm">
|
||||
<include name="**/**" />
|
||||
</fileset>
|
||||
<metadata>
|
||||
<element name="version" value="${version}" />
|
||||
<element name="authors">
|
||||
<element name="Guilherme Blanco"><element name="e-mail" value="guilhermeblanco@gmail.com" /></element>
|
||||
<element name="Benjamin Eberlei"><element name="e-mail" value="kontakt@beberlei.de" /></element>
|
||||
<element name="Jonathan H. Wage"><element name="e-mail" value="jonwage@gmail.com" /></element>
|
||||
<element name="Roman Borschel"><element name="e-mail" value="roman@code-factory.org" /></element>
|
||||
</element>
|
||||
</metadata>
|
||||
</pharpackage>
|
||||
</target>
|
||||
|
||||
<!--
|
||||
Runs the full test suite.
|
||||
-->
|
||||
@ -166,7 +186,7 @@
|
||||
<dirroles key="bin">script</dirroles>
|
||||
<ignore>Doctrine/Common/</ignore>
|
||||
<ignore>Doctrine/DBAL/</ignore>
|
||||
<ignore>Symfony/Component/Yaml/</ignore>
|
||||
<ignore>Symfony/Component/Yaml/</ignore>
|
||||
<ignore>Symfony/Component/Console/</ignore>
|
||||
<release>
|
||||
<install as="doctrine" name="bin/doctrine" />
|
||||
@ -188,9 +208,12 @@
|
||||
</target>
|
||||
|
||||
<target name="git-tag">
|
||||
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
|
||||
<exec command="git tag -a ${version}" passthru="true" />
|
||||
<exec command="git push origin ${version}" passthru="true" />
|
||||
<exec command="grep '${version}-DEV' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
|
||||
<exec command="sed 's/${version}-DEV/${version}/' ${project.basedir}/lib/Doctrine/ORM/Version.php > ${project.basedir}/lib/Doctrine/ORM/Version2.php" passthru="true" />
|
||||
<exec command="mv ${project.basedir}/lib/Doctrine/ORM/Version2.php ${project.basedir}/lib/Doctrine/ORM/Version.php" passthru="true" />
|
||||
<exec command="git add ${project.basedir}/lib/Doctrine/ORM/Version.php" passthru="true" />
|
||||
<exec command="git commit -m 'Release ${version}'" />
|
||||
<exec command="git tag -m 'Tag ${version}' -a ${version}" passthru="true" />
|
||||
</target>
|
||||
|
||||
<target name="pirum-release">
|
||||
@ -200,19 +223,24 @@
|
||||
|
||||
<target name="distribute-download">
|
||||
<copy file="dist/DoctrineORM-${version}-full.tar.gz" todir="${project.download_dir}" />
|
||||
<!--<copy file="${dist.dir}/doctrine-orm-${version}.phar" todir="${project.download_dir}" />-->
|
||||
</target>
|
||||
|
||||
<target name="distribute-xsd">
|
||||
<php expression="substr('${version}', 0, 3)" returnProperty="minorVersion" /><!-- not too robust -->
|
||||
<copy file="${project.basedir}/doctrine-mapping.xsd" tofile="${project.xsd_dir}/doctrine-mapping-${minorVersion}.xsd" />
|
||||
</target>
|
||||
|
||||
<target name="update-dev-version">
|
||||
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
|
||||
<propertyprompt propertyName="next_version" defaultValue="${version}" promptText="Enter next version string (without -DEV)" />
|
||||
<exec command="sed 's/${version}-DEV/${next_version}-DEV/' ${project.basedir}/lib/Doctrine/ORM/Version.php > ${project.basedir}/lib/Doctrine/ORM/Version2.php" passthru="true" />
|
||||
<exec command="sed 's/${version}/${next_version}-DEV/' ${project.basedir}/lib/Doctrine/ORM/Version.php > ${project.basedir}/lib/Doctrine/ORM/Version2.php" passthru="true" />
|
||||
<exec command="mv ${project.basedir}/lib/Doctrine/ORM/Version2.php ${project.basedir}/lib/Doctrine/ORM/Version.php" passthru="true" />
|
||||
<exec command="git add ${project.basedir}/lib/Doctrine/ORM/Version.php" passthru="true" />
|
||||
<exec command="git commit -m 'Bump Dev Version to ${next_version}-DEV'" passthru="true" />
|
||||
<exec command="git push origin master" passthru="true" />
|
||||
</target>
|
||||
|
||||
<target name="release" depends="git-tag,build-packages,distribute-download,pirum-release,update-dev-version" />
|
||||
<target name="release" depends="git-tag,build-packages,distribute-download,pirum-release,update-dev-version,distribute-xsd" />
|
||||
|
||||
<!--
|
||||
Builds distributable PEAR packages for the Symfony Dependencies
|
||||
@ -232,6 +260,7 @@
|
||||
<php minimum_version="5.3.0" />
|
||||
<pear minimum_version="1.6.0" recommended_version="1.6.1" />
|
||||
</dependencies>
|
||||
<ignore>bin/</ignore>
|
||||
<ignore>Doctrine/Common/</ignore>
|
||||
<ignore>Doctrine/DBAL/</ignore>
|
||||
<ignore>Doctrine/ORM/</ignore>
|
||||
@ -254,6 +283,7 @@
|
||||
<php minimum_version="5.3.0" />
|
||||
<pear minimum_version="1.6.0" recommended_version="1.6.1" />
|
||||
</dependencies>
|
||||
<ignore>bin/</ignore>
|
||||
<ignore>Doctrine/Common/</ignore>
|
||||
<ignore>Doctrine/DBAL/</ignore>
|
||||
<ignore>Doctrine/ORM/</ignore>
|
||||
@ -266,4 +296,5 @@
|
||||
<exec command="sudo pirum add ${project.pirum_dir} ${project.basedir}/dist/DoctrineSymfonyYaml-${version}.tgz" dir="." passthru="true" />
|
||||
<exec command="sudo pirum build ${project.pirum_dir}" passthru="true" />
|
||||
</target>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
|
20
composer.json
Normal file
20
composer.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "doctrine/orm",
|
||||
"type": "library",
|
||||
"description": "Object-Relational-Mapper for PHP",
|
||||
"keywords": ["orm", "database"],
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"license": "LGPL",
|
||||
"authors": [
|
||||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
|
||||
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
|
||||
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
|
||||
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.2",
|
||||
"ext-pdo": "*",
|
||||
"doctrine/common": "master-dev",
|
||||
"doctrine/dbal": "master-dev"
|
||||
}
|
||||
}
|
@ -17,11 +17,18 @@
|
||||
<xs:sequence>
|
||||
<xs:element name="mapped-superclass" type="orm:mapped-superclass" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="entity" type="orm:entity" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:complexType name="emptyType"/>
|
||||
<xs:complexType name="emptyType">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="cascade-type">
|
||||
<xs:sequence>
|
||||
@ -30,7 +37,9 @@
|
||||
<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:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="lifecycle-callback-type">
|
||||
@ -46,24 +55,32 @@
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="lifecycle-callback">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="type" type="orm:lifecycle-callback-type" use="required" />
|
||||
<xs:attribute name="method" type="xs:NMTOKEN" use="required" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="lifecycle-callbacks">
|
||||
<xs:sequence>
|
||||
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="named-query">
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="query" type="xs:string" use="required" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="named-queries">
|
||||
<xs:sequence>
|
||||
<xs:element name="named-query" type="orm:named-query" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
@ -75,12 +92,13 @@
|
||||
<xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/>
|
||||
<xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="1" />
|
||||
<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"/>
|
||||
<xs:element name="one-to-many" type="orm:one-to-many" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="many-to-one" type="orm:many-to-one" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="many-to-many" type="orm:many-to-many" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="table" type="xs:NMTOKEN" />
|
||||
@ -89,11 +107,17 @@
|
||||
<xs:attribute name="inheritance-type" type="orm:inheritance-type"/>
|
||||
<xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" />
|
||||
<xs:attribute name="read-only" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="mapped-superclass" >
|
||||
<xs:complexContent>
|
||||
<xs:extension base="orm:entity"/>
|
||||
<xs:extension base="orm:entity">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
@ -139,6 +163,9 @@
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="field">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="type" type="xs:NMTOKEN" default="string" />
|
||||
<xs:attribute name="column" type="xs:NMTOKEN" />
|
||||
@ -149,76 +176,114 @@
|
||||
<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:complexType name="discriminator-column">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="type" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="field-name" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="length" type="xs:NMTOKEN" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="unique-constraint">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
|
||||
<xs:attribute name="columns" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="unique-constraints">
|
||||
<xs:sequence>
|
||||
<xs:element name="unique-constraint" type="orm:unique-constraint" minOccurs="1" maxOccurs="unbounded"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="index">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
|
||||
<xs:attribute name="columns" type="xs:NMTOKENS" use="required"/>
|
||||
<xs:attribute name="columns" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="indexes">
|
||||
<xs:sequence>
|
||||
<xs:element name="index" type="orm:index" minOccurs="1" maxOccurs="unbounded"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="discriminator-mapping">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="value" type="xs:NMTOKEN" use="required"/>
|
||||
<xs:attribute name="class" type="xs:NMTOKEN" use="required"/>
|
||||
<xs:attribute name="class" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="discriminator-map">
|
||||
<xs:sequence>
|
||||
<xs:element name="discriminator-mapping" type="orm:discriminator-mapping" minOccurs="1" maxOccurs="unbounded"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="generator">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="strategy" type="orm:generator-strategy" use="optional" default="AUTO" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="id">
|
||||
<xs:sequence>
|
||||
<xs:element name="generator" type="orm:generator" minOccurs="0" />
|
||||
<xs:element name="sequence-generator" type="orm:sequence-generator" minOccurs="0" maxOccurs="1" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<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="association-key" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="sequence-generator">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="sequence-name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="allocation-size" type="xs:integer" use="optional" default="1" />
|
||||
<xs:attribute name="initial-value" type="xs:integer" use="optional" default="1" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="inverse-join-columns">
|
||||
<xs:sequence>
|
||||
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="join-column">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="referenced-column-name" type="xs:NMTOKEN" use="optional" default="id" />
|
||||
<xs:attribute name="unique" type="xs:boolean" default="false" />
|
||||
@ -226,32 +291,43 @@
|
||||
<xs:attribute name="on-delete" type="orm:fk-action" />
|
||||
<xs:attribute name="on-update" type="orm:fk-action" />
|
||||
<xs:attribute name="column-definition" type="xs:string" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="join-columns">
|
||||
<xs:sequence>
|
||||
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="join-table">
|
||||
<xs:sequence>
|
||||
<xs:element name="join-columns" type="orm:join-columns" />
|
||||
<xs:element name="inverse-join-columns" type="orm:join-columns" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="schema" type="xs:NMTOKEN" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="order-by">
|
||||
<xs:sequence>
|
||||
<xs:element name="order-by-field" type="orm:order-by-field" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="order-by-field">
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="direction" type="orm:order-by-direction" default="ASC" />
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="direction" type="orm:order-by-direction" default="ASC" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="order-by-direction">
|
||||
@ -266,6 +342,7 @@
|
||||
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
|
||||
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
|
||||
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
@ -273,12 +350,14 @@
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="one-to-many">
|
||||
<xs:sequence>
|
||||
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
|
||||
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" />
|
||||
@ -286,6 +365,7 @@
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="many-to-one">
|
||||
@ -294,13 +374,16 @@
|
||||
<xs:choice minOccurs="0" maxOccurs="1">
|
||||
<xs:element name="join-column" type="orm:join-column"/>
|
||||
<xs:element name="join-columns" type="orm:join-columns"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:choice>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="one-to-one">
|
||||
@ -309,7 +392,9 @@
|
||||
<xs:choice minOccurs="0" maxOccurs="1">
|
||||
<xs:element name="join-column" type="orm:join-column"/>
|
||||
<xs:element name="join-columns" type="orm:join-columns"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:choice>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
@ -317,6 +402,7 @@
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
||||
|
@ -162,6 +162,16 @@ abstract class AbstractQuery
|
||||
{
|
||||
return $this->_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all defined parameter types.
|
||||
*
|
||||
* @return array The defined query parameter types.
|
||||
*/
|
||||
public function getParameterTypes()
|
||||
{
|
||||
return $this->_paramTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a query parameter.
|
||||
@ -174,6 +184,17 @@ abstract class AbstractQuery
|
||||
return isset($this->_params[$key]) ? $this->_params[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a query parameter type.
|
||||
*
|
||||
* @param mixed $key The key (index or name) of the bound parameter.
|
||||
* @return mixed The parameter type of the bound parameter.
|
||||
*/
|
||||
public function getParameterType($key)
|
||||
{
|
||||
return isset($this->_paramTypes[$key]) ? $this->_paramTypes[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SQL query that corresponds to this query object.
|
||||
* The returned SQL syntax depends on the connection driver that is used
|
||||
@ -195,6 +216,8 @@ abstract class AbstractQuery
|
||||
*/
|
||||
public function setParameter($key, $value, $type = null)
|
||||
{
|
||||
$key = trim($key, ':');
|
||||
|
||||
if ($type === null) {
|
||||
$type = Query\ParameterTypeInferer::inferType($value);
|
||||
}
|
||||
|
@ -20,7 +20,11 @@
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Cache\Cache,
|
||||
Doctrine\ORM\Mapping\Driver\Driver;
|
||||
Doctrine\Common\Cache\ArrayCache,
|
||||
Doctrine\Common\Annotations\AnnotationRegistry,
|
||||
Doctrine\Common\Annotations\AnnotationReader,
|
||||
Doctrine\ORM\Mapping\Driver\Driver,
|
||||
Doctrine\ORM\Mapping\Driver\AnnotationDriver;
|
||||
|
||||
/**
|
||||
* Configuration container for all configuration options of Doctrine.
|
||||
@ -81,7 +85,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Gets the namespace where proxy classes reside.
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProxyNamespace()
|
||||
@ -92,7 +96,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Sets the namespace where proxy classes reside.
|
||||
*
|
||||
*
|
||||
* @param string $ns
|
||||
*/
|
||||
public function setProxyNamespace($ns)
|
||||
@ -114,16 +118,35 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Add a new default annotation driver with a correctly configured annotation reader.
|
||||
*
|
||||
*
|
||||
* @param array $paths
|
||||
* @return Mapping\Driver\AnnotationDriver
|
||||
*/
|
||||
public function newDefaultAnnotationDriver($paths = array())
|
||||
{
|
||||
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
|
||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||
|
||||
return new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, (array)$paths);
|
||||
if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
|
||||
// Register the ORM Annotations in the AnnotationRegistry
|
||||
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
|
||||
|
||||
$reader = new \Doctrine\Common\Annotations\SimpleAnnotationReader();
|
||||
$reader->addNamespace('Doctrine\ORM\Mapping');
|
||||
$reader = new \Doctrine\Common\Annotations\CachedReader($reader, new ArrayCache());
|
||||
} else if (version_compare(\Doctrine\Common\Version::VERSION, '2.1.0-DEV', '>=')) {
|
||||
// Register the ORM Annotations in the AnnotationRegistry
|
||||
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
|
||||
|
||||
$reader = new AnnotationReader();
|
||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||
$reader->setIgnoreNotImportedAnnotations(true);
|
||||
$reader->setEnableParsePhpImports(false);
|
||||
$reader = new \Doctrine\Common\Annotations\CachedReader(
|
||||
new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache()
|
||||
);
|
||||
} else {
|
||||
$reader = new AnnotationReader();
|
||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||
}
|
||||
return new AnnotationDriver($reader, (array)$paths);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,7 +163,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Resolves a registered namespace alias to the full namespace.
|
||||
*
|
||||
* @param string $entityNamespaceAlias
|
||||
* @param string $entityNamespaceAlias
|
||||
* @return string
|
||||
* @throws MappingException
|
||||
*/
|
||||
@ -163,10 +186,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
{
|
||||
$this->_attributes['entityNamespaces'] = $entityNamespaces;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the list of registered entity namespace aliases.
|
||||
*
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getEntityNamespaces()
|
||||
@ -338,7 +361,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Gets the implementation class name of a registered custom string DQL function.
|
||||
*
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
@ -381,7 +404,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Gets the implementation class name of a registered custom numeric DQL function.
|
||||
*
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
@ -424,7 +447,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Gets the implementation class name of a registered custom date/time DQL function.
|
||||
*
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
@ -475,7 +498,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Set a class metadata factory.
|
||||
*
|
||||
*
|
||||
* @param string $cmf
|
||||
*/
|
||||
public function setClassMetadataFactoryName($cmfName)
|
||||
@ -493,4 +516,32 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
return $this->_attributes['classMetadataFactoryName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default repository class.
|
||||
*
|
||||
* @since 2.2
|
||||
* @param string $className
|
||||
* @throws ORMException If not is a Doctrine\ORM\EntityRepository
|
||||
*/
|
||||
public function setDefaultRepositoryClassName($className)
|
||||
{
|
||||
if ($className != "Doctrine\ORM\EntityRepository" &&
|
||||
!is_subclass_of($className, 'Doctrine\ORM\EntityRepository')){
|
||||
throw ORMException::invalidEntityRepository($className);
|
||||
}
|
||||
$this->_attributes['defaultRepositoryClassName'] = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default repository class.
|
||||
*
|
||||
* @since 2.2
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultRepositoryClassName()
|
||||
{
|
||||
return isset($this->_attributes['defaultRepositoryClassName']) ?
|
||||
$this->_attributes['defaultRepositoryClassName'] : 'Doctrine\ORM\EntityRepository';
|
||||
}
|
||||
}
|
@ -413,6 +413,7 @@ class EntityManager implements ObjectManager
|
||||
$entity = $class->newInstance();
|
||||
$class->setIdentifierValues($entity, $identifier);
|
||||
$this->unitOfWork->registerManaged($entity, $identifier, array());
|
||||
$this->unitOfWork->markReadOnly($entity);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
@ -576,7 +577,8 @@ class EntityManager implements ObjectManager
|
||||
if ($customRepositoryClassName !== null) {
|
||||
$repository = new $customRepositoryClassName($this, $metadata);
|
||||
} else {
|
||||
$repository = new EntityRepository($this, $metadata);
|
||||
$repositoryClass = $this->config->getDefaultRepositoryClassName();
|
||||
$repository = new $repositoryClass($this, $metadata);
|
||||
}
|
||||
|
||||
$this->repositories[$entityName] = $repository;
|
||||
|
110
lib/Doctrine/ORM/Event/EntityEventDelegator.php
Normal file
110
lib/Doctrine/ORM/Event/EntityEventDelegator.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
use \Doctrine\Common\EventSubscriber;
|
||||
use \LogicException;
|
||||
|
||||
/**
|
||||
* Delegate events only for certain entities they are registered for.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @since 2.2
|
||||
*/
|
||||
class EntityEventDelegator implements EventSubscriber
|
||||
{
|
||||
/**
|
||||
* Keeps track of all the event listeners.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $listeners = array();
|
||||
|
||||
/**
|
||||
* If frozen no new event listeners can be added.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $frozen = false;
|
||||
|
||||
/**
|
||||
* Adds an event listener that listens on the specified events.
|
||||
*
|
||||
* @param string|array $events The event(s) to listen on.
|
||||
* @param string|array $entities The entities to trigger this listener for
|
||||
* @param object $listener The listener object.
|
||||
*/
|
||||
public function addEventListener($events, $entities, $listener)
|
||||
{
|
||||
if ($this->frozen) {
|
||||
throw new LogicException("Cannot add event listeners after EntityEventDelegator::getSubscribedEvents() " .
|
||||
"is called once. This happens when you register the delegator with the event manager.");
|
||||
}
|
||||
|
||||
// Picks the hash code related to that listener
|
||||
$hash = spl_object_hash($listener);
|
||||
|
||||
foreach ((array) $events as $event) {
|
||||
// Overrides listener if a previous one was associated already
|
||||
// Prevents duplicate listeners on same event (same instance only)
|
||||
$this->listeners[$event][$hash] = array('listener' => $listener, 'entities' => array_flip((array)$entities));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an EventSubscriber. The subscriber is asked for all the events he is
|
||||
* interested in and added as a listener for these events.
|
||||
*
|
||||
* @param Doctrine\Common\EventSubscriber $subscriber The subscriber.
|
||||
*/
|
||||
public function addEventSubscriber(EventSubscriber $subscriber, $entities)
|
||||
{
|
||||
$this->addEventListener($subscriber->getSubscribedEvents(), $entities, $subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber wants to listen to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSubscribedEvents()
|
||||
{
|
||||
$this->frozen = true;
|
||||
return array_keys($this->listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate the event to an appropriate listener
|
||||
*
|
||||
* @param $eventName
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
public function __call($eventName, $args)
|
||||
{
|
||||
$event = $args[0];
|
||||
foreach ($this->listeners[$eventName] AS $listenerData) {
|
||||
$class = get_class($event->getEntity());
|
||||
if (isset($listenerData['entities'][$class])) {
|
||||
$listenerData['listener']->$eventName($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -90,7 +90,7 @@ class PreUpdateEventArgs extends LifecycleEventArgs
|
||||
if (!isset($this->_entityChangeSet[$field])) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Field '".$field."' is not a valid field of the entity ".
|
||||
"'".get_class($this->getEntity())."' in PreInsertUpdateEventArgs."
|
||||
"'".get_class($this->getEntity())."' in PreUpdateEventArgs."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -47,9 +47,13 @@ class AssignedGenerator extends AbstractIdGenerator
|
||||
if ($class->isIdentifierComposite) {
|
||||
$idFields = $class->getIdentifierFieldNames();
|
||||
foreach ($idFields as $idField) {
|
||||
$value = $class->getReflectionProperty($idField)->getValue($entity);
|
||||
$value = $class->reflFields[$idField]->getValue($entity);
|
||||
if (isset($value)) {
|
||||
if (is_object($value)) {
|
||||
if (isset($class->associationMappings[$idField])) {
|
||||
if (!$em->getUnitOfWork()->isInIdentityMap($value)) {
|
||||
throw ORMException::entityMissingForeignAssignedId($entity, $value);
|
||||
}
|
||||
|
||||
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
|
||||
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
|
||||
} else {
|
||||
@ -63,7 +67,11 @@ class AssignedGenerator extends AbstractIdGenerator
|
||||
$idField = $class->identifier[0];
|
||||
$value = $class->reflFields[$idField]->getValue($entity);
|
||||
if (isset($value)) {
|
||||
if (is_object($value)) {
|
||||
if (isset($class->associationMappings[$idField])) {
|
||||
if (!$em->getUnitOfWork()->isInIdentityMap($value)) {
|
||||
throw ORMException::entityMissingForeignAssignedId($entity, $value);
|
||||
}
|
||||
|
||||
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
|
||||
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
|
||||
} else {
|
||||
|
@ -287,4 +287,25 @@ abstract class AbstractHydrator
|
||||
|
||||
return $rowData;
|
||||
}
|
||||
|
||||
protected function registerManaged($class, $entity, $data)
|
||||
{
|
||||
if ($class->isIdentifierComposite) {
|
||||
$id = array();
|
||||
foreach ($class->identifier as $fieldName) {
|
||||
if (isset($class->associationMappings[$fieldName])) {
|
||||
$id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']];
|
||||
} else {
|
||||
$id[$fieldName] = $data[$fieldName];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isset($class->associationMappings[$class->identifier[0]])) {
|
||||
$id = array($class->identifier[0] => $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]);
|
||||
} else {
|
||||
$id = array($class->identifier[0] => $data[$class->identifier[0]]);
|
||||
}
|
||||
}
|
||||
$this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);
|
||||
}
|
||||
}
|
||||
|
@ -205,18 +205,32 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]];
|
||||
unset($data[$discrColumn]);
|
||||
}
|
||||
|
||||
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) {
|
||||
$class = $this->_ce[$className];
|
||||
$this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
|
||||
}
|
||||
|
||||
return $this->_uow->createEntity($className, $data, $this->_hints);
|
||||
}
|
||||
|
||||
private function _getEntityFromIdentityMap($className, array $data)
|
||||
{
|
||||
// TODO: Abstract this code and UnitOfWork::createEntity() equivalent?
|
||||
$class = $this->_ce[$className];
|
||||
/* @var $class ClassMetadata */
|
||||
if ($class->isIdentifierComposite) {
|
||||
$idHash = '';
|
||||
foreach ($class->identifier as $fieldName) {
|
||||
$idHash .= $data[$fieldName] . ' ';
|
||||
if (isset($class->associationMappings[$fieldName])) {
|
||||
$idHash .= $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] . ' ';
|
||||
} else {
|
||||
$idHash .= $data[$fieldName] . ' ';
|
||||
}
|
||||
}
|
||||
return $this->_uow->tryGetByIdHash(rtrim($idHash), $class->rootEntityName);
|
||||
} else if (isset($class->associationMappings[$class->identifier[0]])) {
|
||||
return $this->_uow->tryGetByIdHash($data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']], $class->rootEntityName);
|
||||
} else {
|
||||
return $this->_uow->tryGetByIdHash($data[$class->identifier[0]], $class->rootEntityName);
|
||||
}
|
||||
|
@ -23,11 +23,10 @@ namespace Doctrine\ORM\Internal\Hydration;
|
||||
use \PDO;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Query;
|
||||
|
||||
class SimpleObjectHydrator extends AbstractHydrator
|
||||
{
|
||||
const REFRESH_ENTITY = 'doctrine_refresh_entity';
|
||||
|
||||
/**
|
||||
* @var ClassMetadata
|
||||
*/
|
||||
@ -123,17 +122,8 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->_hints[self::REFRESH_ENTITY])) {
|
||||
$this->_hints[Query::HINT_REFRESH] = true;
|
||||
$id = array();
|
||||
if ($this->_class->isIdentifierComposite) {
|
||||
foreach ($this->_class->identifier as $fieldName) {
|
||||
$id[$fieldName] = $data[$fieldName];
|
||||
}
|
||||
} else {
|
||||
$id = array($this->_class->identifier[0] => $data[$this->_class->identifier[0]]);
|
||||
}
|
||||
$this->_em->getUnitOfWork()->registerManaged($this->_hints[self::REFRESH_ENTITY], $id, $data);
|
||||
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) {
|
||||
$this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
|
||||
}
|
||||
|
||||
$result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints);
|
||||
|
167
lib/Doctrine/ORM/Mapping/Builder/AssociationBuilder.php
Normal file
167
lib/Doctrine/ORM/Mapping/Builder/AssociationBuilder.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
class AssociationBuilder
|
||||
{
|
||||
/**
|
||||
* @var ClassMetadataBuilder
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $mapping;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $joinColumns;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @param ClassMetadataBuilder $builder
|
||||
* @param array $mapping
|
||||
*/
|
||||
public function __construct(ClassMetadataBuilder $builder, array $mapping, $type)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
$this->mapping = $mapping;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function mappedBy($fieldName)
|
||||
{
|
||||
$this->mapping['mappedBy'] = $fieldName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function inversedBy($fieldName)
|
||||
{
|
||||
$this->mapping['inversedBy'] = $fieldName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cascadeAll()
|
||||
{
|
||||
$this->mapping['cascade'] = array("ALL");
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cascadePersist()
|
||||
{
|
||||
$this->mapping['cascade'][] = "persist";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cascadeRemove()
|
||||
{
|
||||
$this->mapping['cascade'][] = "remove";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cascadeMerge()
|
||||
{
|
||||
$this->mapping['cascade'][] = "merge";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cascadeDetach()
|
||||
{
|
||||
$this->mapping['cascade'][] = "detach";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cascadeRefresh()
|
||||
{
|
||||
$this->mapping['cascade'][] = "refresh";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchExtraLazy()
|
||||
{
|
||||
$this->mapping['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchEager()
|
||||
{
|
||||
$this->mapping['fetch'] = ClassMetadata::FETCH_EAGER;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchLazy()
|
||||
{
|
||||
$this->mapping['fetch'] = ClassMetadata::FETCH_LAZY;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Join Columns
|
||||
*
|
||||
* @param string $columnName
|
||||
* @param string $referencedColumnName
|
||||
* @param bool $nullable
|
||||
* @param bool $unique
|
||||
* @param string $onDelete
|
||||
* @param string $columnDef
|
||||
*/
|
||||
public function addJoinColumn($columnName, $referencedColumnName, $nullable = true, $unique = false, $onDelete = null, $columnDef = null)
|
||||
{
|
||||
$this->joinColumns[] = array(
|
||||
'name' => $columnName,
|
||||
'referencedColumnName' => $referencedColumnName,
|
||||
'nullable' => $nullable,
|
||||
'unique' => $unique,
|
||||
'onDelete' => $onDelete,
|
||||
'columnDefinition' => $columnDef,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$mapping = $this->mapping;
|
||||
if ($this->joinColumns) {
|
||||
$mapping['joinColumns'] = $this->joinColumns;
|
||||
}
|
||||
$cm = $this->builder->getClassMetadata();
|
||||
if ($this->type == ClassMetadata::MANY_TO_ONE) {
|
||||
$cm->mapManyToOne($mapping);
|
||||
} else if ($this->type == ClassMetadata::ONE_TO_ONE) {
|
||||
$cm->mapOneToOne($mapping);
|
||||
} else {
|
||||
throw new \InvalidArgumentException("Type should be a ToOne Assocation here");
|
||||
}
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
407
lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php
Normal file
407
lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php
Normal file
@ -0,0 +1,407 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Builder Object for ClassMetadata
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.com
|
||||
* @since 2.2
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class ClassMetadataBuilder
|
||||
{
|
||||
/**
|
||||
* @var ClassMetadata
|
||||
*/
|
||||
private $cm;
|
||||
|
||||
/**
|
||||
* @param ClassMetadata $cm
|
||||
*/
|
||||
public function __construct(ClassMetadata $cm)
|
||||
{
|
||||
$this->cm = $cm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMetadata
|
||||
*/
|
||||
public function getClassMetadata()
|
||||
{
|
||||
return $this->cm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the class as mapped superclass.
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function setMappedSuperClass()
|
||||
{
|
||||
$this->cm->isMappedSuperclass = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom Repository class name
|
||||
*
|
||||
* @param string $repositoryClassName
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function setCustomRepositoryClass($repositoryClassName)
|
||||
{
|
||||
$this->cm->setCustomRepositoryClass($repositoryClassName);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark class read only
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function setReadOnly()
|
||||
{
|
||||
$this->cm->markReadOnly();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table name
|
||||
*
|
||||
* @param string $name
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function setTable($name)
|
||||
{
|
||||
$this->cm->setPrimaryTable(array('name' => $name));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Index
|
||||
*
|
||||
* @param array $columns
|
||||
* @param string $name
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addIndex(array $columns, $name)
|
||||
{
|
||||
if (!isset($this->cm->table['indexes'])) {
|
||||
$this->cm->table['indexes'] = array();
|
||||
}
|
||||
$this->cm->table['indexes'][$name] = array('columns' => $columns);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Unique Constraint
|
||||
*
|
||||
* @param array $columns
|
||||
* @param string $name
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addUniqueConstraint(array $columns, $name)
|
||||
{
|
||||
if (!isset($this->cm->table['uniqueConstraints'])) {
|
||||
$this->cm->table['uniqueConstraints'] = array();
|
||||
}
|
||||
$this->cm->table['uniqueConstraints'][$name] = array('columns' => $columns);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add named query
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $dqlQuery
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addNamedQuery($name, $dqlQuery)
|
||||
{
|
||||
$this->cm->addNamedQuery(array(
|
||||
'name' => $name,
|
||||
'query' => $dqlQuery,
|
||||
));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set class as root of a joined table inheritance hierachy.
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function setJoinedTableInheritance()
|
||||
{
|
||||
$this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_JOINED);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set class as root of a single table inheritance hierachy.
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function setSingleTableInheritance()
|
||||
{
|
||||
$this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the discriminator column details.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
*/
|
||||
public function setDiscriminatorColumn($name, $type = 'string', $length = 255)
|
||||
{
|
||||
$this->cm->setDiscriminatorColumn(array(
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'length' => $length,
|
||||
));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a subclass to this inheritance hierachy.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $class
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addDiscriminatorMapClass($name, $class)
|
||||
{
|
||||
$this->cm->addDiscriminatorMapClass($name, $class);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set deferred explicit change tracking policy.
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function setChangeTrackingPolicyDeferredExplicit()
|
||||
{
|
||||
$this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set notify change tracking policy.
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function setChangeTrackingPolicyNotify()
|
||||
{
|
||||
$this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_NOTIFY);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add lifecycle event
|
||||
*
|
||||
* @param string $methodName
|
||||
* @param string $event
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addLifecycleEvent($methodName, $event)
|
||||
{
|
||||
$this->cm->addLifecycleCallback($methodName, $event);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Field
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param array $mapping
|
||||
*/
|
||||
public function addField($name, $type, array $mapping = array())
|
||||
{
|
||||
$mapping['fieldName'] = $name;
|
||||
$mapping['type'] = $type;
|
||||
$this->cm->mapField($mapping);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a field builder.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function createField($name, $type)
|
||||
{
|
||||
return new FieldBuilder($this, array('fieldName' => $name, 'type' => $type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a simple many to one association, optionally with the inversed by field.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string|null $inversedBy
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addManyToOne($name, $targetEntity, $inversedBy = null)
|
||||
{
|
||||
$builder = $this->createManyToOne($name, $targetEntity);
|
||||
if ($inversedBy) {
|
||||
$builder->setInversedBy($inversedBy);
|
||||
}
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ManyToOne Assocation Builder.
|
||||
*
|
||||
* Note: This method does not add the association, you have to call build() on the AssociationBuilder.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @return AssociationBuilder
|
||||
*/
|
||||
public function createManyToOne($name, $targetEntity)
|
||||
{
|
||||
return new AssociationBuilder($this, array('fieldName' => $name, 'targetEntity' => $targetEntity), ClassMetadata::MANY_TO_ONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create OneToOne Assocation Builder
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @return AssociationBuilder
|
||||
*/
|
||||
public function createOneToOne($name, $targetEntity)
|
||||
{
|
||||
return new AssociationBuilder($this, array('fieldName' => $name, 'targetEntity' => $targetEntity), ClassMetadata::ONE_TO_ONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add simple inverse one-to-one assocation.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string $mappedBy
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addInverseOneToOne($name, $targetEntity, $mappedBy)
|
||||
{
|
||||
$builder = $this->createOneToOne($name, $targetEntity);
|
||||
$builder->setMappedBy($mappedBy);
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add simple owning one-to-one assocation.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string $inversedBy
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addOwningOneToOne($name, $targetEntity, $inversedBy = null)
|
||||
{
|
||||
$builder = $this->createOneToOne($name, $targetEntity);
|
||||
if ($inversedBy) {
|
||||
$builder->setInversedBy($inversedBy);
|
||||
}
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create ManyToMany Assocation Builder
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @return ManyToManyAssociationBuilder
|
||||
*/
|
||||
public function createManyToMany($name, $targetEntity)
|
||||
{
|
||||
return new ManyToManyAssociationBuilder($this, array('fieldName' => $name, 'targetEntity' => $targetEntity), ClassMetadata::MANY_TO_MANY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a simple owning many to many assocation.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string|null $inversedBy
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addOwningManyToMany($name, $targetEntity, $inversedBy = null)
|
||||
{
|
||||
$builder = $this->createManyToMany($name, $targetEntity);
|
||||
if ($inversedBy) {
|
||||
$builder->setInversedBy($inversedBy);
|
||||
}
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a simple inverse many to many assocation.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string $mappedBy
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addInverseManyToMany($name, $targetEntity, $mappedBy)
|
||||
{
|
||||
$builder = $this->createManyToMany($name, $targetEntity);
|
||||
$builder->setMappedBy($mappedBy);
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a one to many assocation builder
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @return OneToManyAssociationBuilder
|
||||
*/
|
||||
public function createOneToMany($name, $targetEntity)
|
||||
{
|
||||
return new OneToManyAssociationBuilder($this, array('fieldName' => $name, 'targetEntity' => $targetEntity), ClassMetadata::ONE_TO_MANY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add simple OneToMany assocation.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string $mappedBy
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addOneToMany($name, $targetEntity, $mappedBy)
|
||||
{
|
||||
$builder = $this->createOneToMany($name, $targetEntity);
|
||||
$builder->setMappedBy($mappedBy);
|
||||
return $builder->build();
|
||||
}
|
||||
}
|
223
lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php
Normal file
223
lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php
Normal file
@ -0,0 +1,223 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
/**
|
||||
* Field Builder
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.com
|
||||
* @since 2.2
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class FieldBuilder
|
||||
{
|
||||
/**
|
||||
* @var ClassMetadataBuilder
|
||||
*/
|
||||
private $builder;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $mapping;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $generatedValue;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $sequenceDef;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ClassMetadataBuilder $builder
|
||||
* @param array $mapping
|
||||
*/
|
||||
public function __construct(ClassMetadataBuilder $builder, array $mapping)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
$this->mapping = $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set length.
|
||||
*
|
||||
* @param int $length
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function length($length)
|
||||
{
|
||||
$this->mapping['length'] = $length;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set nullable
|
||||
*
|
||||
* @param bool
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function nullable($flag = true)
|
||||
{
|
||||
$this->mapping['nullable'] = (bool)$flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Unique
|
||||
*
|
||||
* @param bool
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function unique($flag = true)
|
||||
{
|
||||
$this->mapping['unique'] = (bool)$flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set column name
|
||||
*
|
||||
* @param string $name
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function columnName($name)
|
||||
{
|
||||
$this->mapping['columnName'] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Precision
|
||||
*
|
||||
* @param int $p
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function precision($p)
|
||||
{
|
||||
$this->mapping['precision'] = $p;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set scale.
|
||||
*
|
||||
* @param int $s
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function scale($s)
|
||||
{
|
||||
$this->mapping['scale'] = $s;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set field as primary key.
|
||||
*
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function isPrimaryKey()
|
||||
{
|
||||
$this->mapping['id'] = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $strategy
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function generatedValue($strategy = 'AUTO')
|
||||
{
|
||||
$this->generatedValue = $strategy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set field versioned
|
||||
*
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function isVersionField()
|
||||
{
|
||||
$this->version = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Sequence Generator
|
||||
*
|
||||
* @param string $sequenceName
|
||||
* @param int $allocationSize
|
||||
* @param int $initialValue
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function setSequenceGenerator($sequenceName, $allocationSize = 1, $initialValue = 1)
|
||||
{
|
||||
$this->sequenceDef = array(
|
||||
'sequenceName' => $sequenceName,
|
||||
'allocationSize' => $allocationSize,
|
||||
'initialValue' => $initialValue,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set column definition.
|
||||
*
|
||||
* @param string $def
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function columnDefinition($def)
|
||||
{
|
||||
$this->mapping['columnDefinition'] = $def;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize this field and attach it to the ClassMetadata.
|
||||
*
|
||||
* Without this call a FieldBuilder has no effect on the ClassMetadata.
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$cm = $this->builder->getClassMetadata();
|
||||
if ($this->generatedValue) {
|
||||
$cm->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $this->generatedValue));
|
||||
}
|
||||
if ($this->version) {
|
||||
$cm->setVersionMapping($this->mapping);
|
||||
}
|
||||
$cm->mapField($this->mapping);
|
||||
if ($this->sequenceDef) {
|
||||
$cm->setSequenceGeneratorDefinition($this->sequenceDef);
|
||||
}
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
/**
|
||||
* ManyToMany Association Builder
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.com
|
||||
* @since 2.0
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
|
||||
{
|
||||
private $joinTableName;
|
||||
|
||||
private $inverseJoinColumns = array();
|
||||
|
||||
public function setJoinTable($name)
|
||||
{
|
||||
$this->joinTableName = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Inverse Join Columns
|
||||
*
|
||||
* @param string $columnName
|
||||
* @param string $referencedColumnName
|
||||
* @param bool $nullable
|
||||
* @param bool $unique
|
||||
* @param string $onDelete
|
||||
* @param string $columnDef
|
||||
*/
|
||||
public function addInverseJoinColumn($columnName, $referencedColumnName, $nullable = true, $unique = false, $onDelete = null, $columnDef = null)
|
||||
{
|
||||
$this->inverseJoinColumns[] = array(
|
||||
'name' => $columnName,
|
||||
'referencedColumnName' => $referencedColumnName,
|
||||
'nullable' => $nullable,
|
||||
'unique' => $unique,
|
||||
'onDelete' => $onDelete,
|
||||
'columnDefinition' => $columnDef,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$mapping = $this->mapping;
|
||||
$mapping['joinTable'] = array();
|
||||
if ($this->joinColumns) {
|
||||
$mapping['joinTable']['joinColumns'] = $this->joinColumns;
|
||||
}
|
||||
if ($this->inverseJoinColumns) {
|
||||
$mapping['joinTable']['inverseJoinColumns'] = $this->inverseJoinColumns;
|
||||
}
|
||||
if ($this->joinTableName) {
|
||||
$mapping['joinTable']['name'] = $this->joinTableName;
|
||||
}
|
||||
$cm = $this->builder->getClassMetadata();
|
||||
$cm->mapManyToMany($mapping);
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
/**
|
||||
* OneToMany Association Builder
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.com
|
||||
* @since 2.0
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class OneToManyAssociationBuilder extends AssociationBuilder
|
||||
{
|
||||
/**
|
||||
* @param array $fieldNames
|
||||
* @return OneToManyAssociationBuilder
|
||||
*/
|
||||
public function setOrderBy(array $fieldNames)
|
||||
{
|
||||
$this->mapping['orderBy'] = $fieldNames;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setIndexBy($fieldName)
|
||||
{
|
||||
$this->mapping['indexBy'] = $fieldName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$mapping = $this->mapping;
|
||||
if ($this->joinColumns) {
|
||||
$mapping['joinColumns'] = $this->joinColumns;
|
||||
}
|
||||
$cm = $this->builder->getClassMetadata();
|
||||
$cm->mapOneToMany($mapping);
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
@ -205,48 +205,6 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
$this->reflFields[$sourceFieldName] = $refProp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) column name of a mapped field for safe use
|
||||
* in an SQL statement.
|
||||
*
|
||||
* @param string $field
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedColumnName($field, $platform)
|
||||
{
|
||||
return isset($this->fieldMappings[$field]['quoted']) ?
|
||||
$platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) :
|
||||
$this->fieldMappings[$field]['columnName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) primary table name of this class for safe use
|
||||
* in an SQL statement.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedTableName($platform)
|
||||
{
|
||||
return isset($this->table['quoted']) ?
|
||||
$platform->quoteIdentifier($this->table['name']) :
|
||||
$this->table['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) name of the join table.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedJoinTableName(array $assoc, $platform)
|
||||
{
|
||||
return isset($assoc['joinTable']['quoted'])
|
||||
? $platform->quoteIdentifier($assoc['joinTable']['name'])
|
||||
: $assoc['joinTable']['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string representation of this instance.
|
||||
*
|
||||
@ -385,4 +343,17 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
}
|
||||
return clone $this->_prototype;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $callback
|
||||
* @param string $event
|
||||
*/
|
||||
public function addLifecycleCallback($callback, $event)
|
||||
{
|
||||
if ( !$this->reflClass->hasMethod($callback) ||
|
||||
($this->reflClass->getMethod($callback)->getModifiers() & \ReflectionMethod::IS_PUBLIC) == 0) {
|
||||
throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callback);
|
||||
}
|
||||
return parent::addLifecycleCallback($callback, $event);
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
private $targetPlatform;
|
||||
|
||||
/**
|
||||
* @var Driver\Driver
|
||||
* @var \Doctrine\ORM\Mapping\Driver\Driver
|
||||
*/
|
||||
private $driver;
|
||||
|
||||
@ -248,11 +248,13 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
|
||||
// Move down the hierarchy of parent classes, starting from the topmost class
|
||||
$parent = null;
|
||||
$rootEntityFound = false;
|
||||
$visited = array();
|
||||
foreach ($parentClasses as $className) {
|
||||
if (isset($this->loadedMetadata[$className])) {
|
||||
$parent = $this->loadedMetadata[$className];
|
||||
if ( ! $parent->isMappedSuperclass) {
|
||||
$rootEntityFound = true;
|
||||
array_unshift($visited, $className);
|
||||
}
|
||||
continue;
|
||||
@ -261,21 +263,20 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
$class = $this->newClassMetadataInstance($className);
|
||||
|
||||
if ($parent) {
|
||||
if (!$parent->isMappedSuperclass) {
|
||||
$class->setInheritanceType($parent->inheritanceType);
|
||||
$class->setDiscriminatorColumn($parent->discriminatorColumn);
|
||||
}
|
||||
$class->setInheritanceType($parent->inheritanceType);
|
||||
$class->setDiscriminatorColumn($parent->discriminatorColumn);
|
||||
$class->setIdGeneratorType($parent->generatorType);
|
||||
$this->addInheritedFields($class, $parent);
|
||||
$this->addInheritedRelations($class, $parent);
|
||||
$class->setIdentifier($parent->identifier);
|
||||
$class->setVersioned($parent->isVersioned);
|
||||
$class->setVersionField($parent->versionField);
|
||||
if (!$parent->isMappedSuperclass) {
|
||||
$class->setDiscriminatorMap($parent->discriminatorMap);
|
||||
}
|
||||
$class->setDiscriminatorMap($parent->discriminatorMap);
|
||||
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
|
||||
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
|
||||
if ($parent->isMappedSuperclass) {
|
||||
$class->setCustomRepositoryClass($parent->customRepositoryClassName);
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke driver
|
||||
@ -285,7 +286,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
throw MappingException::reflectionFailure($className, $e);
|
||||
}
|
||||
|
||||
if ($parent && ! $parent->isMappedSuperclass) {
|
||||
// If this class has a parent the id generator strategy is inherited.
|
||||
// However this is only true if the hierachy of parents contains the root entity,
|
||||
// if it consinsts of mapped superclasses these don't necessarily include the id field.
|
||||
if ($parent && $rootEntityFound) {
|
||||
if ($parent->isIdGeneratorSequence()) {
|
||||
$class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
|
||||
} else if ($parent->isIdGeneratorTable()) {
|
||||
@ -312,28 +316,14 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
|
||||
}
|
||||
|
||||
// Verify & complete identifier mapping
|
||||
if ( ! $class->identifier && ! $class->isMappedSuperclass) {
|
||||
throw MappingException::identifierRequired($className);
|
||||
}
|
||||
|
||||
// verify inheritance
|
||||
if (!$parent && !$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
|
||||
if (count($class->discriminatorMap) == 0) {
|
||||
throw MappingException::missingDiscriminatorMap($class->name);
|
||||
}
|
||||
if (!$class->discriminatorColumn) {
|
||||
throw MappingException::missingDiscriminatorColumn($class->name);
|
||||
}
|
||||
} else if ($class->isMappedSuperclass && (count($class->discriminatorMap) || $class->discriminatorColumn)) {
|
||||
throw MappingException::noInheritanceOnMappedSuperClass($class->name);
|
||||
}
|
||||
|
||||
$this->validateRuntimeMetadata($class, $parent);
|
||||
|
||||
$this->loadedMetadata[$className] = $class;
|
||||
|
||||
$parent = $class;
|
||||
|
||||
if ( ! $class->isMappedSuperclass) {
|
||||
$rootEntityFound = true;
|
||||
array_unshift($visited, $className);
|
||||
}
|
||||
|
||||
@ -343,6 +333,38 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
return $loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate runtime metadata is correctly defined.
|
||||
*
|
||||
* @param ClassMetadata $class
|
||||
* @param ClassMetadata $parent
|
||||
*/
|
||||
protected function validateRuntimeMetadata($class, $parent)
|
||||
{
|
||||
// Verify & complete identifier mapping
|
||||
if ( ! $class->identifier && ! $class->isMappedSuperclass) {
|
||||
throw MappingException::identifierRequired($class->name);
|
||||
}
|
||||
|
||||
// verify inheritance
|
||||
if (!$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
|
||||
if (!$parent) {
|
||||
if (count($class->discriminatorMap) == 0) {
|
||||
throw MappingException::missingDiscriminatorMap($class->name);
|
||||
}
|
||||
if (!$class->discriminatorColumn) {
|
||||
throw MappingException::missingDiscriminatorColumn($class->name);
|
||||
}
|
||||
} else if ($parent && !$class->reflClass->isAbstract() && !in_array($class->name, array_values($class->discriminatorMap))) {
|
||||
// enforce discriminator map for all entities of an inheritance hierachy, otherwise problems will occur.
|
||||
throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName);
|
||||
}
|
||||
} else if ($class->isMappedSuperclass && $class->name == $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) {
|
||||
// second condition is necessary for mapped superclasses in the middle of an inheritance hierachy
|
||||
throw MappingException::noInheritanceOnMappedSuperClass($class->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ClassMetadata instance for the given class name.
|
||||
*
|
||||
@ -429,7 +451,7 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
// <table>_<column>_seq in PostgreSQL for SERIAL columns.
|
||||
// Not pretty but necessary and the simplest solution that currently works.
|
||||
$seqName = $this->targetPlatform instanceof Platforms\PostgreSQLPlatform ?
|
||||
$class->table['name'] . '_' . $class->columnNames[$class->identifier[0]] . '_seq' :
|
||||
$class->getTableName() . '_' . $class->columnNames[$class->identifier[0]] . '_seq' :
|
||||
null;
|
||||
$class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($seqName));
|
||||
break;
|
||||
@ -459,4 +481,15 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
throw new ORMException("Unknown generator type: " . $class->generatorType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this class is mapped by this EntityManager + ClassMetadata configuration
|
||||
*
|
||||
* @param $class
|
||||
* @return bool
|
||||
*/
|
||||
public function isTransient($class)
|
||||
{
|
||||
return $this->driver->isTransient($class);
|
||||
}
|
||||
}
|
||||
|
@ -335,7 +335,6 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* uniqueConstraints => array
|
||||
*
|
||||
* @var array
|
||||
* @todo Rename to just $table
|
||||
*/
|
||||
public $table;
|
||||
|
||||
@ -775,9 +774,13 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
// If targetEntity is unqualified, assume it is in the same namespace as
|
||||
// the sourceEntity.
|
||||
$mapping['sourceEntity'] = $this->name;
|
||||
if (isset($mapping['targetEntity']) && strpos($mapping['targetEntity'], '\\') === false
|
||||
&& strlen($this->namespace) > 0) {
|
||||
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
|
||||
|
||||
if (isset($mapping['targetEntity'])) {
|
||||
if (strlen($this->namespace) > 0 && strpos($mapping['targetEntity'], '\\') === false) {
|
||||
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
|
||||
}
|
||||
|
||||
$mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
|
||||
}
|
||||
|
||||
// Complete id mapping
|
||||
@ -833,16 +836,12 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
}
|
||||
|
||||
// Cascades
|
||||
$cascades = isset($mapping['cascade']) ? $mapping['cascade'] : array();
|
||||
$cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : array();
|
||||
|
||||
if (in_array('all', $cascades)) {
|
||||
$cascades = array(
|
||||
'remove',
|
||||
'persist',
|
||||
'refresh',
|
||||
'merge',
|
||||
'detach'
|
||||
);
|
||||
$cascades = array('remove', 'persist', 'refresh', 'merge', 'detach');
|
||||
}
|
||||
|
||||
$mapping['cascade'] = $cascades;
|
||||
$mapping['isCascadeRemove'] = in_array('remove', $cascades);
|
||||
$mapping['isCascadePersist'] = in_array('persist', $cascades);
|
||||
@ -909,9 +908,8 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
|
||||
}
|
||||
|
||||
//TODO: if orphanRemoval, cascade=remove is implicit!
|
||||
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ?
|
||||
(bool) $mapping['orphanRemoval'] : false;
|
||||
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
|
||||
$mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
|
||||
|
||||
if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
|
||||
throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']);
|
||||
@ -936,9 +934,8 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
|
||||
}
|
||||
|
||||
//TODO: if orphanRemoval, cascade=remove is implicit!
|
||||
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ?
|
||||
(bool) $mapping['orphanRemoval'] : false;
|
||||
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
|
||||
$mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
|
||||
|
||||
if (isset($mapping['orderBy'])) {
|
||||
if ( ! is_array($mapping['orderBy'])) {
|
||||
@ -1272,7 +1269,8 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*/
|
||||
public function getTemporaryIdTableName()
|
||||
{
|
||||
return $this->table['name'] . '_id_tmp';
|
||||
// replace dots with underscores because PostgreSQL creates temporary tables in a special schema
|
||||
return str_replace('.', '_', $this->getTableName() . '_id_tmp');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1371,9 +1369,11 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$this->table['name'] = $table['name'];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($table['indexes'])) {
|
||||
$this->table['indexes'] = $table['indexes'];
|
||||
}
|
||||
|
||||
if (isset($table['uniqueConstraints'])) {
|
||||
$this->table['uniqueConstraints'] = $table['uniqueConstraints'];
|
||||
}
|
||||
@ -1522,6 +1522,10 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*/
|
||||
public function setCustomRepositoryClass($repositoryClassName)
|
||||
{
|
||||
if ($repositoryClassName !== null && strpos($repositoryClassName, '\\') === false
|
||||
&& strlen($this->namespace) > 0) {
|
||||
$repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
|
||||
}
|
||||
$this->customRepositoryClassName = $repositoryClassName;
|
||||
}
|
||||
|
||||
@ -1564,9 +1568,6 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
/**
|
||||
* Adds a lifecycle callback for entities of this class.
|
||||
*
|
||||
* Note: If the same callback is registered more than once, the old one
|
||||
* will be overridden.
|
||||
*
|
||||
* @param string $callback
|
||||
* @param string $event
|
||||
*/
|
||||
@ -1625,20 +1626,33 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
public function setDiscriminatorMap(array $map)
|
||||
{
|
||||
foreach ($map as $value => $className) {
|
||||
if (strpos($className, '\\') === false && strlen($this->namespace)) {
|
||||
$className = $this->namespace . '\\' . $className;
|
||||
$this->addDiscriminatorMapClass($value, $className);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one entry of the discriminator map with a new class and corresponding name.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $className
|
||||
*/
|
||||
public function addDiscriminatorMapClass($name, $className)
|
||||
{
|
||||
if (strlen($this->namespace) > 0 && strpos($className, '\\') === false) {
|
||||
$className = $this->namespace . '\\' . $className;
|
||||
}
|
||||
|
||||
$className = ltrim($className, '\\');
|
||||
$this->discriminatorMap[$name] = $className;
|
||||
|
||||
if ($this->name == $className) {
|
||||
$this->discriminatorValue = $name;
|
||||
} else {
|
||||
if ( ! class_exists($className)) {
|
||||
throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
|
||||
}
|
||||
$className = ltrim($className, '\\');
|
||||
$this->discriminatorMap[$value] = $className;
|
||||
if ($this->name == $className) {
|
||||
$this->discriminatorValue = $value;
|
||||
} else {
|
||||
if ( ! class_exists($className)) {
|
||||
throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
|
||||
}
|
||||
if (is_subclass_of($className, $this->name)) {
|
||||
$this->subClasses[] = $className;
|
||||
}
|
||||
if (is_subclass_of($className, $this->name)) {
|
||||
$this->subClasses[] = $className;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1800,7 +1814,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$this->versionField = $mapping['fieldName'];
|
||||
|
||||
if ( ! isset($mapping['default'])) {
|
||||
if ($mapping['type'] == 'integer') {
|
||||
if (in_array($mapping['type'], array('integer', 'bigint', 'smallint'))) {
|
||||
$mapping['default'] = 1;
|
||||
} else if ($mapping['type'] == 'datetime') {
|
||||
$mapping['default'] = 'CURRENT_TIMESTAMP';
|
||||
@ -1873,9 +1887,10 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*/
|
||||
public function getAssociationTargetClass($assocName)
|
||||
{
|
||||
if (!isset($this->associationMappings[$assocName])) {
|
||||
if ( ! isset($this->associationMappings[$assocName])) {
|
||||
throw new \InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
|
||||
}
|
||||
|
||||
return $this->associationMappings[$assocName]['targetEntity'];
|
||||
}
|
||||
|
||||
@ -1888,4 +1903,40 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) column name of a mapped field for safe use
|
||||
* in an SQL statement.
|
||||
*
|
||||
* @param string $field
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedColumnName($field, $platform)
|
||||
{
|
||||
return isset($this->fieldMappings[$field]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) : $this->fieldMappings[$field]['columnName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) primary table name of this class for safe use
|
||||
* in an SQL statement.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedTableName($platform)
|
||||
{
|
||||
return isset($this->table['quoted']) ? $platform->quoteIdentifier($this->table['name']) : $this->table['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) name of the join table.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedJoinTableName(array $assoc, $platform)
|
||||
{
|
||||
return isset($assoc['joinTable']['quoted']) ? $platform->quoteIdentifier($assoc['joinTable']['name']) : $assoc['joinTable']['name'];
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,10 @@ namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Common\Cache\ArrayCache,
|
||||
Doctrine\Common\Annotations\AnnotationReader,
|
||||
Doctrine\Common\Annotations\AnnotationRegistry,
|
||||
Doctrine\ORM\Mapping\ClassMetadataInfo,
|
||||
Doctrine\ORM\Mapping\MappingException;
|
||||
|
||||
require __DIR__ . '/DoctrineAnnotations.php';
|
||||
|
||||
/**
|
||||
* The AnnotationDriver reads the mapping metadata from docblock annotations.
|
||||
*
|
||||
@ -42,7 +41,7 @@ class AnnotationDriver implements Driver
|
||||
*
|
||||
* @var AnnotationReader
|
||||
*/
|
||||
private $_reader;
|
||||
protected $_reader;
|
||||
|
||||
/**
|
||||
* The paths where to look for mapping files.
|
||||
@ -62,13 +61,13 @@ class AnnotationDriver implements Driver
|
||||
* @param array
|
||||
*/
|
||||
protected $_classNames;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
|
||||
* docblock annotations.
|
||||
*
|
||||
*
|
||||
* @param AnnotationReader $reader The AnnotationReader to use, duck-typed.
|
||||
* @param string|array $paths One or multiple paths where mapping classes can be found.
|
||||
* @param string|array $paths One or multiple paths where mapping classes can be found.
|
||||
*/
|
||||
public function __construct($reader, $paths = null)
|
||||
{
|
||||
@ -77,7 +76,7 @@ class AnnotationDriver implements Driver
|
||||
$this->addPaths((array) $paths);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append lookup paths to metadata driver.
|
||||
*
|
||||
@ -98,6 +97,16 @@ class AnnotationDriver implements Driver
|
||||
return $this->_paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current annotation reader
|
||||
*
|
||||
* @return AnnotationReader
|
||||
*/
|
||||
public function getReader()
|
||||
{
|
||||
return $this->_reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file extension used to look for mapping files under
|
||||
*
|
||||
@ -128,15 +137,25 @@ class AnnotationDriver implements Driver
|
||||
|
||||
$classAnnotations = $this->_reader->getClassAnnotations($class);
|
||||
|
||||
// Compatibility with Doctrine Common 3.x
|
||||
if ($classAnnotations && is_int(key($classAnnotations))) {
|
||||
foreach ($classAnnotations as $annot) {
|
||||
$classAnnotations[get_class($annot)] = $annot;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate Entity annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) {
|
||||
$entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity'];
|
||||
$metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
|
||||
|
||||
if ($entityAnnot->repositoryClass !== null) {
|
||||
$metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
|
||||
}
|
||||
if ($entityAnnot->readOnly) {
|
||||
$metadata->markReadOnly();
|
||||
}
|
||||
} else if (isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'])) {
|
||||
$mappedSuperclassAnnot = $classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'];
|
||||
$metadata->setCustomRepositoryClass($mappedSuperclassAnnot->repositoryClass);
|
||||
$metadata->isMappedSuperclass = true;
|
||||
} else {
|
||||
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
|
||||
@ -237,7 +256,6 @@ class AnnotationDriver implements Driver
|
||||
'unique' => $joinColumnAnnot->unique,
|
||||
'nullable' => $joinColumnAnnot->nullable,
|
||||
'onDelete' => $joinColumnAnnot->onDelete,
|
||||
'onUpdate' => $joinColumnAnnot->onUpdate,
|
||||
'columnDefinition' => $joinColumnAnnot->columnDefinition,
|
||||
);
|
||||
} else if ($joinColumnsAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumns')) {
|
||||
@ -248,7 +266,6 @@ class AnnotationDriver implements Driver
|
||||
'unique' => $joinColumn->unique,
|
||||
'nullable' => $joinColumn->nullable,
|
||||
'onDelete' => $joinColumn->onDelete,
|
||||
'onUpdate' => $joinColumn->onUpdate,
|
||||
'columnDefinition' => $joinColumn->columnDefinition,
|
||||
);
|
||||
}
|
||||
@ -356,7 +373,6 @@ class AnnotationDriver implements Driver
|
||||
'unique' => $joinColumn->unique,
|
||||
'nullable' => $joinColumn->nullable,
|
||||
'onDelete' => $joinColumn->onDelete,
|
||||
'onUpdate' => $joinColumn->onUpdate,
|
||||
'columnDefinition' => $joinColumn->columnDefinition,
|
||||
);
|
||||
}
|
||||
@ -368,7 +384,6 @@ class AnnotationDriver implements Driver
|
||||
'unique' => $joinColumn->unique,
|
||||
'nullable' => $joinColumn->nullable,
|
||||
'onDelete' => $joinColumn->onDelete,
|
||||
'onUpdate' => $joinColumn->onUpdate,
|
||||
'columnDefinition' => $joinColumn->columnDefinition,
|
||||
);
|
||||
}
|
||||
@ -397,6 +412,13 @@ class AnnotationDriver implements Driver
|
||||
if ($method->isPublic() && $method->getDeclaringClass()->getName() == $class->name) {
|
||||
$annotations = $this->_reader->getMethodAnnotations($method);
|
||||
|
||||
// Compatibility with Doctrine Common 3.x
|
||||
if ($annotations && is_int(key($annotations))) {
|
||||
foreach ($annotations as $annot) {
|
||||
$annotations[get_class($annot)] = $annot;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($annotations['Doctrine\ORM\Mapping\PrePersist'])) {
|
||||
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::prePersist);
|
||||
}
|
||||
@ -442,6 +464,20 @@ class AnnotationDriver implements Driver
|
||||
{
|
||||
$classAnnotations = $this->_reader->getClassAnnotations(new \ReflectionClass($className));
|
||||
|
||||
// Compatibility with Doctrine Common 3.x
|
||||
if ($classAnnotations && is_int(key($classAnnotations))) {
|
||||
foreach ($classAnnotations as $annot) {
|
||||
if ($annot instanceof \Doctrine\ORM\Mapping\Entity) {
|
||||
return false;
|
||||
}
|
||||
if ($annot instanceof \Doctrine\ORM\Mapping\MappedSuperclass) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! isset($classAnnotations['Doctrine\ORM\Mapping\Entity']) &&
|
||||
! isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass']);
|
||||
}
|
||||
@ -467,18 +503,20 @@ class AnnotationDriver implements Driver
|
||||
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
|
||||
}
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($path),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
$iterator = new \RegexIterator(
|
||||
new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
),
|
||||
'/^.+' . str_replace('.', '\.', $this->_fileExtension) . '$/i',
|
||||
\RecursiveRegexIterator::GET_MATCH
|
||||
);
|
||||
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if (($fileName = $file->getBasename($this->_fileExtension)) == $file->getBasename()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sourceFile = realpath($file->getPathName());
|
||||
$sourceFile = realpath($file[0]);
|
||||
|
||||
require_once $sourceFile;
|
||||
|
||||
$includedFiles[] = $sourceFile;
|
||||
}
|
||||
}
|
||||
@ -500,7 +538,7 @@ class AnnotationDriver implements Driver
|
||||
|
||||
/**
|
||||
* Factory method for the Annotation Driver
|
||||
*
|
||||
*
|
||||
* @param array|string $paths
|
||||
* @param AnnotationReader $reader
|
||||
* @return AnnotationDriver
|
||||
|
@ -55,7 +55,24 @@ class DatabaseDriver implements Driver
|
||||
* @var array
|
||||
*/
|
||||
private $manyToManyTables = array();
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $classNamesForTables = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $fieldNamesForColumns = array();
|
||||
|
||||
/**
|
||||
* The namespace for the generated entities.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
|
||||
* docblock annotations.
|
||||
@ -78,7 +95,7 @@ class DatabaseDriver implements Driver
|
||||
{
|
||||
$this->tables = $this->manyToManyTables = $this->classToTableNames = array();
|
||||
foreach ($entityTables AS $table) {
|
||||
$className = Inflector::classify(strtolower($table->getName()));
|
||||
$className = $this->getClassNameForTable($table->getName());
|
||||
$this->classToTableNames[$className] = $table->getName();
|
||||
$this->tables[$table->getName()] = $table;
|
||||
}
|
||||
@ -93,6 +110,8 @@ class DatabaseDriver implements Driver
|
||||
return;
|
||||
}
|
||||
|
||||
$tables = array();
|
||||
|
||||
foreach ($this->_sm->listTableNames() as $tableName) {
|
||||
$tables[$tableName] = $this->_sm->listTableDetails($tableName);
|
||||
}
|
||||
@ -120,7 +139,7 @@ class DatabaseDriver implements Driver
|
||||
} else {
|
||||
// lower-casing is necessary because of Oracle Uppercase Tablenames,
|
||||
// assumption is lower-case + underscore separated.
|
||||
$className = Inflector::classify(strtolower($tableName));
|
||||
$className = $this->getClassNameForTable($tableName);
|
||||
$this->tables[$tableName] = $table;
|
||||
$this->classToTableNames[$className] = $tableName;
|
||||
}
|
||||
@ -172,7 +191,7 @@ class DatabaseDriver implements Driver
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldMapping['fieldName'] = Inflector::camelize(strtolower($column->getName()));
|
||||
$fieldMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $column->getName(), false);
|
||||
$fieldMapping['columnName'] = $column->getName();
|
||||
$fieldMapping['type'] = strtolower((string) $column->getType());
|
||||
|
||||
@ -226,10 +245,10 @@ class DatabaseDriver implements Driver
|
||||
|
||||
$localColumn = current($myFk->getColumns());
|
||||
$associationMapping = array();
|
||||
$associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower(current($otherFk->getColumns()))));
|
||||
$associationMapping['targetEntity'] = Inflector::classify(strtolower($otherFk->getForeignTableName()));
|
||||
$associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true);
|
||||
$associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
|
||||
if (current($manyTable->getColumns())->getName() == $localColumn) {
|
||||
$associationMapping['inversedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns()))));
|
||||
$associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
|
||||
$associationMapping['joinTable'] = array(
|
||||
'name' => strtolower($manyTable->getName()),
|
||||
'joinColumns' => array(),
|
||||
@ -254,7 +273,7 @@ class DatabaseDriver implements Driver
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$associationMapping['mappedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns()))));
|
||||
$associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
|
||||
}
|
||||
$metadata->mapManyToMany($associationMapping);
|
||||
break;
|
||||
@ -269,8 +288,8 @@ class DatabaseDriver implements Driver
|
||||
|
||||
$localColumn = current($cols);
|
||||
$associationMapping = array();
|
||||
$associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower($localColumn)));
|
||||
$associationMapping['targetEntity'] = Inflector::classify($foreignTable);
|
||||
$associationMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $localColumn, true);
|
||||
$associationMapping['targetEntity'] = $this->getClassNameForTable($foreignTable);
|
||||
|
||||
for ($i = 0; $i < count($cols); $i++) {
|
||||
$associationMapping['joinColumns'][] = array(
|
||||
@ -303,4 +322,78 @@ class DatabaseDriver implements Driver
|
||||
|
||||
return array_keys($this->classToTableNames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set class name for a table.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param string $className
|
||||
* @return void
|
||||
*/
|
||||
public function setClassNameForTable($tableName, $className)
|
||||
{
|
||||
$this->classNamesForTables[$tableName] = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set field name for a column on a specific table.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param string $columnName
|
||||
* @param string $fieldName
|
||||
* @return void
|
||||
*/
|
||||
public function setFieldNameForColumn($tableName, $columnName, $fieldName)
|
||||
{
|
||||
$this->fieldNamesForColumns[$tableName][$columnName] = $fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapped class name for a table if it exists. Otherwise return "classified" version.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @return string
|
||||
*/
|
||||
private function getClassNameForTable($tableName)
|
||||
{
|
||||
if (isset($this->classNamesForTables[$tableName])) {
|
||||
return $this->namespace . $this->classNamesForTables[$tableName];
|
||||
}
|
||||
|
||||
return $this->namespace . Inflector::classify(strtolower($tableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapped field name for a column, if it exists. Otherwise return camelized version.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param string $columnName
|
||||
* @param boolean $fk Whether the column is a foreignkey or not.
|
||||
* @return string
|
||||
*/
|
||||
private function getFieldNameForColumn($tableName, $columnName, $fk = false)
|
||||
{
|
||||
if (isset($this->fieldNamesForColumns[$tableName]) && isset($this->fieldNamesForColumns[$tableName][$columnName])) {
|
||||
return $this->fieldNamesForColumns[$tableName][$columnName];
|
||||
}
|
||||
|
||||
$columnName = strtolower($columnName);
|
||||
|
||||
// Replace _id if it is a foreignkey column
|
||||
if ($fk) {
|
||||
$columnName = str_replace('_id', '', $columnName);
|
||||
}
|
||||
return Inflector::camelize($columnName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the namespace for the generated entities.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @return void
|
||||
*/
|
||||
public function setNamespace($namespace)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
}
|
||||
|
@ -19,126 +19,371 @@
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
interface Annotation {}
|
||||
|
||||
|
||||
/* Annotations */
|
||||
|
||||
final class Entity extends Annotation {
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class Entity implements Annotation {
|
||||
/** @var string */
|
||||
public $repositoryClass;
|
||||
/** @var boolean */
|
||||
public $readOnly = false;
|
||||
}
|
||||
final class MappedSuperclass extends Annotation {}
|
||||
final class InheritanceType extends Annotation {}
|
||||
final class DiscriminatorColumn extends Annotation {
|
||||
public $name;
|
||||
public $fieldName; // field name used in non-object hydration (array/scalar)
|
||||
public $type;
|
||||
public $length;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class MappedSuperclass implements Annotation {
|
||||
/** @var string */
|
||||
public $repositoryClass;
|
||||
}
|
||||
final class DiscriminatorMap extends Annotation {}
|
||||
final class Id extends Annotation {}
|
||||
final class GeneratedValue extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class InheritanceType implements Annotation {
|
||||
/** @var string */
|
||||
public $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class DiscriminatorColumn implements Annotation {
|
||||
/** @var string */
|
||||
public $name;
|
||||
/** @var string */
|
||||
public $type;
|
||||
/** @var integer */
|
||||
public $length;
|
||||
/** @var mixed */
|
||||
public $fieldName; // field name used in non-object hydration (array/scalar)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class DiscriminatorMap implements Annotation {
|
||||
/** @var array<string> */
|
||||
public $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class Id implements Annotation {}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class GeneratedValue implements Annotation {
|
||||
/** @var string */
|
||||
public $strategy = 'AUTO';
|
||||
}
|
||||
final class Version extends Annotation {}
|
||||
final class JoinColumn extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class Version implements Annotation {}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"PROPERTY","ANNOTATION"})
|
||||
*/
|
||||
final class JoinColumn implements Annotation {
|
||||
/** @var string */
|
||||
public $name;
|
||||
public $fieldName; // field name used in non-object hydration (array/scalar)
|
||||
/** @var string */
|
||||
public $referencedColumnName = 'id';
|
||||
/** @var boolean */
|
||||
public $unique = false;
|
||||
/** @var boolean */
|
||||
public $nullable = true;
|
||||
/** @var mixed */
|
||||
public $onDelete;
|
||||
public $onUpdate;
|
||||
/** @var string */
|
||||
public $columnDefinition;
|
||||
/** @var string */
|
||||
public $fieldName; // field name used in non-object hydration (array/scalar)
|
||||
}
|
||||
final class JoinColumns extends Annotation {}
|
||||
final class Column extends Annotation {
|
||||
public $type = 'string';
|
||||
public $length;
|
||||
// The precision for a decimal (exact numeric) column (Applies only for decimal column)
|
||||
public $precision = 0;
|
||||
// The scale for a decimal (exact numeric) column (Applies only for decimal column)
|
||||
public $scale = 0;
|
||||
public $unique = false;
|
||||
public $nullable = false;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class JoinColumns implements Annotation {
|
||||
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
|
||||
public $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class Column implements Annotation {
|
||||
/** @var string */
|
||||
public $name;
|
||||
/** @var mixed */
|
||||
public $type = 'string';
|
||||
/** @var integer */
|
||||
public $length;
|
||||
/** @var integer */
|
||||
public $precision = 0; // The precision for a decimal (exact numeric) column (Applies only for decimal column)
|
||||
/** @var integer */
|
||||
public $scale = 0; // The scale for a decimal (exact numeric) column (Applies only for decimal column)
|
||||
/** @var boolean */
|
||||
public $unique = false;
|
||||
/** @var boolean */
|
||||
public $nullable = false;
|
||||
/** @var array */
|
||||
public $options = array();
|
||||
/** @var string */
|
||||
public $columnDefinition;
|
||||
}
|
||||
final class OneToOne extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class OneToOne implements Annotation {
|
||||
/** @var string */
|
||||
public $targetEntity;
|
||||
/** @var string */
|
||||
public $mappedBy;
|
||||
/** @var string */
|
||||
public $inversedBy;
|
||||
/** @var array<string> */
|
||||
public $cascade;
|
||||
/** @var string */
|
||||
public $fetch = 'LAZY';
|
||||
/** @var boolean */
|
||||
public $orphanRemoval = false;
|
||||
}
|
||||
final class OneToMany extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class OneToMany implements Annotation {
|
||||
/** @var string */
|
||||
public $mappedBy;
|
||||
/** @var string */
|
||||
public $targetEntity;
|
||||
/** @var array<string> */
|
||||
public $cascade;
|
||||
/** @var string */
|
||||
public $fetch = 'LAZY';
|
||||
/** @var boolean */
|
||||
public $orphanRemoval = false;
|
||||
/** @var string */
|
||||
public $indexBy;
|
||||
}
|
||||
final class ManyToOne extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class ManyToOne implements Annotation {
|
||||
/** @var string */
|
||||
public $targetEntity;
|
||||
/** @var array<string> */
|
||||
public $cascade;
|
||||
/** @var string */
|
||||
public $fetch = 'LAZY';
|
||||
/** @var string */
|
||||
public $inversedBy;
|
||||
}
|
||||
final class ManyToMany extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class ManyToMany implements Annotation {
|
||||
/** @var string */
|
||||
public $targetEntity;
|
||||
/** @var string */
|
||||
public $mappedBy;
|
||||
/** @var string */
|
||||
public $inversedBy;
|
||||
/** @var array<string> */
|
||||
public $cascade;
|
||||
/** @var string */
|
||||
public $fetch = 'LAZY';
|
||||
/** @var string */
|
||||
public $indexBy;
|
||||
}
|
||||
final class ElementCollection extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
* @todo check available targets
|
||||
*/
|
||||
final class ElementCollection implements Annotation {
|
||||
/** @var string */
|
||||
public $tableName;
|
||||
}
|
||||
final class Table extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class Table implements Annotation {
|
||||
/** @var string */
|
||||
public $name;
|
||||
/** @var string */
|
||||
public $schema;
|
||||
/** @var array<Doctrine\ORM\Mapping\Index> */
|
||||
public $indexes;
|
||||
/** @var array<Doctrine\ORM\Mapping\UniqueConstraint> */
|
||||
public $uniqueConstraints;
|
||||
}
|
||||
final class UniqueConstraint extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class UniqueConstraint implements Annotation {
|
||||
/** @var string */
|
||||
public $name;
|
||||
/** @var array<string> */
|
||||
public $columns;
|
||||
}
|
||||
final class Index extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class Index implements Annotation {
|
||||
/** @var string */
|
||||
public $name;
|
||||
/** @var array<string> */
|
||||
public $columns;
|
||||
}
|
||||
final class JoinTable extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class JoinTable implements Annotation {
|
||||
/** @var string */
|
||||
public $name;
|
||||
/** @var string */
|
||||
public $schema;
|
||||
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
|
||||
public $joinColumns = array();
|
||||
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
|
||||
public $inverseJoinColumns = array();
|
||||
}
|
||||
final class SequenceGenerator extends Annotation {
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class SequenceGenerator implements Annotation {
|
||||
/** @var string */
|
||||
public $sequenceName;
|
||||
/** @var integer */
|
||||
public $allocationSize = 1;
|
||||
/** @var integer */
|
||||
public $initialValue = 1;
|
||||
}
|
||||
final class ChangeTrackingPolicy extends Annotation {}
|
||||
final class OrderBy extends Annotation {}
|
||||
|
||||
final class NamedQueries extends Annotation {}
|
||||
final class NamedQuery extends Annotation {
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class ChangeTrackingPolicy implements Annotation {
|
||||
/** @var string */
|
||||
public $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class OrderBy implements Annotation {
|
||||
/** @var array<string> */
|
||||
public $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class NamedQueries implements Annotation {
|
||||
/** @var array<Doctrine\ORM\Mapping\NamedQuery> */
|
||||
public $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class NamedQuery implements Annotation {
|
||||
/** @var string */
|
||||
public $name;
|
||||
/** @var string */
|
||||
public $query;
|
||||
}
|
||||
|
||||
/* Annotations for lifecycle callbacks */
|
||||
final class HasLifecycleCallbacks extends Annotation {}
|
||||
final class PrePersist extends Annotation {}
|
||||
final class PostPersist extends Annotation {}
|
||||
final class PreUpdate extends Annotation {}
|
||||
final class PostUpdate extends Annotation {}
|
||||
final class PreRemove extends Annotation {}
|
||||
final class PostRemove extends Annotation {}
|
||||
final class PostLoad extends Annotation {}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class HasLifecycleCallbacks implements Annotation {}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
final class PrePersist implements Annotation {}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
final class PostPersist implements Annotation {}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
final class PreUpdate implements Annotation {}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
final class PostUpdate implements Annotation {}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
final class PreRemove implements Annotation {}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
final class PostRemove implements Annotation {}
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
final class PostLoad implements Annotation {}
|
||||
|
@ -88,15 +88,20 @@ class DriverChain implements Driver
|
||||
public function getAllClassNames()
|
||||
{
|
||||
$classNames = array();
|
||||
$driverClasses = array();
|
||||
foreach ($this->_drivers AS $namespace => $driver) {
|
||||
$driverClasses = $driver->getAllClassNames();
|
||||
foreach ($driverClasses AS $className) {
|
||||
$oid = spl_object_hash($driver);
|
||||
if (!isset($driverClasses[$oid])) {
|
||||
$driverClasses[$oid] = $driver->getAllClassNames();
|
||||
}
|
||||
|
||||
foreach ($driverClasses[$oid] AS $className) {
|
||||
if (strpos($className, $namespace) === 0) {
|
||||
$classNames[] = $className;
|
||||
$classNames[$className] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_unique($classNames);
|
||||
return array_keys($classNames);
|
||||
}
|
||||
|
||||
/**
|
||||
|
176
lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php
Normal file
176
lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php
Normal file
@ -0,0 +1,176 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
|
||||
/**
|
||||
* XmlDriver that additionally looks for mapping information in a global file.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @license MIT
|
||||
*/
|
||||
class SimplifiedXmlDriver extends XmlDriver
|
||||
{
|
||||
protected $_prefixes = array();
|
||||
protected $_globalBasename;
|
||||
protected $_classCache;
|
||||
protected $_fileExtension = '.orm.xml';
|
||||
|
||||
public function __construct($prefixes)
|
||||
{
|
||||
$this->addNamespacePrefixes($prefixes);
|
||||
}
|
||||
|
||||
public function setGlobalBasename($file)
|
||||
{
|
||||
$this->_globalBasename = $file;
|
||||
}
|
||||
|
||||
public function getGlobalBasename()
|
||||
{
|
||||
return $this->_globalBasename;
|
||||
}
|
||||
|
||||
public function addNamespacePrefixes($prefixes)
|
||||
{
|
||||
$this->_prefixes = array_merge($this->_prefixes, $prefixes);
|
||||
$this->addPaths(array_flip($prefixes));
|
||||
}
|
||||
|
||||
public function getNamespacePrefixes()
|
||||
{
|
||||
return $this->_prefixes;
|
||||
}
|
||||
|
||||
public function isTransient($className)
|
||||
{
|
||||
if (null === $this->_classCache) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
// The mapping is defined in the global mapping file
|
||||
if (isset($this->_classCache[$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->_findMappingFile($className);
|
||||
|
||||
return false;
|
||||
} catch (MappingException $e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function getAllClassNames()
|
||||
{
|
||||
if (null === $this->_classCache) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
$classes = array();
|
||||
|
||||
if ($this->_paths) {
|
||||
foreach ((array) $this->_paths as $path) {
|
||||
if (!is_dir($path)) {
|
||||
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
|
||||
}
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($path),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$fileName = $file->getBasename($this->_fileExtension);
|
||||
|
||||
if ($fileName == $file->getBasename() || $fileName == $this->_globalBasename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: All files found here means classes are not transient!
|
||||
if (isset($this->_prefixes[$path])) {
|
||||
$classes[] = $this->_prefixes[$path].'\\'.str_replace('.', '\\', $fileName);
|
||||
} else {
|
||||
$classes[] = str_replace('.', '\\', $fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($classes, array_keys($this->_classCache));
|
||||
}
|
||||
|
||||
public function getElement($className)
|
||||
{
|
||||
if (null === $this->_classCache) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
if (!isset($this->_classCache[$className])) {
|
||||
$this->_classCache[$className] = parent::getElement($className);
|
||||
}
|
||||
|
||||
return $this->_classCache[$className];
|
||||
}
|
||||
|
||||
protected function initialize()
|
||||
{
|
||||
$this->_classCache = array();
|
||||
if (null !== $this->_globalBasename) {
|
||||
foreach ($this->_paths as $path) {
|
||||
if (is_file($file = $path.'/'.$this->_globalBasename.$this->_fileExtension)) {
|
||||
$this->_classCache = array_merge($this->_classCache, $this->_loadMappingFile($file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _findMappingFile($className)
|
||||
{
|
||||
$defaultFileName = str_replace('\\', '.', $className).$this->_fileExtension;
|
||||
foreach ($this->_paths as $path) {
|
||||
if (!isset($this->_prefixes[$path])) {
|
||||
if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) {
|
||||
return $path.DIRECTORY_SEPARATOR.$defaultFileName;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$prefix = $this->_prefixes[$path];
|
||||
|
||||
if (0 !== strpos($className, $prefix.'\\')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->_fileExtension;
|
||||
if (is_file($filename)) {
|
||||
return $filename;
|
||||
}
|
||||
|
||||
throw MappingException::mappingFileNotFound($className, $filename);
|
||||
}
|
||||
|
||||
throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->_fileExtension);
|
||||
}
|
||||
}
|
182
lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php
Normal file
182
lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
|
||||
/**
|
||||
* YamlDriver that additionally looks for mapping information in a global file.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @license MIT
|
||||
*/
|
||||
class SimplifiedYamlDriver extends YamlDriver
|
||||
{
|
||||
protected $_prefixes = array();
|
||||
protected $_globalBasename;
|
||||
protected $_classCache;
|
||||
protected $_fileExtension = '.orm.yml';
|
||||
|
||||
public function __construct($prefixes)
|
||||
{
|
||||
$this->addNamespacePrefixes($prefixes);
|
||||
}
|
||||
|
||||
public function setGlobalBasename($file)
|
||||
{
|
||||
$this->_globalBasename = $file;
|
||||
}
|
||||
|
||||
public function getGlobalBasename()
|
||||
{
|
||||
return $this->_globalBasename;
|
||||
}
|
||||
|
||||
public function addNamespacePrefixes($prefixes)
|
||||
{
|
||||
$this->_prefixes = array_merge($this->_prefixes, $prefixes);
|
||||
$this->addPaths(array_flip($prefixes));
|
||||
}
|
||||
|
||||
public function addNamespacePrefix($prefix, $path)
|
||||
{
|
||||
$this->_prefixes[$path] = $prefix;
|
||||
}
|
||||
|
||||
|
||||
public function getNamespacePrefixes()
|
||||
{
|
||||
return $this->_prefixes;
|
||||
}
|
||||
|
||||
public function isTransient($className)
|
||||
{
|
||||
if (null === $this->_classCache) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
// The mapping is defined in the global mapping file
|
||||
if (isset($this->_classCache[$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->_findMappingFile($className);
|
||||
|
||||
return false;
|
||||
} catch (MappingException $e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function getAllClassNames()
|
||||
{
|
||||
if (null === $this->_classCache) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
$classes = array();
|
||||
|
||||
if ($this->_paths) {
|
||||
foreach ((array) $this->_paths as $path) {
|
||||
if (!is_dir($path)) {
|
||||
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
|
||||
}
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($path),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$fileName = $file->getBasename($this->_fileExtension);
|
||||
|
||||
if ($fileName == $file->getBasename() || $fileName == $this->_globalBasename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: All files found here means classes are not transient!
|
||||
if (isset($this->_prefixes[$path])) {
|
||||
$classes[] = $this->_prefixes[$path].'\\'.str_replace('.', '\\', $fileName);
|
||||
} else {
|
||||
$classes[] = str_replace('.', '\\', $fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($classes, array_keys($this->_classCache));
|
||||
}
|
||||
|
||||
public function getElement($className)
|
||||
{
|
||||
if (null === $this->_classCache) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
if (!isset($this->_classCache[$className])) {
|
||||
$this->_classCache[$className] = parent::getElement($className);
|
||||
}
|
||||
|
||||
return $this->_classCache[$className];
|
||||
}
|
||||
|
||||
protected function initialize()
|
||||
{
|
||||
$this->_classCache = array();
|
||||
if (null !== $this->_globalBasename) {
|
||||
foreach ($this->_paths as $path) {
|
||||
if (is_file($file = $path.'/'.$this->_globalBasename.$this->_fileExtension)) {
|
||||
$this->_classCache = array_merge($this->_classCache, $this->_loadMappingFile($file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _findMappingFile($className)
|
||||
{
|
||||
$defaultFileName = str_replace('\\', '.', $className).$this->_fileExtension;
|
||||
foreach ($this->_paths as $path) {
|
||||
if (!isset($this->_prefixes[$path])) {
|
||||
if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) {
|
||||
return $path.DIRECTORY_SEPARATOR.$defaultFileName;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$prefix = $this->_prefixes[$path];
|
||||
|
||||
if (0 !== strpos($className, $prefix.'\\')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->_fileExtension;
|
||||
if (is_file($filename)) {
|
||||
return $filename;
|
||||
}
|
||||
|
||||
throw MappingException::mappingFileNotFound($className, $filename);
|
||||
}
|
||||
|
||||
throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->_fileExtension);
|
||||
}
|
||||
}
|
@ -52,13 +52,16 @@ class XmlDriver extends AbstractFileDriver
|
||||
$xmlRoot = $this->getElement($className);
|
||||
|
||||
if ($xmlRoot->getName() == 'entity') {
|
||||
$metadata->setCustomRepositoryClass(
|
||||
isset($xmlRoot['repository-class']) ? (string)$xmlRoot['repository-class'] : null
|
||||
);
|
||||
if (isset($xmlRoot['repository-class'])) {
|
||||
$metadata->setCustomRepositoryClass((string)$xmlRoot['repository-class']);
|
||||
}
|
||||
if (isset($xmlRoot['read-only']) && $xmlRoot['read-only'] == "true") {
|
||||
$metadata->markReadOnly();
|
||||
}
|
||||
} else if ($xmlRoot->getName() == 'mapped-superclass') {
|
||||
$metadata->setCustomRepositoryClass(
|
||||
isset($xmlRoot['repository-class']) ? (string)$xmlRoot['repository-class'] : null
|
||||
);
|
||||
$metadata->isMappedSuperclass = true;
|
||||
} else {
|
||||
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
|
||||
@ -285,8 +288,8 @@ class XmlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $this->_getCascadeMappings($oneToOneElement->cascade);
|
||||
}
|
||||
|
||||
if (isset($oneToOneElement->{'orphan-removal'})) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToOneElement->{'orphan-removal'};
|
||||
if (isset($oneToOneElement['orphan-removal'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToOneElement['orphan-removal'];
|
||||
}
|
||||
|
||||
$metadata->mapOneToOne($mapping);
|
||||
@ -310,8 +313,8 @@ class XmlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $this->_getCascadeMappings($oneToManyElement->cascade);
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement->{'orphan-removal'})) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToManyElement->{'orphan-removal'};
|
||||
if (isset($oneToManyElement['orphan-removal'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToManyElement['orphan-removal'];
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement->{'order-by'})) {
|
||||
@ -322,8 +325,8 @@ class XmlDriver extends AbstractFileDriver
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement->{'index-by'})) {
|
||||
$mapping['indexBy'] = (string)$oneToManyElement->{'index-by'};
|
||||
if (isset($oneToManyElement['index-by'])) {
|
||||
$mapping['indexBy'] = (string)$oneToManyElement['index-by'];
|
||||
}
|
||||
|
||||
$metadata->mapOneToMany($mapping);
|
||||
@ -356,9 +359,6 @@ class XmlDriver extends AbstractFileDriver
|
||||
$joinColumns[] = $this->_getJoinColumnMapping($manyToOneElement->{'join-column'});
|
||||
} else if (isset($manyToOneElement->{'join-columns'})) {
|
||||
foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
|
||||
if (!isset($joinColumnElement['name'])) {
|
||||
$joinColumnElement['name'] = $name;
|
||||
}
|
||||
$joinColumns[] = $this->_getJoinColumnMapping($joinColumnElement);
|
||||
}
|
||||
}
|
||||
@ -474,10 +474,6 @@ class XmlDriver extends AbstractFileDriver
|
||||
$joinColumn['onDelete'] = (string)$joinColumnElement['on-delete'];
|
||||
}
|
||||
|
||||
if (isset($joinColumnElement['on-update'])) {
|
||||
$joinColumn['onUpdate'] = (string)$joinColumnElement['on-update'];
|
||||
}
|
||||
|
||||
if (isset($joinColumnElement['column-definition'])) {
|
||||
$joinColumn['columnDefinition'] = (string)$joinColumnElement['column-definition'];
|
||||
}
|
||||
|
@ -46,13 +46,16 @@ class YamlDriver extends AbstractFileDriver
|
||||
$element = $this->getElement($className);
|
||||
|
||||
if ($element['type'] == 'entity') {
|
||||
$metadata->setCustomRepositoryClass(
|
||||
isset($element['repositoryClass']) ? $element['repositoryClass'] : null
|
||||
);
|
||||
if (isset($element['repositoryClass'])) {
|
||||
$metadata->setCustomRepositoryClass($element['repositoryClass']);
|
||||
}
|
||||
if (isset($element['readOnly']) && $element['readOnly'] == true) {
|
||||
$metadata->markReadOnly();
|
||||
}
|
||||
} else if ($element['type'] == 'mappedSuperclass') {
|
||||
$metadata->setCustomRepositoryClass(
|
||||
isset($element['repositoryClass']) ? $element['repositoryClass'] : null
|
||||
);
|
||||
$metadata->isMappedSuperclass = true;
|
||||
} else {
|
||||
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
|
||||
@ -435,7 +438,7 @@ class YamlDriver extends AbstractFileDriver
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['orphanRemoval'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphan-removal'];
|
||||
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval'];
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['orderBy'])) {
|
||||
@ -490,10 +493,6 @@ class YamlDriver extends AbstractFileDriver
|
||||
$joinColumn['onDelete'] = $joinColumnElement['onDelete'];
|
||||
}
|
||||
|
||||
if (isset($joinColumnElement['onUpdate'])) {
|
||||
$joinColumn['onUpdate'] = $joinColumnElement['onUpdate'];
|
||||
}
|
||||
|
||||
if (isset($joinColumnElement['columnDefinition'])) {
|
||||
$joinColumn['columnDefinition'] = $joinColumnElement['columnDefinition'];
|
||||
}
|
||||
@ -506,6 +505,6 @@ class YamlDriver extends AbstractFileDriver
|
||||
*/
|
||||
protected function _loadMappingFile($file)
|
||||
{
|
||||
return \Symfony\Component\Yaml\Yaml::load($file);
|
||||
return \Symfony\Component\Yaml\Yaml::parse($file);
|
||||
}
|
||||
}
|
||||
|
@ -284,4 +284,18 @@ class MappingException extends \Doctrine\ORM\ORMException
|
||||
{
|
||||
return new self("Its not supported to define inheritance information on a mapped superclass '" . $className . "'.");
|
||||
}
|
||||
|
||||
public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName)
|
||||
{
|
||||
return new self(
|
||||
"Entity '" . $className . "' has to be part of the descriminator 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."
|
||||
);
|
||||
}
|
||||
|
||||
public static function lifecycleCallbackMethodNotFound($className, $methodName)
|
||||
{
|
||||
return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback.");
|
||||
}
|
||||
}
|
@ -34,10 +34,25 @@ class ORMException extends Exception
|
||||
return new self("It's a requirement to specify a Metadata Driver and pass it ".
|
||||
"to Doctrine\ORM\Configuration::setMetadataDriverImpl().");
|
||||
}
|
||||
|
||||
public static function entityMissingForeignAssignedId($entity, $relatedEntity)
|
||||
{
|
||||
return new self(
|
||||
"Entity of type " . get_class($entity) . " has identity through a foreign entity " . get_class($relatedEntity) . ", " .
|
||||
"however this entity has no identity itself. You have to call EntityManager#persist() on the related entity " .
|
||||
"and make sure that an identifier was generated before trying to persist '" . get_class($entity) . "'. In case " .
|
||||
"of Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL) this means you have to call " .
|
||||
"EntityManager#flush() between both persist operations."
|
||||
);
|
||||
}
|
||||
|
||||
public static function entityMissingAssignedId($entity)
|
||||
{
|
||||
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID.");
|
||||
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID. " .
|
||||
"The identifier generation strategy for this entity requires the ID field to be populated before ".
|
||||
"EntityManager#persist() is called. If you want automatically generated identifiers instead " .
|
||||
"you need to adjust the metadata mapping accordingly."
|
||||
);
|
||||
}
|
||||
|
||||
public static function unrecognizedField($field)
|
||||
@ -115,4 +130,10 @@ class ORMException extends Exception
|
||||
"Unknown Entity namespace alias '$entityNamespaceAlias'."
|
||||
);
|
||||
}
|
||||
|
||||
public static function invalidEntityRepository($className)
|
||||
{
|
||||
return new self("Invalid repository class '".$className."'. ".
|
||||
"it must be a Doctrine\ORM\EntityRepository.");
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ class OptimisticLockException extends ORMException
|
||||
|
||||
public function __construct($msg, $entity)
|
||||
{
|
||||
parent::__construct($msg);
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
|
@ -572,6 +572,7 @@ final class PersistentCollection implements Collection
|
||||
}
|
||||
}
|
||||
$this->coll->clear();
|
||||
$this->initialized = true; // direct call, {@link initialize()} is too expensive
|
||||
if ($this->association['isOwningSide']) {
|
||||
$this->changed();
|
||||
$this->em->getUnitOfWork()->scheduleCollectionDeletion($this);
|
||||
|
@ -65,9 +65,11 @@ abstract class AbstractCollectionPersister
|
||||
public function delete(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
|
||||
if ( ! $mapping['isOwningSide']) {
|
||||
return; // ignore inverse side
|
||||
}
|
||||
|
||||
$sql = $this->_getDeleteSQL($coll);
|
||||
$this->_conn->executeUpdate($sql, $this->_getDeleteSQLParameters($coll));
|
||||
}
|
||||
@ -96,9 +98,11 @@ abstract class AbstractCollectionPersister
|
||||
public function update(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
|
||||
if ( ! $mapping['isOwningSide']) {
|
||||
return; // ignore inverse side
|
||||
}
|
||||
|
||||
$this->deleteRows($coll);
|
||||
//$this->updateRows($coll);
|
||||
$this->insertRows($coll);
|
||||
@ -108,6 +112,7 @@ abstract class AbstractCollectionPersister
|
||||
{
|
||||
$deleteDiff = $coll->getDeleteDiff();
|
||||
$sql = $this->_getDeleteRowSQL($coll);
|
||||
|
||||
foreach ($deleteDiff as $element) {
|
||||
$this->_conn->executeUpdate($sql, $this->_getDeleteRowSQLParameters($coll, $element));
|
||||
}
|
||||
@ -120,6 +125,7 @@ abstract class AbstractCollectionPersister
|
||||
{
|
||||
$insertDiff = $coll->getInsertDiff();
|
||||
$sql = $this->_getInsertRowSQL($coll);
|
||||
|
||||
foreach ($insertDiff as $element) {
|
||||
$this->_conn->executeUpdate($sql, $this->_getInsertRowSQLParameters($coll, $element));
|
||||
}
|
||||
|
@ -39,10 +39,12 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
|
||||
protected function _prepareInsertData($entity)
|
||||
{
|
||||
$data = parent::_prepareInsertData($entity);
|
||||
|
||||
// Populate the discriminator column
|
||||
$discColumn = $this->_class->discriminatorColumn;
|
||||
$this->_columnTypes[$discColumn['name']] = $discColumn['type'];
|
||||
$data[$this->_getDiscriminatorColumnTableName()][$discColumn['name']] = $this->_class->discriminatorValue;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@ -63,7 +65,7 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
|
||||
$this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
|
||||
|
||||
return "$sql AS $columnAlias";
|
||||
return $sql . ' AS ' . $columnAlias;
|
||||
}
|
||||
|
||||
protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className)
|
||||
@ -72,6 +74,6 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->addMetaResult('r', $resultColumnName, $joinColumnName);
|
||||
|
||||
return $tableAlias . ".$joinColumnName AS $columnAlias";
|
||||
return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias;
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ use PDO,
|
||||
Doctrine\ORM\ORMException,
|
||||
Doctrine\ORM\OptimisticLockException,
|
||||
Doctrine\ORM\EntityManager,
|
||||
Doctrine\ORM\UnitOfWork,
|
||||
Doctrine\ORM\Query,
|
||||
Doctrine\ORM\PersistentCollection,
|
||||
Doctrine\ORM\Mapping\MappingException,
|
||||
@ -221,13 +222,14 @@ class BasicEntityPersister
|
||||
$isPostInsertId = $idGen->isPostInsertGenerator();
|
||||
|
||||
$stmt = $this->_conn->prepare($this->_getInsertSQL());
|
||||
$tableName = $this->_class->table['name'];
|
||||
$tableName = $this->_class->getTableName();
|
||||
|
||||
foreach ($this->_queuedInserts as $entity) {
|
||||
$insertData = $this->_prepareInsertData($entity);
|
||||
|
||||
if (isset($insertData[$tableName])) {
|
||||
$paramIndex = 1;
|
||||
|
||||
foreach ($insertData[$tableName] as $column => $value) {
|
||||
$stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]);
|
||||
}
|
||||
@ -277,11 +279,14 @@ class BasicEntityPersister
|
||||
protected function fetchVersionValue($versionedClass, $id)
|
||||
{
|
||||
$versionField = $versionedClass->versionField;
|
||||
$identifier = $versionedClass->getIdentifierColumnNames();
|
||||
$versionFieldColumnName = $versionedClass->getColumnName($versionField);
|
||||
$identifier = $versionedClass->getIdentifierColumnNames();
|
||||
|
||||
$versionFieldColumnName = $versionedClass->getQuotedColumnName($versionField, $this->_platform);
|
||||
|
||||
//FIXME: Order with composite keys might not be correct
|
||||
$sql = "SELECT " . $versionFieldColumnName . " FROM " . $versionedClass->getQuotedTableName($this->_platform)
|
||||
. " WHERE " . implode(' = ? AND ', $identifier) . " = ?";
|
||||
$sql = 'SELECT ' . $versionFieldColumnName
|
||||
. ' FROM ' . $versionedClass->getQuotedTableName($this->_platform)
|
||||
. ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?';
|
||||
$value = $this->_conn->fetchColumn($sql, array_values((array)$id));
|
||||
|
||||
return Type::getType($versionedClass->fieldMappings[$versionField]['type'])->convertToPHPValue($value, $this->_platform);
|
||||
@ -304,7 +309,8 @@ class BasicEntityPersister
|
||||
public function update($entity)
|
||||
{
|
||||
$updateData = $this->_prepareUpdateData($entity);
|
||||
$tableName = $this->_class->table['name'];
|
||||
$tableName = $this->_class->getTableName();
|
||||
|
||||
if (isset($updateData[$tableName]) && $updateData[$tableName]) {
|
||||
$this->_updateTable(
|
||||
$entity, $this->_class->getQuotedTableName($this->_platform),
|
||||
@ -332,17 +338,17 @@ class BasicEntityPersister
|
||||
$set = $params = $types = array();
|
||||
|
||||
foreach ($updateData as $columnName => $value) {
|
||||
if (isset($this->_class->fieldNames[$columnName])) {
|
||||
$set[] = $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?';
|
||||
} else {
|
||||
$set[] = $columnName . ' = ?';
|
||||
}
|
||||
$set[] = (isset($this->_class->fieldNames[$columnName]))
|
||||
? $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?'
|
||||
: $columnName . ' = ?';
|
||||
|
||||
$params[] = $value;
|
||||
$types[] = $this->_columnTypes[$columnName];
|
||||
}
|
||||
|
||||
$where = array();
|
||||
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
|
||||
|
||||
foreach ($this->_class->identifier as $idField) {
|
||||
if (isset($this->_class->associationMappings[$idField])) {
|
||||
$targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']);
|
||||
@ -360,18 +366,21 @@ class BasicEntityPersister
|
||||
$versionField = $this->_class->versionField;
|
||||
$versionFieldType = $this->_class->fieldMappings[$versionField]['type'];
|
||||
$versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform);
|
||||
|
||||
if ($versionFieldType == Type::INTEGER) {
|
||||
$set[] = $versionColumn . ' = ' . $versionColumn . ' + 1';
|
||||
} else if ($versionFieldType == Type::DATETIME) {
|
||||
$set[] = $versionColumn . ' = CURRENT_TIMESTAMP';
|
||||
}
|
||||
|
||||
$where[] = $versionColumn;
|
||||
$params[] = $this->_class->reflFields[$versionField]->getValue($entity);
|
||||
$types[] = $this->_class->fieldMappings[$versionField]['type'];
|
||||
}
|
||||
|
||||
$sql = "UPDATE $quotedTableName SET " . implode(', ', $set)
|
||||
. ' WHERE ' . implode(' = ? AND ', $where) . ' = ?';
|
||||
$sql = 'UPDATE ' . $quotedTableName
|
||||
. ' SET ' . implode(', ', $set)
|
||||
. ' WHERE ' . implode(' = ? AND ', $where) . ' = ?';
|
||||
|
||||
$result = $this->_conn->executeUpdate($sql, $params, $types);
|
||||
|
||||
@ -397,21 +406,29 @@ class BasicEntityPersister
|
||||
$relatedClass = $this->_em->getClassMetadata($mapping['targetEntity']);
|
||||
$mapping = $relatedClass->associationMappings[$mapping['mappedBy']];
|
||||
$keys = array_keys($mapping['relationToTargetKeyColumns']);
|
||||
|
||||
if ($selfReferential) {
|
||||
$otherKeys = array_keys($mapping['relationToSourceKeyColumns']);
|
||||
}
|
||||
} else {
|
||||
$keys = array_keys($mapping['relationToSourceKeyColumns']);
|
||||
|
||||
if ($selfReferential) {
|
||||
$otherKeys = array_keys($mapping['relationToTargetKeyColumns']);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset($mapping['isOnDeleteCascade'])) {
|
||||
$this->_conn->delete($mapping['joinTable']['name'], array_combine($keys, $identifier));
|
||||
$this->_conn->delete(
|
||||
$this->_class->getQuotedJoinTableName($mapping, $this->_platform),
|
||||
array_combine($keys, $identifier)
|
||||
);
|
||||
|
||||
if ($selfReferential) {
|
||||
$this->_conn->delete($mapping['joinTable']['name'], array_combine($otherKeys, $identifier));
|
||||
$this->_conn->delete(
|
||||
$this->_class->getQuotedJoinTableName($mapping, $this->_platform),
|
||||
array_combine($otherKeys, $identifier)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -462,7 +479,7 @@ class BasicEntityPersister
|
||||
$result = array();
|
||||
$uow = $this->_em->getUnitOfWork();
|
||||
|
||||
if ($versioned = $this->_class->isVersioned) {
|
||||
if (($versioned = $this->_class->isVersioned) != false) {
|
||||
$versionField = $this->_class->versionField;
|
||||
}
|
||||
|
||||
@ -476,6 +493,7 @@ class BasicEntityPersister
|
||||
|
||||
if (isset($this->_class->associationMappings[$field])) {
|
||||
$assoc = $this->_class->associationMappings[$field];
|
||||
|
||||
// Only owning side of x-1 associations can have a FK column.
|
||||
if ( ! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE)) {
|
||||
continue;
|
||||
@ -483,6 +501,7 @@ class BasicEntityPersister
|
||||
|
||||
if ($newVal !== null) {
|
||||
$oid = spl_object_hash($newVal);
|
||||
|
||||
if (isset($this->_queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) {
|
||||
// The associated entity $newVal is not yet persisted, so we must
|
||||
// set $newVal = null, in order to insert a null value and schedule an
|
||||
@ -509,6 +528,7 @@ class BasicEntityPersister
|
||||
} else {
|
||||
$result[$owningTable][$sourceColumn] = $newValId[$targetClass->fieldNames[$targetColumn]];
|
||||
}
|
||||
|
||||
$this->_columnTypes[$sourceColumn] = $targetClass->getTypeOfColumn($targetColumn);
|
||||
}
|
||||
} else {
|
||||
@ -517,6 +537,7 @@ class BasicEntityPersister
|
||||
$result[$this->getOwningTable($field)][$columnName] = $newVal;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@ -547,7 +568,7 @@ class BasicEntityPersister
|
||||
*/
|
||||
public function getOwningTable($fieldName)
|
||||
{
|
||||
return $this->_class->table['name'];
|
||||
return $this->_class->getTableName();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -570,14 +591,12 @@ class BasicEntityPersister
|
||||
|
||||
if ($entity !== null) {
|
||||
$hints[Query::HINT_REFRESH] = true;
|
||||
$hints[Query::HINT_REFRESH_ENTITY] = $entity;
|
||||
}
|
||||
|
||||
if ($this->_selectJoinSql) {
|
||||
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
|
||||
} else {
|
||||
$hydrator = $this->_em->newHydrator(Query::HYDRATE_SIMPLEOBJECT);
|
||||
}
|
||||
$hydrator = $this->_em->newHydrator($this->_selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT);
|
||||
$entities = $hydrator->hydrateAll($stmt, $this->_rsm, $hints);
|
||||
|
||||
return $entities ? $entities[0] : null;
|
||||
}
|
||||
|
||||
@ -587,15 +606,14 @@ class BasicEntityPersister
|
||||
*
|
||||
* @param array $assoc The association to load.
|
||||
* @param object $sourceEntity The entity that owns the association (not necessarily the "owning side").
|
||||
* @param object $targetEntity The existing ghost entity (proxy) to load, if any.
|
||||
* @param array $identifier The identifier of the entity to load. Must be provided if
|
||||
* the association to load represents the owning side, otherwise
|
||||
* the identifier is derived from the $sourceEntity.
|
||||
* @return object The loaded and managed entity instance or NULL if the entity can not be found.
|
||||
*/
|
||||
public function loadOneToOneEntity(array $assoc, $sourceEntity, $targetEntity, array $identifier = array())
|
||||
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
|
||||
{
|
||||
if ($foundEntity = $this->_em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) {
|
||||
if (($foundEntity = $this->_em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) != false) {
|
||||
return $foundEntity;
|
||||
}
|
||||
|
||||
@ -607,21 +625,24 @@ class BasicEntityPersister
|
||||
// Mark inverse side as fetched in the hints, otherwise the UoW would
|
||||
// try to load it in a separate query (remember: to-one inverse sides can not be lazy).
|
||||
$hints = array();
|
||||
|
||||
if ($isInverseSingleValued) {
|
||||
$hints['fetched'][$targetClass->name][$assoc['inversedBy']] = true;
|
||||
|
||||
if ($targetClass->subClasses) {
|
||||
foreach ($targetClass->subClasses as $targetSubclassName) {
|
||||
$hints['fetched'][$targetSubclassName][$assoc['inversedBy']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* cascade read-only status
|
||||
if ($this->_em->getUnitOfWork()->isReadOnly($sourceEntity)) {
|
||||
$hints[Query::HINT_READ_ONLY] = true;
|
||||
}
|
||||
*/
|
||||
|
||||
$targetEntity = $this->load($identifier, $targetEntity, $assoc, $hints);
|
||||
$targetEntity = $this->load($identifier, null, $assoc, $hints);
|
||||
|
||||
// Complete bidirectional association, if necessary
|
||||
if ($targetEntity !== null && $isInverseSingleValued) {
|
||||
@ -630,18 +651,24 @@ class BasicEntityPersister
|
||||
} else {
|
||||
$sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']);
|
||||
$owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']);
|
||||
|
||||
// TRICKY: since the association is specular source and target are flipped
|
||||
foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) {
|
||||
if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
|
||||
$identifier[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
|
||||
} else {
|
||||
if ( ! isset($sourceClass->fieldNames[$sourceKeyColumn])) {
|
||||
throw MappingException::joinColumnMustPointToMappedField(
|
||||
$sourceClass->name, $sourceKeyColumn
|
||||
);
|
||||
}
|
||||
|
||||
// unset the old value and set the new sql aliased value here. By definition
|
||||
// unset($identifier[$targetKeyColumn] works here with how UnitOfWork::createEntity() calls this method.
|
||||
$identifier[$this->_getSQLTableAlias($targetClass->name) . "." . $targetKeyColumn] =
|
||||
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
|
||||
|
||||
unset($identifier[$targetKeyColumn]);
|
||||
}
|
||||
|
||||
$targetEntity = $this->load($identifier, $targetEntity, $assoc);
|
||||
$targetEntity = $this->load($identifier, null, $assoc);
|
||||
|
||||
if ($targetEntity !== null) {
|
||||
$targetClass->setFieldValue($targetEntity, $assoc['mappedBy'], $sourceEntity);
|
||||
@ -670,7 +697,9 @@ class BasicEntityPersister
|
||||
if (isset($this->_class->lifecycleCallbacks[Events::postLoad])) {
|
||||
$this->_class->invokeLifecycleCallbacks(Events::postLoad, $entity);
|
||||
}
|
||||
|
||||
$evm = $this->_em->getEventManager();
|
||||
|
||||
if ($evm->hasListeners(Events::postLoad)) {
|
||||
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
|
||||
}
|
||||
@ -692,11 +721,8 @@ class BasicEntityPersister
|
||||
list($params, $types) = $this->expandParameters($criteria);
|
||||
$stmt = $this->_conn->executeQuery($sql, $params, $types);
|
||||
|
||||
if ($this->_selectJoinSql) {
|
||||
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
|
||||
} else {
|
||||
$hydrator = $this->_em->newHydrator(Query::HYDRATE_SIMPLEOBJECT);
|
||||
}
|
||||
$hydrator = $this->_em->newHydrator(($this->_selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT);
|
||||
|
||||
return $hydrator->hydrateAll($stmt, $this->_rsm, array('deferEagerLoads' => true));
|
||||
}
|
||||
|
||||
@ -712,6 +738,7 @@ class BasicEntityPersister
|
||||
public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
|
||||
{
|
||||
$stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit);
|
||||
|
||||
return $this->loadArrayFromStatement($assoc, $stmt);
|
||||
}
|
||||
|
||||
@ -720,6 +747,7 @@ class BasicEntityPersister
|
||||
*
|
||||
* @param array $assoc
|
||||
* @param Doctrine\DBAL\Statement $stmt
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function loadArrayFromStatement($assoc, $stmt)
|
||||
@ -734,6 +762,7 @@ class BasicEntityPersister
|
||||
}
|
||||
|
||||
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
|
||||
|
||||
return $hydrator->hydrateAll($stmt, $rsm, $hints);
|
||||
}
|
||||
|
||||
@ -743,6 +772,8 @@ class BasicEntityPersister
|
||||
* @param array $assoc
|
||||
* @param Doctrine\DBAL\Statement $stmt
|
||||
* @param PersistentCollection $coll
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function loadCollectionFromStatement($assoc, $stmt, $coll)
|
||||
{
|
||||
@ -756,7 +787,8 @@ class BasicEntityPersister
|
||||
}
|
||||
|
||||
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
|
||||
$hydrator->hydrateAll($stmt, $rsm, $hints);
|
||||
|
||||
return $hydrator->hydrateAll($stmt, $rsm, $hints);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -772,6 +804,7 @@ class BasicEntityPersister
|
||||
public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
|
||||
{
|
||||
$stmt = $this->getManyToManyStatement($assoc, $sourceEntity);
|
||||
|
||||
return $this->loadCollectionFromStatement($assoc, $stmt, $coll);
|
||||
}
|
||||
|
||||
@ -779,12 +812,15 @@ class BasicEntityPersister
|
||||
{
|
||||
$criteria = array();
|
||||
$sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']);
|
||||
|
||||
if ($assoc['isOwningSide']) {
|
||||
$quotedJoinTable = $sourceClass->getQuotedJoinTableName($assoc, $this->_platform);
|
||||
|
||||
foreach ($assoc['relationToSourceKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) {
|
||||
if ($sourceClass->containsForeignIdentifier) {
|
||||
$field = $sourceClass->getFieldForColumn($sourceKeyColumn);
|
||||
$value = $sourceClass->reflFields[$field]->getValue($sourceEntity);
|
||||
|
||||
if (isset($sourceClass->associationMappings[$field])) {
|
||||
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
|
||||
$value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]];
|
||||
@ -802,15 +838,18 @@ class BasicEntityPersister
|
||||
} else {
|
||||
$owningAssoc = $this->_em->getClassMetadata($assoc['targetEntity'])->associationMappings[$assoc['mappedBy']];
|
||||
$quotedJoinTable = $sourceClass->getQuotedJoinTableName($owningAssoc, $this->_platform);
|
||||
|
||||
// TRICKY: since the association is inverted source and target are flipped
|
||||
foreach ($owningAssoc['relationToTargetKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) {
|
||||
if ($sourceClass->containsForeignIdentifier) {
|
||||
$field = $sourceClass->getFieldForColumn($sourceKeyColumn);
|
||||
$value = $sourceClass->reflFields[$field]->getValue($sourceEntity);
|
||||
|
||||
if (isset($sourceClass->associationMappings[$field])) {
|
||||
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
|
||||
$value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]];
|
||||
}
|
||||
|
||||
$criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value;
|
||||
} else if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
|
||||
$criteria[$quotedJoinTable . "." . $relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
|
||||
@ -824,6 +863,7 @@ class BasicEntityPersister
|
||||
|
||||
$sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset);
|
||||
list($params, $types) = $this->expandParameters($criteria);
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params, $types);
|
||||
}
|
||||
|
||||
@ -842,15 +882,14 @@ class BasicEntityPersister
|
||||
*/
|
||||
protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
|
||||
{
|
||||
$joinSql = $assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY ?
|
||||
$this->_getSelectManyToManyJoinSQL($assoc) : '';
|
||||
|
||||
$joinSql = ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) ? $this->_getSelectManyToManyJoinSQL($assoc) : '';
|
||||
$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
|
||||
|
||||
$orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
|
||||
$orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
|
||||
$orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $this->_getSQLTableAlias($this->_class->name)) : '';
|
||||
|
||||
$lockSql = '';
|
||||
|
||||
if ($lockMode == LockMode::PESSIMISTIC_READ) {
|
||||
$lockSql = ' ' . $this->_platform->getReadLockSql();
|
||||
} else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
|
||||
@ -877,6 +916,7 @@ class BasicEntityPersister
|
||||
protected final function _getOrderBySQL(array $orderBy, $baseTableAlias)
|
||||
{
|
||||
$orderBySql = '';
|
||||
|
||||
foreach ($orderBy as $fieldName => $orientation) {
|
||||
if ( ! isset($this->_class->fieldMappings[$fieldName])) {
|
||||
throw ORMException::unrecognizedField($fieldName);
|
||||
@ -887,6 +927,7 @@ class BasicEntityPersister
|
||||
: $baseTableAlias;
|
||||
|
||||
$columnName = $this->_class->getQuotedColumnName($fieldName, $this->_platform);
|
||||
|
||||
$orderBySql .= $orderBySql ? ', ' : ' ORDER BY ';
|
||||
$orderBySql .= $tableAlias . '.' . $columnName . ' ' . $orientation;
|
||||
}
|
||||
@ -919,20 +960,25 @@ class BasicEntityPersister
|
||||
// Add regular columns to select list
|
||||
foreach ($this->_class->fieldNames as $field) {
|
||||
if ($columnList) $columnList .= ', ';
|
||||
|
||||
$columnList .= $this->_getSelectColumnSQL($field, $this->_class);
|
||||
}
|
||||
|
||||
$this->_selectJoinSql = '';
|
||||
$eagerAliasCounter = 0;
|
||||
|
||||
foreach ($this->_class->associationMappings as $assocField => $assoc) {
|
||||
$assocColumnSQL = $this->_getSelectColumnAssociationSQL($assocField, $assoc, $this->_class);
|
||||
|
||||
if ($assocColumnSQL) {
|
||||
if ($columnList) $columnList .= ', ';
|
||||
|
||||
$columnList .= $assocColumnSQL;
|
||||
}
|
||||
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE && ($assoc['fetch'] == ClassMetadata::FETCH_EAGER || !$assoc['isOwningSide'])) {
|
||||
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
continue; // now this is why you shouldn't use inheritance
|
||||
}
|
||||
@ -942,33 +988,49 @@ class BasicEntityPersister
|
||||
|
||||
foreach ($eagerEntity->fieldNames AS $field) {
|
||||
if ($columnList) $columnList .= ', ';
|
||||
|
||||
$columnList .= $this->_getSelectColumnSQL($field, $eagerEntity, $assocAlias);
|
||||
}
|
||||
|
||||
foreach ($eagerEntity->associationMappings as $assoc2Field => $assoc2) {
|
||||
$assoc2ColumnSQL = $this->_getSelectColumnAssociationSQL($assoc2Field, $assoc2, $eagerEntity, $assocAlias);
|
||||
|
||||
if ($assoc2ColumnSQL) {
|
||||
if ($columnList) $columnList .= ', ';
|
||||
$columnList .= $assoc2ColumnSQL;
|
||||
}
|
||||
}
|
||||
|
||||
$this->_selectJoinSql .= ' LEFT JOIN'; // TODO: Inner join when all join columns are NOT nullable.
|
||||
$first = true;
|
||||
|
||||
if ($assoc['isOwningSide']) {
|
||||
$this->_selectJoinSql .= ' ' . $eagerEntity->table['name'] . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON ';
|
||||
$this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON ';
|
||||
|
||||
foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) {
|
||||
$this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.'.$sourceCol.' = ' .
|
||||
$this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.'.$targetCol.' ';
|
||||
if ( ! $first) {
|
||||
$this->_selectJoinSql .= ' AND ';
|
||||
}
|
||||
|
||||
$this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = '
|
||||
. $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol . ' ';
|
||||
$first = false;
|
||||
}
|
||||
} else {
|
||||
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
|
||||
$owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']);
|
||||
|
||||
$this->_selectJoinSql .= ' ' . $eagerEntity->table['name'] . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON ';
|
||||
$this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' '
|
||||
. $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON ';
|
||||
|
||||
foreach ($owningAssoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) {
|
||||
$this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.'.$sourceCol.' = ' .
|
||||
$this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol . ' ';
|
||||
if ( ! $first) {
|
||||
$this->_selectJoinSql .= ' AND ';
|
||||
}
|
||||
|
||||
$this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = '
|
||||
. $this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol . ' ';
|
||||
$first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -979,19 +1041,32 @@ class BasicEntityPersister
|
||||
return $this->_selectColumnListSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SQL join fragment used when selecting entities from an association.
|
||||
*
|
||||
* @param string $field
|
||||
* @param array $assoc
|
||||
* @param ClassMetadata $class
|
||||
* @param string $alias
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $class, $alias = 'r')
|
||||
{
|
||||
$columnList = '';
|
||||
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
if ($columnList) $columnList .= ', ';
|
||||
|
||||
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
|
||||
$columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . ".$srcColumn AS $columnAlias";
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->addMetaResult($alias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, isset($assoc['id']) && $assoc['id'] === true);
|
||||
$columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) )
|
||||
. '.' . $srcColumn . ' AS ' . $resultColumnName;
|
||||
$this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true);
|
||||
}
|
||||
}
|
||||
|
||||
return $columnList;
|
||||
}
|
||||
|
||||
@ -1013,23 +1088,22 @@ class BasicEntityPersister
|
||||
}
|
||||
|
||||
$joinTableName = $this->_class->getQuotedJoinTableName($owningAssoc, $this->_platform);
|
||||
|
||||
$joinSql = '';
|
||||
|
||||
foreach ($joinClauses as $joinTableColumn => $sourceColumn) {
|
||||
if ($joinSql != '') $joinSql .= ' AND ';
|
||||
|
||||
if ($this->_class->containsForeignIdentifier && !isset($this->_class->fieldNames[$sourceColumn])) {
|
||||
if ($this->_class->containsForeignIdentifier && ! isset($this->_class->fieldNames[$sourceColumn])) {
|
||||
$quotedColumn = $sourceColumn; // join columns cannot be quoted
|
||||
} else {
|
||||
$quotedColumn = $this->_class->getQuotedColumnName($this->_class->fieldNames[$sourceColumn], $this->_platform);
|
||||
}
|
||||
|
||||
$joinSql .= $this->_getSQLTableAlias($this->_class->name) .
|
||||
'.' . $quotedColumn . ' = '
|
||||
. $joinTableName . '.' . $joinTableColumn;
|
||||
$joinSql .= $this->_getSQLTableAlias($this->_class->name) . '.' . $quotedColumn . ' = '
|
||||
. $joinTableName . '.' . $joinTableColumn;
|
||||
}
|
||||
|
||||
return " INNER JOIN $joinTableName ON $joinSql";
|
||||
return ' INNER JOIN ' . $joinTableName . ' ON ' . $joinSql;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1042,21 +1116,23 @@ class BasicEntityPersister
|
||||
if ($this->_insertSql === null) {
|
||||
$insertSql = '';
|
||||
$columns = $this->_getInsertColumnList();
|
||||
|
||||
if (empty($columns)) {
|
||||
$insertSql = $this->_platform->getEmptyIdentityInsertSQL(
|
||||
$this->_class->getQuotedTableName($this->_platform),
|
||||
$this->_class->getQuotedColumnName($this->_class->identifier[0], $this->_platform)
|
||||
$this->_class->getQuotedTableName($this->_platform),
|
||||
$this->_class->getQuotedColumnName($this->_class->identifier[0], $this->_platform)
|
||||
);
|
||||
} else {
|
||||
$columns = array_unique($columns);
|
||||
$values = array_fill(0, count($columns), '?');
|
||||
|
||||
$insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform)
|
||||
. ' (' . implode(', ', $columns) . ') '
|
||||
. 'VALUES (' . implode(', ', $values) . ')';
|
||||
. ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')';
|
||||
}
|
||||
|
||||
$this->_insertSql = $insertSql;
|
||||
}
|
||||
|
||||
return $this->_insertSql;
|
||||
}
|
||||
|
||||
@ -1071,19 +1147,21 @@ class BasicEntityPersister
|
||||
protected function _getInsertColumnList()
|
||||
{
|
||||
$columns = array();
|
||||
|
||||
foreach ($this->_class->reflFields as $name => $field) {
|
||||
if ($this->_class->isVersioned && $this->_class->versionField == $name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($this->_class->associationMappings[$name])) {
|
||||
$assoc = $this->_class->associationMappings[$name];
|
||||
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) {
|
||||
$columns[] = $sourceCol;
|
||||
}
|
||||
}
|
||||
} else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY ||
|
||||
$this->_class->identifier[0] != $name) {
|
||||
} else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || $this->_class->identifier[0] != $name) {
|
||||
$columns[] = $this->_class->getQuotedColumnName($name, $this->_platform);
|
||||
}
|
||||
}
|
||||
@ -1102,11 +1180,13 @@ class BasicEntityPersister
|
||||
protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r')
|
||||
{
|
||||
$columnName = $class->columnNames[$field];
|
||||
$sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform);
|
||||
$sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias)
|
||||
. '.' . $class->getQuotedColumnName($field, $this->_platform);
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
|
||||
|
||||
$this->_rsm->addFieldResult($alias, $columnAlias, $field);
|
||||
|
||||
return "$sql AS $columnAlias";
|
||||
return $sql . ' AS ' . $columnAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1119,15 +1199,17 @@ class BasicEntityPersister
|
||||
protected function _getSQLTableAlias($className, $assocName = '')
|
||||
{
|
||||
if ($assocName) {
|
||||
$className .= '#'.$assocName;
|
||||
$className .= '#' . $assocName;
|
||||
}
|
||||
|
||||
if (isset($this->_sqlTableAliases[$className])) {
|
||||
return $this->_sqlTableAliases[$className];
|
||||
}
|
||||
|
||||
$tableAlias = 't' . $this->_sqlAliasCounter++;
|
||||
|
||||
$this->_sqlTableAliases[$className] = $tableAlias;
|
||||
|
||||
return $tableAlias;
|
||||
}
|
||||
|
||||
@ -1151,7 +1233,9 @@ class BasicEntityPersister
|
||||
$sql = 'SELECT 1 '
|
||||
. $this->_platform->appendLockHint($this->getLockTablesSql(), $lockMode)
|
||||
. ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql;
|
||||
|
||||
list($params, $types) = $this->expandParameters($criteria);
|
||||
|
||||
$stmt = $this->_conn->executeQuery($sql, $params, $types);
|
||||
}
|
||||
|
||||
@ -1163,7 +1247,7 @@ class BasicEntityPersister
|
||||
protected function getLockTablesSql()
|
||||
{
|
||||
return 'FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
|
||||
. $this->_getSQLTableAlias($this->_class->name);
|
||||
. $this->_getSQLTableAlias($this->_class->name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1180,37 +1264,36 @@ class BasicEntityPersister
|
||||
protected function _getSelectConditionSQL(array $criteria, $assoc = null)
|
||||
{
|
||||
$conditionSql = '';
|
||||
|
||||
foreach ($criteria as $field => $value) {
|
||||
$conditionSql .= $conditionSql ? ' AND ' : '';
|
||||
|
||||
if (isset($this->_class->columnNames[$field])) {
|
||||
if (isset($this->_class->fieldMappings[$field]['inherited'])) {
|
||||
$conditionSql .= $this->_getSQLTableAlias($this->_class->fieldMappings[$field]['inherited']) . '.';
|
||||
} else {
|
||||
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.';
|
||||
}
|
||||
$conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform);
|
||||
$className = (isset($this->_class->fieldMappings[$field]['inherited']))
|
||||
? $this->_class->fieldMappings[$field]['inherited']
|
||||
: $this->_class->name;
|
||||
|
||||
$conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform);
|
||||
} else if (isset($this->_class->associationMappings[$field])) {
|
||||
if (!$this->_class->associationMappings[$field]['isOwningSide']) {
|
||||
if ( ! $this->_class->associationMappings[$field]['isOwningSide']) {
|
||||
throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field);
|
||||
}
|
||||
|
||||
if (isset($this->_class->associationMappings[$field]['inherited'])) {
|
||||
$conditionSql .= $this->_getSQLTableAlias($this->_class->associationMappings[$field]['inherited']) . '.';
|
||||
} else {
|
||||
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.';
|
||||
}
|
||||
|
||||
$conditionSql .= $this->_class->associationMappings[$field]['joinColumns'][0]['name'];
|
||||
|
||||
$className = (isset($this->_class->associationMappings[$field]['inherited']))
|
||||
? $this->_class->associationMappings[$field]['inherited']
|
||||
: $this->_class->name;
|
||||
|
||||
$conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->associationMappings[$field]['joinColumns'][0]['name'];
|
||||
} else if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) {
|
||||
// very careless developers could potentially open up this normally hidden api for userland attacks,
|
||||
// therefore checking for spaces and function calls which are not allowed.
|
||||
|
||||
|
||||
// found a join column condition, not really a "field"
|
||||
$conditionSql .= $field;
|
||||
} else {
|
||||
throw ORMException::unrecognizedField($field);
|
||||
}
|
||||
|
||||
$conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ?');
|
||||
}
|
||||
return $conditionSql;
|
||||
@ -1228,6 +1311,7 @@ class BasicEntityPersister
|
||||
public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
|
||||
{
|
||||
$stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit);
|
||||
|
||||
return $this->loadArrayFromStatement($assoc, $stmt);
|
||||
}
|
||||
|
||||
@ -1243,7 +1327,8 @@ class BasicEntityPersister
|
||||
public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
|
||||
{
|
||||
$stmt = $this->getOneToManyStatement($assoc, $sourceEntity);
|
||||
$this->loadCollectionFromStatement($assoc, $stmt, $coll);
|
||||
|
||||
return $this->loadCollectionFromStatement($assoc, $stmt, $coll);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1261,18 +1346,18 @@ class BasicEntityPersister
|
||||
$owningAssoc = $this->_class->associationMappings[$assoc['mappedBy']];
|
||||
$sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']);
|
||||
|
||||
$tableAlias = isset($owningAssoc['inherited']) ?
|
||||
$this->_getSQLTableAlias($owningAssoc['inherited'])
|
||||
: $this->_getSQLTableAlias($this->_class->name);
|
||||
$tableAlias = $this->_getSQLTableAlias(isset($owningAssoc['inherited']) ? $owningAssoc['inherited'] : $this->_class->name);
|
||||
|
||||
foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) {
|
||||
if ($sourceClass->containsForeignIdentifier) {
|
||||
$field = $sourceClass->getFieldForColumn($sourceKeyColumn);
|
||||
$value = $sourceClass->reflFields[$field]->getValue($sourceEntity);
|
||||
|
||||
if (isset($sourceClass->associationMappings[$field])) {
|
||||
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
|
||||
$value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]];
|
||||
}
|
||||
|
||||
$criteria[$tableAlias . "." . $targetKeyColumn] = $value;
|
||||
} else {
|
||||
$criteria[$tableAlias . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
|
||||
@ -1300,18 +1385,96 @@ class BasicEntityPersister
|
||||
continue; // skip null values.
|
||||
}
|
||||
|
||||
$type = null;
|
||||
if (isset($this->_class->fieldMappings[$field])) {
|
||||
$types[] = $this->getType($field, $value);
|
||||
$params[] = $this->getValue($value);
|
||||
}
|
||||
|
||||
return array($params, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer field type to be used by parameter type casting.
|
||||
*
|
||||
* @param string $field
|
||||
* @param mixed $value
|
||||
* @return integer
|
||||
*/
|
||||
private function getType($field, $value)
|
||||
{
|
||||
switch (true) {
|
||||
case (isset($this->_class->fieldMappings[$field])):
|
||||
$type = Type::getType($this->_class->fieldMappings[$field]['type'])->getBindingType();
|
||||
}
|
||||
if (is_array($value)) {
|
||||
$type += Connection::ARRAY_PARAM_OFFSET;
|
||||
break;
|
||||
|
||||
case (isset($this->_class->associationMappings[$field])):
|
||||
$assoc = $this->_class->associationMappings[$field];
|
||||
|
||||
if (count($assoc['sourceToTargetKeyColumns']) > 1) {
|
||||
throw Query\QueryException::associationPathCompositeKeyNotSupported();
|
||||
}
|
||||
|
||||
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
|
||||
$targetColumn = $assoc['joinColumns'][0]['referencedColumnName'];
|
||||
$type = null;
|
||||
|
||||
if (isset($targetClass->fieldNames[$targetColumn])) {
|
||||
$type = Type::getType($targetClass->fieldMappings[$targetClass->fieldNames[$targetColumn]]['type'])->getBindingType();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$type = null;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$type += Connection::ARRAY_PARAM_OFFSET;
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve parameter value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
private function getValue($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$newValue = array();
|
||||
|
||||
foreach ($value as $itemValue) {
|
||||
$newValue[] = $this->getIndividualValue($itemValue);
|
||||
}
|
||||
|
||||
$params[] = $value;
|
||||
$types[] = $type;
|
||||
return $newValue;
|
||||
}
|
||||
return array($params, $types);
|
||||
|
||||
return $this->getIndividualValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an invidiual parameter value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
private function getIndividualValue($value)
|
||||
{
|
||||
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
|
||||
if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
|
||||
$idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
|
||||
} else {
|
||||
$class = $this->_em->getClassMetadata(get_class($value));
|
||||
$idValues = $class->getIdentifierValues($value);
|
||||
}
|
||||
|
||||
$value = $idValues[key($idValues)];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1323,14 +1486,17 @@ class BasicEntityPersister
|
||||
public function exists($entity, array $extraConditions = array())
|
||||
{
|
||||
$criteria = $this->_class->getIdentifierValues($entity);
|
||||
|
||||
if ($extraConditions) {
|
||||
$criteria = array_merge($criteria, $extraConditions);
|
||||
}
|
||||
|
||||
$sql = 'SELECT 1 FROM ' . $this->_class->getQuotedTableName($this->_platform)
|
||||
. ' ' . $this->_getSQLTableAlias($this->_class->name)
|
||||
. ' WHERE ' . $this->_getSelectConditionSQL($criteria);
|
||||
|
||||
return (bool) $this->_conn->fetchColumn($sql, array_values($criteria));
|
||||
$sql = 'SELECT 1'
|
||||
. ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($this->_class->name)
|
||||
. ' WHERE ' . $this->_getSelectConditionSQL($criteria);
|
||||
|
||||
list($params, $types) = $this->expandParameters($criteria);
|
||||
|
||||
return (bool) $this->_conn->fetchColumn($sql, $params);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ namespace Doctrine\ORM\Persisters;
|
||||
use Doctrine\ORM\ORMException,
|
||||
Doctrine\ORM\Mapping\ClassMetadata,
|
||||
Doctrine\DBAL\LockMode,
|
||||
Doctrine\DBAL\Types\Type,
|
||||
Doctrine\ORM\Query\ResultSetMapping;
|
||||
|
||||
/**
|
||||
@ -55,11 +56,11 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
*/
|
||||
protected function _getDiscriminatorColumnTableName()
|
||||
{
|
||||
if ($this->_class->name == $this->_class->rootEntityName) {
|
||||
return $this->_class->table['name'];
|
||||
} else {
|
||||
return $this->_em->getClassMetadata($this->_class->rootEntityName)->table['name'];
|
||||
}
|
||||
$class = ($this->_class->name !== $this->_class->rootEntityName)
|
||||
? $this->_em->getClassMetadata($this->_class->rootEntityName)
|
||||
: $this->_class;
|
||||
|
||||
return $class->getTableName();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,8 +73,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
{
|
||||
if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) {
|
||||
$definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited'];
|
||||
|
||||
return $this->_em->getClassMetadata($definingClassName);
|
||||
}
|
||||
|
||||
return $this->_class;
|
||||
}
|
||||
|
||||
@ -86,19 +89,24 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
*/
|
||||
public function getOwningTable($fieldName)
|
||||
{
|
||||
if (!isset($this->_owningTableMap[$fieldName])) {
|
||||
if (isset($this->_class->associationMappings[$fieldName]['inherited'])) {
|
||||
$cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']);
|
||||
} else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) {
|
||||
$cm = $this->_em->getClassMetadata($this->_class->fieldMappings[$fieldName]['inherited']);
|
||||
} else {
|
||||
$cm = $this->_class;
|
||||
}
|
||||
$this->_owningTableMap[$fieldName] = $cm->table['name'];
|
||||
$this->_quotedTableMap[$cm->table['name']] = $cm->getQuotedTableName($this->_platform);
|
||||
if (isset($this->_owningTableMap[$fieldName])) {
|
||||
return $this->_owningTableMap[$fieldName];
|
||||
}
|
||||
|
||||
if (isset($this->_class->associationMappings[$fieldName]['inherited'])) {
|
||||
$cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']);
|
||||
} else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) {
|
||||
$cm = $this->_em->getClassMetadata($this->_class->fieldMappings[$fieldName]['inherited']);
|
||||
} else {
|
||||
$cm = $this->_class;
|
||||
}
|
||||
|
||||
return $this->_owningTableMap[$fieldName];
|
||||
$tableName = $cm->getTableName();
|
||||
|
||||
$this->_owningTableMap[$fieldName] = $tableName;
|
||||
$this->_quotedTableMap[$tableName] = $cm->getQuotedTableName($this->_platform);
|
||||
|
||||
return $tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,20 +123,22 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
$isPostInsertId = $idGen->isPostInsertGenerator();
|
||||
|
||||
// Prepare statement for the root table
|
||||
$rootClass = $this->_class->name == $this->_class->rootEntityName ?
|
||||
$this->_class : $this->_em->getClassMetadata($this->_class->rootEntityName);
|
||||
$rootClass = ($this->_class->name !== $this->_class->rootEntityName) ? $this->_em->getClassMetadata($this->_class->rootEntityName) : $this->_class;
|
||||
$rootPersister = $this->_em->getUnitOfWork()->getEntityPersister($rootClass->name);
|
||||
$rootTableName = $rootClass->table['name'];
|
||||
$rootTableName = $rootClass->getTableName();
|
||||
$rootTableStmt = $this->_conn->prepare($rootPersister->_getInsertSQL());
|
||||
|
||||
// Prepare statements for sub tables.
|
||||
$subTableStmts = array();
|
||||
|
||||
if ($rootClass !== $this->_class) {
|
||||
$subTableStmts[$this->_class->table['name']] = $this->_conn->prepare($this->_getInsertSQL());
|
||||
$subTableStmts[$this->_class->getTableName()] = $this->_conn->prepare($this->_getInsertSQL());
|
||||
}
|
||||
|
||||
foreach ($this->_class->parentClasses as $parentClassName) {
|
||||
$parentClass = $this->_em->getClassMetadata($parentClassName);
|
||||
$parentTableName = $parentClass->table['name'];
|
||||
$parentTableName = $parentClass->getTableName();
|
||||
|
||||
if ($parentClass !== $rootClass) {
|
||||
$parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName);
|
||||
$subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL());
|
||||
@ -143,9 +153,11 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
// Execute insert on root table
|
||||
$paramIndex = 1;
|
||||
|
||||
foreach ($insertData[$rootTableName] as $columnName => $value) {
|
||||
$rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
|
||||
}
|
||||
|
||||
$rootTableStmt->execute();
|
||||
|
||||
if ($isPostInsertId) {
|
||||
@ -160,17 +172,23 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
foreach ($subTableStmts as $tableName => $stmt) {
|
||||
$data = isset($insertData[$tableName]) ? $insertData[$tableName] : array();
|
||||
$paramIndex = 1;
|
||||
foreach ((array) $id as $idVal) {
|
||||
$stmt->bindValue($paramIndex++, $idVal);
|
||||
|
||||
foreach ((array) $id as $idName => $idVal) {
|
||||
$type = isset($this->_columnTypes[$idName]) ? $this->_columnTypes[$idName] : Type::STRING;
|
||||
|
||||
$stmt->bindValue($paramIndex++, $idVal, $type);
|
||||
}
|
||||
|
||||
foreach ($data as $columnName => $value) {
|
||||
$stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
||||
|
||||
$rootTableStmt->closeCursor();
|
||||
|
||||
foreach ($subTableStmts as $stmt) {
|
||||
$stmt->closeCursor();
|
||||
}
|
||||
@ -191,15 +209,18 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
{
|
||||
$updateData = $this->_prepareUpdateData($entity);
|
||||
|
||||
if ($isVersioned = $this->_class->isVersioned) {
|
||||
if (($isVersioned = $this->_class->isVersioned) != false) {
|
||||
$versionedClass = $this->_getVersionedClassMetadata();
|
||||
$versionedTable = $versionedClass->table['name'];
|
||||
$versionedTable = $versionedClass->getTableName();
|
||||
}
|
||||
|
||||
if ($updateData) {
|
||||
foreach ($updateData as $tableName => $data) {
|
||||
$this->_updateTable($entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName);
|
||||
$this->_updateTable(
|
||||
$entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure the table with the version column is updated even if no columns on that
|
||||
// table were affected.
|
||||
if ($isVersioned && ! isset($updateData[$versionedTable])) {
|
||||
@ -224,13 +245,17 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
// If the database platform supports FKs, just
|
||||
// delete the row from the root table. Cascades do the rest.
|
||||
if ($this->_platform->supportsForeignKeyConstraints()) {
|
||||
$this->_conn->delete($this->_em->getClassMetadata($this->_class->rootEntityName)
|
||||
->getQuotedTableName($this->_platform), $id);
|
||||
$this->_conn->delete(
|
||||
$this->_em->getClassMetadata($this->_class->rootEntityName)->getQuotedTableName($this->_platform), $id
|
||||
);
|
||||
} else {
|
||||
// Delete from all tables individually, starting from this class' table up to the root table.
|
||||
$this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id);
|
||||
|
||||
foreach ($this->_class->parentClasses as $parentClass) {
|
||||
$this->_conn->delete($this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id);
|
||||
$this->_conn->delete(
|
||||
$this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -251,23 +276,27 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
// Add regular columns
|
||||
$columnList = '';
|
||||
|
||||
foreach ($this->_class->fieldMappings as $fieldName => $mapping) {
|
||||
if ($columnList != '') $columnList .= ', ';
|
||||
$columnList .= $this->_getSelectColumnSQL($fieldName,
|
||||
isset($mapping['inherited']) ?
|
||||
$this->_em->getClassMetadata($mapping['inherited']) :
|
||||
$this->_class);
|
||||
|
||||
$columnList .= $this->_getSelectColumnSQL(
|
||||
$fieldName,
|
||||
isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class
|
||||
);
|
||||
}
|
||||
|
||||
// Add foreign key columns
|
||||
foreach ($this->_class->associationMappings as $assoc2) {
|
||||
if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE) {
|
||||
$tableAlias = isset($assoc2['inherited']) ?
|
||||
$this->_getSQLTableAlias($assoc2['inherited'])
|
||||
: $baseTableAlias;
|
||||
$tableAlias = isset($assoc2['inherited']) ? $this->_getSQLTableAlias($assoc2['inherited']) : $baseTableAlias;
|
||||
|
||||
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
if ($columnList != '') $columnList .= ', ';
|
||||
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
|
||||
|
||||
$columnList .= $this->getSelectJoinColumnSQL(
|
||||
$tableAlias,
|
||||
$srcColumn,
|
||||
isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name
|
||||
);
|
||||
}
|
||||
@ -276,27 +305,27 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
// Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#_processSQLResult).
|
||||
$discrColumn = $this->_class->discriminatorColumn['name'];
|
||||
if ($this->_class->rootEntityName == $this->_class->name) {
|
||||
$columnList .= ", $baseTableAlias.$discrColumn";
|
||||
} else {
|
||||
$columnList .= ', ' . $this->_getSQLTableAlias($this->_class->rootEntityName)
|
||||
. ".$discrColumn";
|
||||
}
|
||||
$tableAlias = ($this->_class->rootEntityName == $this->_class->name) ? $baseTableAlias : $this->_getSQLTableAlias($this->_class->rootEntityName);
|
||||
$columnList .= ', ' . $tableAlias . '.' . $discrColumn;
|
||||
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
|
||||
$this->_rsm->setDiscriminatorColumn('r', $discrColumn);
|
||||
|
||||
$this->_rsm->setDiscriminatorColumn('r', $resultColumnName);
|
||||
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
|
||||
}
|
||||
|
||||
// INNER JOIN parent tables
|
||||
$joinSql = '';
|
||||
|
||||
foreach ($this->_class->parentClasses as $parentClassName) {
|
||||
$parentClass = $this->_em->getClassMetadata($parentClassName);
|
||||
$tableAlias = $this->_getSQLTableAlias($parentClassName);
|
||||
$joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
|
||||
$first = true;
|
||||
|
||||
foreach ($idColumns as $idColumn) {
|
||||
if ($first) $first = false; else $joinSql .= ' AND ';
|
||||
|
||||
$joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
|
||||
}
|
||||
}
|
||||
@ -309,19 +338,20 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
if ($this->_selectColumnListSql === null) {
|
||||
// Add subclass columns
|
||||
foreach ($subClass->fieldMappings as $fieldName => $mapping) {
|
||||
if (isset($mapping['inherited'])) {
|
||||
continue;
|
||||
}
|
||||
if (isset($mapping['inherited'])) continue;
|
||||
|
||||
$columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass);
|
||||
}
|
||||
|
||||
// Add join columns (foreign keys)
|
||||
foreach ($subClass->associationMappings as $assoc2) {
|
||||
if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE
|
||||
&& ! isset($assoc2['inherited'])) {
|
||||
if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE && ! isset($assoc2['inherited'])) {
|
||||
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
if ($columnList != '') $columnList .= ', ';
|
||||
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
|
||||
|
||||
$columnList .= $this->getSelectJoinColumnSQL(
|
||||
$tableAlias,
|
||||
$srcColumn,
|
||||
isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name
|
||||
);
|
||||
}
|
||||
@ -332,14 +362,15 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
// Add LEFT JOIN
|
||||
$joinSql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
|
||||
$first = true;
|
||||
|
||||
foreach ($idColumns as $idColumn) {
|
||||
if ($first) $first = false; else $joinSql .= ' AND ';
|
||||
|
||||
$joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
|
||||
}
|
||||
}
|
||||
|
||||
$joinSql .= $assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY ?
|
||||
$this->_getSelectManyToManyJoinSQL($assoc) : '';
|
||||
$joinSql .= ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) ? $this->_getSelectManyToManyJoinSQL($assoc) : '';
|
||||
|
||||
$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
|
||||
|
||||
@ -351,6 +382,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
}
|
||||
|
||||
$lockSql = '';
|
||||
|
||||
if ($lockMode == LockMode::PESSIMISTIC_READ) {
|
||||
$lockSql = ' ' . $this->_platform->getReadLockSql();
|
||||
} else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
|
||||
@ -376,13 +408,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
// INNER JOIN parent tables
|
||||
$joinSql = '';
|
||||
|
||||
foreach ($this->_class->parentClasses as $parentClassName) {
|
||||
$parentClass = $this->_em->getClassMetadata($parentClassName);
|
||||
$tableAlias = $this->_getSQLTableAlias($parentClassName);
|
||||
$joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
|
||||
$first = true;
|
||||
|
||||
foreach ($idColumns as $idColumn) {
|
||||
if ($first) $first = false; else $joinSql .= ' AND ';
|
||||
|
||||
$joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
|
||||
}
|
||||
}
|
||||
|
@ -40,9 +40,10 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
protected function _getDeleteRowSQL(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$columns = $mapping['joinTableColumns'];
|
||||
return 'DELETE FROM ' . $joinTable['name'] . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
|
||||
$class = $this->_em->getClassMetadata(get_class($coll->getOwner()));
|
||||
|
||||
return 'DELETE FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
|
||||
. ' WHERE ' . implode(' = ? AND ', $mapping['joinTableColumns']) . ' = ?';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,10 +76,11 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
protected function _getInsertRowSQL(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$columns = $mapping['joinTableColumns'];
|
||||
return 'INSERT INTO ' . $joinTable['name'] . ' (' . implode(', ', $columns) . ')'
|
||||
. ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
|
||||
$class = $this->_em->getClassMetadata(get_class($coll->getOwner()));
|
||||
|
||||
return 'INSERT INTO ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
|
||||
. ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,8 +105,8 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
*/
|
||||
private function _collectJoinTableColumnParameters(PersistentCollection $coll, $element)
|
||||
{
|
||||
$params = array();
|
||||
$mapping = $coll->getMapping();
|
||||
$params = array();
|
||||
$mapping = $coll->getMapping();
|
||||
$isComposite = count($mapping['joinTableColumns']) > 2;
|
||||
|
||||
$identifier1 = $this->_uow->getEntityIdentifier($coll->getOwner());
|
||||
@ -149,14 +151,19 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
*/
|
||||
protected function _getDeleteSQL(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$mapping = $coll->getMapping();
|
||||
$class = $this->_em->getClassMetadata(get_class($coll->getOwner()));
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$whereClause = '';
|
||||
|
||||
foreach ($mapping['relationToSourceKeyColumns'] as $relationColumn => $srcColumn) {
|
||||
if ($whereClause !== '') $whereClause .= ' AND ';
|
||||
$whereClause .= "$relationColumn = ?";
|
||||
|
||||
$whereClause .= $relationColumn . ' = ?';
|
||||
}
|
||||
return 'DELETE FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
|
||||
|
||||
return 'DELETE FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
|
||||
. ' WHERE ' . $whereClause;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,8 +178,10 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
$params = array();
|
||||
$mapping = $coll->getMapping();
|
||||
$identifier = $this->_uow->getEntityIdentifier($coll->getOwner());
|
||||
|
||||
if (count($mapping['relationToSourceKeyColumns']) > 1) {
|
||||
$sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner()));
|
||||
|
||||
foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) {
|
||||
$params[] = $identifier[$sourceClass->fieldNames[$srcColumn]];
|
||||
}
|
||||
@ -188,36 +197,37 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
*/
|
||||
public function count(PersistentCollection $coll)
|
||||
{
|
||||
$params = array();
|
||||
$params = array();
|
||||
$mapping = $coll->getMapping();
|
||||
$class = $this->_em->getClassMetadata($mapping['sourceEntity']);
|
||||
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
|
||||
$class = $this->_em->getClassMetadata($mapping['sourceEntity']);
|
||||
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
|
||||
|
||||
if ($mapping['isOwningSide']) {
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$joinColumns = $mapping['relationToSourceKeyColumns'];
|
||||
} else {
|
||||
$mapping = $this->_em->getClassMetadata($mapping['targetEntity'])->associationMappings[$mapping['mappedBy']];
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$joinColumns = $mapping['relationToTargetKeyColumns'];
|
||||
}
|
||||
|
||||
$whereClause = '';
|
||||
|
||||
foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
|
||||
if (isset($joinColumns[$joinTableColumn])) {
|
||||
if ($whereClause !== '') {
|
||||
$whereClause .= ' AND ';
|
||||
}
|
||||
|
||||
$whereClause .= "$joinTableColumn = ?";
|
||||
|
||||
if ($class->containsForeignIdentifier) {
|
||||
$params[] = $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])];
|
||||
} else {
|
||||
$params[] = $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
|
||||
}
|
||||
$params[] = ($class->containsForeignIdentifier)
|
||||
? $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])]
|
||||
: $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
|
||||
}
|
||||
}
|
||||
$sql = 'SELECT count(*) FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
|
||||
|
||||
$sql = 'SELECT COUNT(*)'
|
||||
. ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
|
||||
. ' WHERE ' . $whereClause;
|
||||
|
||||
return $this->_conn->fetchColumn($sql, $params);
|
||||
}
|
||||
@ -231,9 +241,8 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
public function slice(PersistentCollection $coll, $offset, $length = null)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
return $this->_em->getUnitOfWork()
|
||||
->getEntityPersister($mapping['targetEntity'])
|
||||
->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length);
|
||||
|
||||
return $this->_em->getUnitOfWork()->getEntityPersister($mapping['targetEntity'])->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -252,7 +261,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
$params = array();
|
||||
$mapping = $coll->getMapping();
|
||||
|
||||
if (!$mapping['isOwningSide']) {
|
||||
if ( ! $mapping['isOwningSide']) {
|
||||
$sourceClass = $this->_em->getClassMetadata($mapping['targetEntity']);
|
||||
$targetClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
|
||||
$sourceId = $uow->getEntityIdentifier($element);
|
||||
@ -265,36 +274,37 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
$sourceId = $uow->getEntityIdentifier($coll->getOwner());
|
||||
$targetId = $uow->getEntityIdentifier($element);
|
||||
}
|
||||
$joinTable = $mapping['joinTable'];
|
||||
|
||||
|
||||
$whereClause = '';
|
||||
|
||||
foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
|
||||
if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
|
||||
if ($whereClause !== '') {
|
||||
$whereClause .= ' AND ';
|
||||
}
|
||||
$whereClause .= "$joinTableColumn = ?";
|
||||
|
||||
$whereClause .= $joinTableColumn . ' = ?';
|
||||
|
||||
if ($targetClass->containsForeignIdentifier) {
|
||||
$params[] = $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
|
||||
} else {
|
||||
$params[] = $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
|
||||
}
|
||||
$params[] = ($targetClass->containsForeignIdentifier)
|
||||
? $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]
|
||||
: $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
|
||||
} else if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) {
|
||||
if ($whereClause !== '') {
|
||||
$whereClause .= ' AND ';
|
||||
}
|
||||
$whereClause .= "$joinTableColumn = ?";
|
||||
|
||||
$whereClause .= $joinTableColumn . ' = ?';
|
||||
|
||||
if ($sourceClass->containsForeignIdentifier) {
|
||||
$params[] = $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
|
||||
} else {
|
||||
$params[] = $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
|
||||
}
|
||||
$params[] = ($sourceClass->containsForeignIdentifier)
|
||||
? $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]
|
||||
: $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
|
||||
}
|
||||
}
|
||||
$sql = 'SELECT 1 FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
|
||||
|
||||
$sql = 'SELECT 1'
|
||||
. ' FROM ' . $sourceClass->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
|
||||
. ' WHERE ' . $whereClause;
|
||||
|
||||
return (bool)$this->_conn->fetchColumn($sql, $params);
|
||||
return (bool) $this->_conn->fetchColumn($sql, $params);
|
||||
}
|
||||
}
|
@ -124,24 +124,26 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
public function count(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$class = $this->_em->getClassMetadata($mapping['targetEntity']);
|
||||
$targetClass = $this->_em->getClassMetadata($mapping['targetEntity']);
|
||||
$sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
|
||||
|
||||
$params = array();
|
||||
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
|
||||
|
||||
$where = '';
|
||||
foreach ($class->associationMappings[$mapping['mappedBy']]['joinColumns'] AS $joinColumn) {
|
||||
foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] AS $joinColumn) {
|
||||
if ($where != '') {
|
||||
$where .= ' AND ';
|
||||
}
|
||||
$where .= $joinColumn['name'] . " = ?";
|
||||
if ($class->containsForeignIdentifier) {
|
||||
$params[] = $id[$class->getFieldForColumn($joinColumn['referencedColumnName'])];
|
||||
if ($targetClass->containsForeignIdentifier) {
|
||||
$params[] = $id[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])];
|
||||
} else {
|
||||
$params[] = $id[$class->fieldNames[$joinColumn['referencedColumnName']]];
|
||||
$params[] = $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]];
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "SELECT count(*) FROM " . $class->getQuotedTableName($this->_conn->getDatabasePlatform()) . " WHERE " . $where;
|
||||
$sql = "SELECT count(*) FROM " . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . " WHERE " . $where;
|
||||
return $this->_conn->fetchColumn($sql, $params);
|
||||
}
|
||||
|
||||
@ -180,4 +182,4 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
return $uow->getEntityPersister($mapping['targetEntity'])
|
||||
->exists($element, array($mapping['mappedBy'] => $id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,38 +35,49 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
|
||||
/** {@inheritdoc} */
|
||||
protected function _getDiscriminatorColumnTableName()
|
||||
{
|
||||
return $this->_class->table['name'];
|
||||
return $this->_class->getTableName();
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
protected function _getSelectColumnListSQL()
|
||||
{
|
||||
if ($this->_selectColumnListSql !== null) {
|
||||
return $this->_selectColumnListSql;
|
||||
}
|
||||
|
||||
$columnList = parent::_getSelectColumnListSQL();
|
||||
|
||||
// Append discriminator column
|
||||
$discrColumn = $this->_class->discriminatorColumn['name'];
|
||||
$columnList .= ", $discrColumn";
|
||||
$rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName);
|
||||
$columnList .= ', ' . $discrColumn;
|
||||
|
||||
$rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName);
|
||||
$tableAlias = $this->_getSQLTableAlias($rootClass->name);
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
|
||||
$this->_rsm->setDiscriminatorColumn('r', $discrColumn);
|
||||
|
||||
$this->_rsm->setDiscriminatorColumn('r', $resultColumnName);
|
||||
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
|
||||
|
||||
// Append subclass columns
|
||||
foreach ($this->_class->subClasses as $subClassName) {
|
||||
$subClass = $this->_em->getClassMetadata($subClassName);
|
||||
|
||||
// Regular columns
|
||||
foreach ($subClass->fieldMappings as $fieldName => $mapping) {
|
||||
if ( ! isset($mapping['inherited'])) {
|
||||
$columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass);
|
||||
}
|
||||
}
|
||||
|
||||
// Foreign key columns
|
||||
foreach ($subClass->associationMappings as $assoc) {
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
if ($columnList != '') $columnList .= ', ';
|
||||
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
|
||||
|
||||
$columnList .= $this->getSelectJoinColumnSQL(
|
||||
$tableAlias,
|
||||
$srcColumn,
|
||||
isset($assoc['inherited']) ? $assoc['inherited'] : $this->_class->name
|
||||
);
|
||||
}
|
||||
@ -74,13 +85,15 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
|
||||
}
|
||||
}
|
||||
|
||||
return $columnList;
|
||||
$this->_selectColumnListSql = $columnList;
|
||||
return $this->_selectColumnListSql;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
protected function _getInsertColumnList()
|
||||
{
|
||||
$columns = parent::_getInsertColumnList();
|
||||
|
||||
// Add discriminator column to the INSERT SQL
|
||||
$columns[] = $this->_class->discriminatorColumn['name'];
|
||||
|
||||
@ -100,18 +113,21 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
|
||||
|
||||
// Append discriminator condition
|
||||
if ($conditionSql) $conditionSql .= ' AND ';
|
||||
|
||||
$values = array();
|
||||
|
||||
if ($this->_class->discriminatorValue !== null) { // discriminators can be 0
|
||||
$values[] = $this->_conn->quote($this->_class->discriminatorValue);
|
||||
}
|
||||
|
||||
$discrValues = array_flip($this->_class->discriminatorMap);
|
||||
|
||||
foreach ($this->_class->subClasses as $subclassName) {
|
||||
$values[] = $this->_conn->quote($discrValues[$subclassName]);
|
||||
}
|
||||
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'
|
||||
. $this->_class->discriminatorColumn['name']
|
||||
. ' IN (' . implode(', ', $values) . ')';
|
||||
|
||||
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.' . $this->_class->discriminatorColumn['name']
|
||||
. ' IN (' . implode(', ', $values) . ')';
|
||||
|
||||
return $conditionSql;
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ class ProxyFactory
|
||||
}
|
||||
|
||||
if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
|
||||
$methods .= PHP_EOL . ' public function ';
|
||||
$methods .= "\n" . ' public function ';
|
||||
if ($method->returnsReference()) {
|
||||
$methods .= '&';
|
||||
}
|
||||
@ -208,15 +208,15 @@ class ProxyFactory
|
||||
}
|
||||
|
||||
$methods .= $parameterString . ')';
|
||||
$methods .= PHP_EOL . ' {' . PHP_EOL;
|
||||
$methods .= "\n" . ' {' . "\n";
|
||||
if ($this->isShortIdentifierGetter($method, $class)) {
|
||||
$methods .= ' if ($this->__isInitialized__ === false) {' . PHP_EOL;
|
||||
$methods .= ' return $this->_identifier["' . lcfirst(substr($method->getName(), 3)) . '"];' . PHP_EOL;
|
||||
$methods .= ' }' . PHP_EOL;
|
||||
$methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
|
||||
$methods .= ' return $this->_identifier["' . lcfirst(substr($method->getName(), 3)) . '"];' . "\n";
|
||||
$methods .= ' }' . "\n";
|
||||
}
|
||||
$methods .= ' $this->__load();' . PHP_EOL;
|
||||
$methods .= ' $this->__load();' . "\n";
|
||||
$methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
|
||||
$methods .= PHP_EOL . ' }' . PHP_EOL;
|
||||
$methods .= "\n" . ' }' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,17 +289,26 @@ class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
|
||||
$this->_entityPersister = $entityPersister;
|
||||
$this->_identifier = $identifier;
|
||||
}
|
||||
private function __load()
|
||||
/** @private */
|
||||
public function __load()
|
||||
{
|
||||
if (!$this->__isInitialized__ && $this->_entityPersister) {
|
||||
$this->__isInitialized__ = true;
|
||||
|
||||
if (method_exists($this, "__wakeup")) {
|
||||
// call this after __isInitialized__to avoid infinite recursion
|
||||
// but before loading to emulate what ClassMetadata::newInstance()
|
||||
// provides.
|
||||
$this->__wakeup();
|
||||
}
|
||||
|
||||
if ($this->_entityPersister->load($this->_identifier, $this) === null) {
|
||||
throw new \Doctrine\ORM\EntityNotFoundException();
|
||||
}
|
||||
unset($this->_entityPersister, $this->_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<methods>
|
||||
|
||||
public function __sleep()
|
||||
|
@ -53,6 +53,15 @@ final class Query extends AbstractQuery
|
||||
* @var string
|
||||
*/
|
||||
const HINT_REFRESH = 'doctrine.refresh';
|
||||
|
||||
|
||||
/**
|
||||
* Internal hint: is set to the proxy entity that is currently triggered for loading
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
|
||||
|
||||
/**
|
||||
* The forcePartialLoad query hint forces a particular query to return
|
||||
* partial objects.
|
||||
@ -195,8 +204,9 @@ final class Query extends AbstractQuery
|
||||
|
||||
// Check query cache.
|
||||
if ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver())) {
|
||||
$hash = $this->_getQueryCacheId();
|
||||
$hash = $this->_getQueryCacheId();
|
||||
$cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
|
||||
|
||||
if ($cached === false) {
|
||||
// Cache miss.
|
||||
$parser = new Parser($this);
|
||||
@ -210,6 +220,7 @@ final class Query extends AbstractQuery
|
||||
$parser = new Parser($this);
|
||||
$this->_parserResult = $parser->parse();
|
||||
}
|
||||
|
||||
$this->_state = self::STATE_CLEAN;
|
||||
|
||||
return $this->_parserResult;
|
||||
@ -229,50 +240,85 @@ final class Query extends AbstractQuery
|
||||
throw QueryException::invalidParameterNumber();
|
||||
}
|
||||
|
||||
$sqlParams = $types = array();
|
||||
|
||||
foreach ($this->_params as $key => $value) {
|
||||
if ( ! isset($paramMappings[$key])) {
|
||||
throw QueryException::unknownParameter($key);
|
||||
}
|
||||
if (isset($this->_paramTypes[$key])) {
|
||||
foreach ($paramMappings[$key] as $position) {
|
||||
$types[$position] = $this->_paramTypes[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
|
||||
if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) {
|
||||
$idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
|
||||
} else {
|
||||
$class = $this->_em->getClassMetadata(get_class($value));
|
||||
$idValues = $class->getIdentifierValues($value);
|
||||
}
|
||||
$sqlPositions = $paramMappings[$key];
|
||||
$cSqlPos = count($sqlPositions);
|
||||
$cIdValues = count($idValues);
|
||||
$idValues = array_values($idValues);
|
||||
for ($i = 0; $i < $cSqlPos; $i++) {
|
||||
$sqlParams[$sqlPositions[$i]] = $idValues[ ($i % $cIdValues) ];
|
||||
}
|
||||
} else {
|
||||
foreach ($paramMappings[$key] as $position) {
|
||||
$sqlParams[$position] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($sqlParams) {
|
||||
ksort($sqlParams);
|
||||
$sqlParams = array_values($sqlParams);
|
||||
}
|
||||
|
||||
list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
|
||||
|
||||
if ($this->_resultSetMapping === null) {
|
||||
$this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
|
||||
}
|
||||
|
||||
return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes query parameter mappings
|
||||
*
|
||||
* @param array $paramMappings
|
||||
* @return array
|
||||
*/
|
||||
private function processParameterMappings($paramMappings)
|
||||
{
|
||||
$sqlParams = $types = array();
|
||||
|
||||
foreach ($this->_params as $key => $value) {
|
||||
if ( ! isset($paramMappings[$key])) {
|
||||
throw QueryException::unknownParameter($key);
|
||||
}
|
||||
|
||||
if (isset($this->_paramTypes[$key])) {
|
||||
foreach ($paramMappings[$key] as $position) {
|
||||
$types[$position] = $this->_paramTypes[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$sqlPositions = $paramMappings[$key];
|
||||
$value = array_values($this->processParameterValue($value));
|
||||
$countValue = count($value);
|
||||
|
||||
for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
|
||||
$sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
|
||||
}
|
||||
}
|
||||
|
||||
if ($sqlParams) {
|
||||
ksort($sqlParams);
|
||||
$sqlParams = array_values($sqlParams);
|
||||
}
|
||||
|
||||
return array($sqlParams, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an individual parameter value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
private function processParameterValue($value)
|
||||
{
|
||||
switch (true) {
|
||||
case is_array($value):
|
||||
for ($i = 0, $l = count($value); $i < $l; $i++) {
|
||||
$paramValue = $this->processParameterValue($value[$i]);
|
||||
|
||||
// TODO: What about Entities that have composite primary key?
|
||||
$value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
|
||||
}
|
||||
|
||||
return array($value);
|
||||
|
||||
case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)):
|
||||
if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
|
||||
return array_values($this->_em->getUnitOfWork()->getEntityIdentifier($value));
|
||||
}
|
||||
|
||||
$class = $this->_em->getClassMetadata(get_class($value));
|
||||
|
||||
return array_values($class->getIdentifierValues($value));
|
||||
|
||||
default:
|
||||
return array($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a cache driver to be used for caching queries.
|
||||
|
47
lib/Doctrine/ORM/Query/AST/CoalesceExpression.php
Normal file
47
lib/Doctrine/ORM/Query/AST/CoalesceExpression.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
/**
|
||||
* CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
|
||||
*
|
||||
* @since 2.1
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @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 CoalesceExpression extends Node
|
||||
{
|
||||
public $scalarExpressions = array();
|
||||
|
||||
|
||||
public function __construct(array $scalarExpressions)
|
||||
{
|
||||
$this->scalarExpressions = $scalarExpressions;
|
||||
}
|
||||
|
||||
public function dispatch($sqlWalker)
|
||||
{
|
||||
return $sqlWalker->walkCoalesceExpression($this);
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* 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
|
||||
@ -27,7 +25,6 @@ namespace Doctrine\ORM\Query\AST;
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @version $Revision: 3938 $
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
|
68
lib/Doctrine/ORM/Query/AST/Functions/IdentityFunction.php
Normal file
68
lib/Doctrine/ORM/Query/AST/Functions/IdentityFunction.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST\Functions;
|
||||
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
|
||||
/**
|
||||
* "IDENTITY" "(" SingleValuedAssociationPathExpression ")"
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.2
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class IdentityFunction extends FunctionNode
|
||||
{
|
||||
public $pathExpression;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
{
|
||||
$platform = $sqlWalker->getConnection()->getDatabasePlatform();
|
||||
$dqlAlias = $this->pathExpression->identificationVariable;
|
||||
$assocField = $this->pathExpression->field;
|
||||
|
||||
$qComp = $sqlWalker->getQueryComponent($dqlAlias);
|
||||
$class = $qComp['metadata'];
|
||||
$assoc = $class->associationMappings[$assocField];
|
||||
|
||||
$tableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
|
||||
return $tableAlias . '.' . reset($assoc['targetToSourceKeyColumns']);;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->pathExpression = $parser->SingleValuedAssociationPathExpression();
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
||||
|
@ -53,8 +53,8 @@ class SizeFunction extends FunctionNode
|
||||
|
||||
if ($assoc['type'] == \Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_MANY) {
|
||||
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
|
||||
$targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->table['name']);
|
||||
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
$targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->getTableName());
|
||||
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
|
||||
$sql .= $targetClass->getQuotedTableName($platform) . ' ' . $targetTableAlias . ' WHERE ';
|
||||
|
||||
@ -77,7 +77,7 @@ class SizeFunction extends FunctionNode
|
||||
|
||||
// SQL table aliases
|
||||
$joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable['name']);
|
||||
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
|
||||
// join to target table
|
||||
$sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $platform) . ' ' . $joinTableAlias . ' WHERE ';
|
||||
|
48
lib/Doctrine/ORM/Query/AST/GeneralCaseExpression.php
Normal file
48
lib/Doctrine/ORM/Query/AST/GeneralCaseExpression.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
/**
|
||||
* GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
|
||||
*
|
||||
* @since 2.2
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @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 GeneralCaseExpression extends Node
|
||||
{
|
||||
public $whenClauses = array();
|
||||
public $elseScalarExpression = null;
|
||||
|
||||
public function __construct(array $whenClauses, $elseScalarExpression)
|
||||
{
|
||||
$this->whenClauses = $whenClauses;
|
||||
$this->elseScalarExpression = $elseScalarExpression;
|
||||
}
|
||||
|
||||
public function dispatch($sqlWalker)
|
||||
{
|
||||
return $sqlWalker->walkGeneralCaseExpression($this);
|
||||
}
|
||||
}
|
@ -20,7 +20,8 @@
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
/**
|
||||
* InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (AbstractSchemaName | InputParameter)
|
||||
* InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
|
||||
* InstanceOfParameter ::= AbstractSchemaName | InputParameter
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
|
49
lib/Doctrine/ORM/Query/AST/NullIfExpression.php
Normal file
49
lib/Doctrine/ORM/Query/AST/NullIfExpression.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
/**
|
||||
* NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
|
||||
*
|
||||
* @since 2.1
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @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 NullIfExpression extends Node
|
||||
{
|
||||
public $firstExpression;
|
||||
|
||||
public $secondExpression;
|
||||
|
||||
public function __construct($firstExpression, $secondExpression)
|
||||
{
|
||||
$this->firstExpression = $firstExpression;
|
||||
$this->secondExpression = $secondExpression;
|
||||
}
|
||||
|
||||
public function dispatch($sqlWalker)
|
||||
{
|
||||
return $sqlWalker->walkNullIfExpression($this);
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
/**
|
||||
* SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression |
|
||||
* (AggregateExpression | "(" Subselect ")") [["AS"] FieldAliasIdentificationVariable]
|
||||
* (AggregateExpression | "(" Subselect ")") [["AS"] ["HIDDEN"] FieldAliasIdentificationVariable]
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
@ -37,11 +37,13 @@ class SelectExpression extends Node
|
||||
{
|
||||
public $expression;
|
||||
public $fieldIdentificationVariable;
|
||||
public $hiddenAliasResultVariable;
|
||||
|
||||
public function __construct($expression, $fieldIdentificationVariable)
|
||||
public function __construct($expression, $fieldIdentificationVariable, $hiddenAliasResultVariable = false)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
$this->fieldIdentificationVariable = $fieldIdentificationVariable;
|
||||
$this->hiddenAliasResultVariable = $hiddenAliasResultVariable;
|
||||
}
|
||||
|
||||
public function dispatch($sqlWalker)
|
||||
|
50
lib/Doctrine/ORM/Query/AST/SimpleCaseExpression.php
Normal file
50
lib/Doctrine/ORM/Query/AST/SimpleCaseExpression.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
/**
|
||||
* SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
|
||||
*
|
||||
* @since 2.2
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @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 SimpleCaseExpression extends Node
|
||||
{
|
||||
public $caseOperand = null;
|
||||
public $simpleWhenClauses = array();
|
||||
public $elseScalarExpression = null;
|
||||
|
||||
public function __construct($caseOperand, array $simpleWhenClauses, $elseScalarExpression)
|
||||
{
|
||||
$this->caseOperand = $caseOperand;
|
||||
$this->simpleWhenClauses = $simpleWhenClauses;
|
||||
$this->elseScalarExpression = $elseScalarExpression;
|
||||
}
|
||||
|
||||
public function dispatch($sqlWalker)
|
||||
{
|
||||
return $sqlWalker->walkSimpleCaseExpression($this);
|
||||
}
|
||||
}
|
48
lib/Doctrine/ORM/Query/AST/SimpleWhenClause.php
Normal file
48
lib/Doctrine/ORM/Query/AST/SimpleWhenClause.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
/**
|
||||
* SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
|
||||
*
|
||||
* @since 2.2
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @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 SimpleWhenClause extends Node
|
||||
{
|
||||
public $caseScalarExpression = null;
|
||||
public $thenScalarExpression = null;
|
||||
|
||||
public function __construct($caseScalarExpression, $thenScalarExpression)
|
||||
{
|
||||
$this->caseScalarExpression = $caseScalarExpression;
|
||||
$this->thenScalarExpression = $thenScalarExpression;
|
||||
}
|
||||
|
||||
public function dispatch($sqlWalker)
|
||||
{
|
||||
return $sqlWalker->walkWhenClauseExpression($this);
|
||||
}
|
||||
}
|
48
lib/Doctrine/ORM/Query/AST/WhenClause.php
Normal file
48
lib/Doctrine/ORM/Query/AST/WhenClause.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
/**
|
||||
* WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
|
||||
*
|
||||
* @since 2.2
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @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 WhenClause extends Node
|
||||
{
|
||||
public $caseConditionExpression = null;
|
||||
public $thenScalarExpression = null;
|
||||
|
||||
public function __construct($caseConditionExpression, $thenScalarExpression)
|
||||
{
|
||||
$this->caseConditionExpression = $caseConditionExpression;
|
||||
$this->thenScalarExpression = $thenScalarExpression;
|
||||
}
|
||||
|
||||
public function dispatch($sqlWalker)
|
||||
{
|
||||
return $sqlWalker->walkWhenClauseExpression($this);
|
||||
}
|
||||
}
|
@ -63,7 +63,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
|
||||
$idColumnList = implode(', ', $idColumnNames);
|
||||
|
||||
// 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
|
||||
$sqlWalker->setSQLTableAlias($primaryClass->table['name'], 't0', $primaryDqlAlias);
|
||||
$sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $primaryDqlAlias);
|
||||
|
||||
$this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
|
||||
. ' SELECT t0.' . implode(', t0.', $idColumnNames);
|
||||
@ -98,7 +98,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
|
||||
}
|
||||
$this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
|
||||
. $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
|
||||
$this->_dropTempTableSql = 'DROP TABLE ' . $tempTable;
|
||||
$this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,7 +64,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
$idColumnList = implode(', ', $idColumnNames);
|
||||
|
||||
// 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
|
||||
$sqlWalker->setSQLTableAlias($primaryClass->table['name'], 't0', $updateClause->aliasIdentificationVariable);
|
||||
$sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $updateClause->aliasIdentificationVariable);
|
||||
|
||||
$this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
|
||||
. ' SELECT t0.' . implode(', t0.', $idColumnNames);
|
||||
@ -106,7 +106,8 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
//FIXME (URGENT): With query cache the parameter is out of date. Move to execute() stage.
|
||||
if ($newValue instanceof AST\InputParameter) {
|
||||
$paramKey = $newValue->name;
|
||||
$this->_sqlParameters[$i][] = $sqlWalker->getQuery()->getParameter($paramKey);
|
||||
$this->_sqlParameters[$i]['parameters'][] = $sqlWalker->getQuery()->getParameter($paramKey);
|
||||
$this->_sqlParameters[$i]['types'][] = $sqlWalker->getQuery()->getParameterType($paramKey);
|
||||
|
||||
++$this->_numParametersInUpdateClause;
|
||||
}
|
||||
@ -136,7 +137,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
$this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
|
||||
. $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
|
||||
|
||||
$this->_dropTempTableSql = 'DROP TABLE ' . $tempTable;
|
||||
$this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,11 +155,23 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
$conn->executeUpdate($this->_createTempTableSql);
|
||||
|
||||
// Insert identifiers. Parameters from the update clause are cut off.
|
||||
$numUpdated = $conn->executeUpdate($this->_insertSql, array_slice($params, $this->_numParametersInUpdateClause), $types);
|
||||
$numUpdated = $conn->executeUpdate(
|
||||
$this->_insertSql,
|
||||
array_slice($params, $this->_numParametersInUpdateClause),
|
||||
array_slice($types, $this->_numParametersInUpdateClause)
|
||||
);
|
||||
|
||||
// Execute UPDATE statements
|
||||
for ($i=0, $count=count($this->_sqlStatements); $i<$count; ++$i) {
|
||||
$conn->executeUpdate($this->_sqlStatements[$i], isset($this->_sqlParameters[$i]) ? $this->_sqlParameters[$i] : array());
|
||||
$parameters = array();
|
||||
$types = array();
|
||||
|
||||
if (isset($this->_sqlParameters[$i])) {
|
||||
$parameters = isset($this->_sqlParameters[$i]['parameters']) ? $this->_sqlParameters[$i]['parameters'] : array();
|
||||
$types = isset($this->_sqlParameters[$i]['types']) ? $this->_sqlParameters[$i]['types'] : array();
|
||||
}
|
||||
|
||||
$conn->executeUpdate($this->_sqlStatements[$i], $parameters, $types);
|
||||
}
|
||||
|
||||
// Drop temporary table
|
||||
|
@ -39,5 +39,6 @@ class Andx extends Composite
|
||||
'Doctrine\ORM\Query\Expr\Comparison',
|
||||
'Doctrine\ORM\Query\Expr\Func',
|
||||
'Doctrine\ORM\Query\Expr\Orx',
|
||||
'Doctrine\ORM\Query\Expr\Andx',
|
||||
);
|
||||
}
|
@ -55,7 +55,7 @@ abstract class Base
|
||||
|
||||
public function add($arg)
|
||||
{
|
||||
if ( ! empty($arg) || ($arg instanceof self && $arg->count() > 0)) {
|
||||
if ( $arg !== null || ($arg instanceof self && $arg->count() > 0)) {
|
||||
// If we decide to keep Expr\Base instances, we can use this check
|
||||
if ( ! is_string($arg)) {
|
||||
$class = get_class($arg);
|
||||
|
@ -43,11 +43,26 @@ class Composite extends Base
|
||||
$components = array();
|
||||
|
||||
foreach ($this->_parts as $part) {
|
||||
$components[] = (is_object($part) && $part instanceof self && $part->count() > 1)
|
||||
? $this->_preSeparator . ((string) $part) . $this->_postSeparator
|
||||
: ((string) $part);
|
||||
$components[] = $this->processQueryPart($part);
|
||||
}
|
||||
|
||||
return implode($this->_separator, $components);
|
||||
}
|
||||
|
||||
|
||||
private function processQueryPart($part)
|
||||
{
|
||||
$queryPart = (string) $part;
|
||||
|
||||
if (is_object($part) && $part instanceof self && $part->count() > 1) {
|
||||
return $this->_preSeparator . $queryPart . $this->_postSeparator;
|
||||
}
|
||||
|
||||
// Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND")
|
||||
if (mb_stripos($queryPart, ' OR ') !== false || mb_stripos($queryPart, ' AND ') !== false) {
|
||||
return $this->_preSeparator . $queryPart . $this->_postSeparator;
|
||||
}
|
||||
|
||||
return $queryPart;
|
||||
}
|
||||
}
|
@ -34,27 +34,55 @@ namespace Doctrine\ORM\Query\Expr;
|
||||
*/
|
||||
class From
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $_from;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $_alias;
|
||||
|
||||
public function __construct($from, $alias)
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $_indexBy;
|
||||
|
||||
/**
|
||||
* @param string $from The class name.
|
||||
* @param string $alias The alias of the class.
|
||||
* @param string $indexBy The index for the from.
|
||||
*/
|
||||
public function __construct($from, $alias, $indexBy = null)
|
||||
{
|
||||
$this->_from = $from;
|
||||
$this->_alias = $alias;
|
||||
$this->_from = $from;
|
||||
$this->_alias = $alias;
|
||||
$this->_indexBy = $indexBy;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFrom()
|
||||
{
|
||||
return $this->_from;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAlias()
|
||||
{
|
||||
return $this->_alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->_from . ' ' . $this->_alias;
|
||||
return $this->_from . ' ' . $this->_alias .
|
||||
($this->_indexBy ? ' INDEX BY ' . $this->_indexBy : '');
|
||||
}
|
||||
}
|
@ -36,8 +36,9 @@ class Orx extends Composite
|
||||
{
|
||||
protected $_separator = ' OR ';
|
||||
protected $_allowedClasses = array(
|
||||
'Doctrine\ORM\Query\Expr\Andx',
|
||||
'Doctrine\ORM\Query\Expr\Comparison',
|
||||
'Doctrine\ORM\Query\Expr\Func',
|
||||
'Doctrine\ORM\Query\Expr\Andx',
|
||||
'Doctrine\ORM\Query\Expr\Orx',
|
||||
);
|
||||
}
|
@ -67,46 +67,50 @@ class Lexer extends \Doctrine\Common\Lexer
|
||||
const T_DELETE = 113;
|
||||
const T_DESC = 114;
|
||||
const T_DISTINCT = 115;
|
||||
const T_EMPTY = 116;
|
||||
const T_ESCAPE = 117;
|
||||
const T_EXISTS = 118;
|
||||
const T_FALSE = 119;
|
||||
const T_FROM = 120;
|
||||
const T_GROUP = 121;
|
||||
const T_HAVING = 122;
|
||||
const T_IN = 123;
|
||||
const T_INDEX = 124;
|
||||
const T_INNER = 125;
|
||||
const T_INSTANCE = 126;
|
||||
const T_IS = 127;
|
||||
const T_JOIN = 128;
|
||||
const T_LEADING = 129;
|
||||
const T_LEFT = 130;
|
||||
const T_LIKE = 131;
|
||||
const T_MAX = 132;
|
||||
const T_MEMBER = 133;
|
||||
const T_MIN = 134;
|
||||
const T_NOT = 135;
|
||||
const T_NULL = 136;
|
||||
const T_NULLIF = 137;
|
||||
const T_OF = 138;
|
||||
const T_OR = 139;
|
||||
const T_ORDER = 140;
|
||||
const T_OUTER = 141;
|
||||
const T_SELECT = 142;
|
||||
const T_SET = 143;
|
||||
const T_SIZE = 144;
|
||||
const T_SOME = 145;
|
||||
const T_SUM = 146;
|
||||
const T_TRAILING = 147;
|
||||
const T_TRUE = 148;
|
||||
const T_UPDATE = 149;
|
||||
const T_WHEN = 150;
|
||||
const T_WHERE = 151;
|
||||
const T_WITH = 153;
|
||||
const T_PARTIAL = 154;
|
||||
const T_MOD = 155;
|
||||
|
||||
const T_ELSE = 116;
|
||||
const T_EMPTY = 117;
|
||||
const T_END = 118;
|
||||
const T_ESCAPE = 119;
|
||||
const T_EXISTS = 120;
|
||||
const T_FALSE = 121;
|
||||
const T_FROM = 122;
|
||||
const T_GROUP = 123;
|
||||
const T_HAVING = 124;
|
||||
const T_HIDDEN = 125;
|
||||
const T_IN = 126;
|
||||
const T_INDEX = 127;
|
||||
const T_INNER = 128;
|
||||
const T_INSTANCE = 129;
|
||||
const T_IS = 130;
|
||||
const T_JOIN = 131;
|
||||
const T_LEADING = 132;
|
||||
const T_LEFT = 133;
|
||||
const T_LIKE = 134;
|
||||
const T_MAX = 135;
|
||||
const T_MEMBER = 136;
|
||||
const T_MIN = 137;
|
||||
const T_NOT = 138;
|
||||
const T_NULL = 139;
|
||||
const T_NULLIF = 140;
|
||||
const T_OF = 141;
|
||||
const T_OR = 142;
|
||||
const T_ORDER = 143;
|
||||
const T_OUTER = 144;
|
||||
const T_SELECT = 145;
|
||||
const T_SET = 146;
|
||||
const T_SIZE = 147;
|
||||
const T_SOME = 148;
|
||||
const T_SUM = 149;
|
||||
const T_THEN = 150;
|
||||
const T_TRAILING = 151;
|
||||
const T_TRUE = 152;
|
||||
const T_UPDATE = 153;
|
||||
const T_WHEN = 154;
|
||||
const T_WHERE = 155;
|
||||
const T_WITH = 156;
|
||||
const T_PARTIAL = 157;
|
||||
const T_MOD = 158;
|
||||
|
||||
/**
|
||||
* Creates a new query scanner object.
|
||||
*
|
||||
|
@ -40,7 +40,8 @@ class Parser
|
||||
'substring' => 'Doctrine\ORM\Query\AST\Functions\SubstringFunction',
|
||||
'trim' => 'Doctrine\ORM\Query\AST\Functions\TrimFunction',
|
||||
'lower' => 'Doctrine\ORM\Query\AST\Functions\LowerFunction',
|
||||
'upper' => 'Doctrine\ORM\Query\AST\Functions\UpperFunction'
|
||||
'upper' => 'Doctrine\ORM\Query\AST\Functions\UpperFunction',
|
||||
'identity' => 'Doctrine\ORM\Query\AST\Functions\IdentityFunction',
|
||||
);
|
||||
|
||||
/** READ-ONLY: Maps BUILT-IN numeric function names to AST class names. */
|
||||
@ -1342,10 +1343,7 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"]
|
||||
*
|
||||
* @todo Post 2.0 release. Support general SingleValuedPathExpression instead
|
||||
* of only StateFieldPathExpression.
|
||||
* OrderByItem ::= (ResultVariable | SingleValuedPathExpression) ["ASC" | "DESC"]
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\OrderByItem
|
||||
*/
|
||||
@ -1360,7 +1358,7 @@ class Parser
|
||||
$token = $this->_lexer->lookahead;
|
||||
$expr = $this->ResultVariable();
|
||||
} else {
|
||||
$expr = $this->StateFieldPathExpression();
|
||||
$expr = $this->SingleValuedPathExpression();
|
||||
}
|
||||
|
||||
$item = new AST\OrderByItem($expr);
|
||||
@ -1624,7 +1622,7 @@ class Parser
|
||||
/**
|
||||
* ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
|
||||
* StateFieldPathExpression | BooleanPrimary | CaseExpression |
|
||||
* EntityTypeExpression
|
||||
* InstanceOfExpression
|
||||
*
|
||||
* @return mixed One of the possible expressions or subexpressions.
|
||||
*/
|
||||
@ -1644,6 +1642,10 @@ class Parser
|
||||
return $this->StateFieldPathExpression();
|
||||
} else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) {
|
||||
return $this->SimpleArithmeticExpression();
|
||||
} else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) {
|
||||
// Since NULLIF and COALESCE can be identified as a function,
|
||||
// we need to check if before check for FunctionDeclaration
|
||||
return $this->CaseExpression();
|
||||
} else if ($this->_isFunction() || $this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
|
||||
// We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator)
|
||||
$this->_lexer->peek(); // "("
|
||||
@ -1655,9 +1657,9 @@ class Parser
|
||||
|
||||
if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
|
||||
return $this->AggregateExpression();
|
||||
} else {
|
||||
return $this->FunctionDeclaration();
|
||||
}
|
||||
|
||||
return $this->FunctionDeclaration();
|
||||
} else if ($lookahead == Lexer::T_STRING) {
|
||||
return $this->StringPrimary();
|
||||
} else if ($lookahead == Lexer::T_INPUT_PARAMETER) {
|
||||
@ -1665,26 +1667,171 @@ class Parser
|
||||
} else if ($lookahead == Lexer::T_TRUE || $lookahead == Lexer::T_FALSE) {
|
||||
$this->match($lookahead);
|
||||
return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
|
||||
} else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) {
|
||||
return $this->CaseExpression();
|
||||
} else {
|
||||
$this->syntaxError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
|
||||
* GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
|
||||
* WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
|
||||
* SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
|
||||
* CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
|
||||
* SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
|
||||
* CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
|
||||
* NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
|
||||
*
|
||||
* @return mixed One of the possible expressions or subexpressions.
|
||||
*/
|
||||
public function CaseExpression()
|
||||
{
|
||||
// if "CASE" "WHEN" => GeneralCaseExpression
|
||||
// else if "CASE" => SimpleCaseExpression
|
||||
// else if "COALESCE" => CoalesceExpression
|
||||
// else if "NULLIF" => NullifExpression
|
||||
$this->semanticalError('CaseExpression not yet supported.');
|
||||
$lookahead = $this->_lexer->lookahead['type'];
|
||||
|
||||
switch ($lookahead) {
|
||||
case Lexer::T_NULLIF:
|
||||
return $this->NullIfExpression();
|
||||
|
||||
case Lexer::T_COALESCE:
|
||||
return $this->CoalesceExpression();
|
||||
|
||||
case Lexer::T_CASE:
|
||||
$this->_lexer->resetPeek();
|
||||
$peek = $this->_lexer->peek();
|
||||
|
||||
return ($peek['type'] === Lexer::T_WHEN)
|
||||
? $this->GeneralCaseExpression()
|
||||
: $this->SimpleCaseExpression();
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
$this->syntaxError();
|
||||
}
|
||||
|
||||
/**
|
||||
* CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\CoalesceExpression
|
||||
*/
|
||||
public function CoalesceExpression()
|
||||
{
|
||||
$this->match(Lexer::T_COALESCE);
|
||||
$this->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
// Process ScalarExpressions (1..N)
|
||||
$scalarExpressions = array();
|
||||
$scalarExpressions[] = $this->ScalarExpression();
|
||||
|
||||
while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
|
||||
$this->match(Lexer::T_COMMA);
|
||||
$scalarExpressions[] = $this->ScalarExpression();
|
||||
}
|
||||
|
||||
$this->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
|
||||
return new AST\CoalesceExpression($scalarExpressions);
|
||||
}
|
||||
|
||||
/**
|
||||
* NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\NullIfExpression
|
||||
*/
|
||||
public function NullIfExpression()
|
||||
{
|
||||
$this->match(Lexer::T_NULLIF);
|
||||
$this->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$firstExpression = $this->ScalarExpression();
|
||||
$this->match(Lexer::T_COMMA);
|
||||
$secondExpression = $this->ScalarExpression();
|
||||
|
||||
$this->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
|
||||
return new AST\NullIfExpression($firstExpression, $secondExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\GeneralExpression
|
||||
*/
|
||||
public function GeneralCaseExpression()
|
||||
{
|
||||
$this->match(Lexer::T_CASE);
|
||||
|
||||
// Process WhenClause (1..N)
|
||||
$whenClauses = array();
|
||||
|
||||
do {
|
||||
$whenClauses[] = $this->WhenClause();
|
||||
} while ($this->_lexer->isNextToken(Lexer::T_WHEN));
|
||||
|
||||
$this->match(Lexer::T_ELSE);
|
||||
$scalarExpression = $this->ScalarExpression();
|
||||
$this->match(Lexer::T_END);
|
||||
|
||||
return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
|
||||
* CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
|
||||
*/
|
||||
public function SimpleCaseExpression()
|
||||
{
|
||||
$this->match(Lexer::T_CASE);
|
||||
$caseOperand = $this->StateFieldPathExpression();
|
||||
|
||||
// Process SimpleWhenClause (1..N)
|
||||
$simpleWhenClauses = array();
|
||||
|
||||
do {
|
||||
$simpleWhenClauses[] = $this->SimpleWhenClause();
|
||||
} while ($this->_lexer->isNextToken(Lexer::T_WHEN));
|
||||
|
||||
$this->match(Lexer::T_ELSE);
|
||||
$scalarExpression = $this->ScalarExpression();
|
||||
$this->match(Lexer::T_END);
|
||||
|
||||
return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\WhenExpression
|
||||
*/
|
||||
public function WhenClause()
|
||||
{
|
||||
$this->match(Lexer::T_WHEN);
|
||||
$conditionalExpression = $this->ConditionalExpression();
|
||||
$this->match(Lexer::T_THEN);
|
||||
|
||||
return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
|
||||
}
|
||||
|
||||
/**
|
||||
* SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\SimpleWhenExpression
|
||||
*/
|
||||
public function SimpleWhenClause()
|
||||
{
|
||||
$this->match(Lexer::T_WHEN);
|
||||
$conditionalExpression = $this->ScalarExpression();
|
||||
$this->match(Lexer::T_THEN);
|
||||
|
||||
return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
|
||||
}
|
||||
|
||||
/**
|
||||
* SelectExpression ::=
|
||||
* IdentificationVariable | StateFieldPathExpression |
|
||||
* (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
|
||||
* (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\SelectExpression
|
||||
*/
|
||||
@ -1692,6 +1839,7 @@ class Parser
|
||||
{
|
||||
$expression = null;
|
||||
$identVariable = null;
|
||||
$hiddenAliasResultVariable = false;
|
||||
$fieldAliasIdentificationVariable = null;
|
||||
$peek = $this->_lexer->glimpse();
|
||||
|
||||
@ -1717,12 +1865,16 @@ class Parser
|
||||
}
|
||||
} else if ($this->_isFunction()) {
|
||||
$this->_lexer->peek(); // "("
|
||||
$beyond = $this->_peekBeyondClosingParenthesis();
|
||||
|
||||
|
||||
$lookaheadType = $this->_lexer->lookahead['type'];
|
||||
$beyond = $this->_peekBeyondClosingParenthesis();
|
||||
|
||||
if ($this->_isMathOperator($beyond)) {
|
||||
$expression = $this->ScalarExpression();
|
||||
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
|
||||
$expression = $this->AggregateExpression();
|
||||
} else if (in_array($lookaheadType, array(Lexer::T_COALESCE, Lexer::T_NULLIF))) {
|
||||
$expression = $this->CaseExpression();
|
||||
} else {
|
||||
// Shortcut: ScalarExpression => Function
|
||||
$expression = $this->FunctionDeclaration();
|
||||
@ -1736,16 +1888,25 @@ class Parser
|
||||
$this->_lexer->lookahead['type'] == Lexer::T_STRING) {
|
||||
// Shortcut: ScalarExpression => SimpleArithmeticExpression
|
||||
$expression = $this->SimpleArithmeticExpression();
|
||||
} else if ($this->_lexer->lookahead['type'] == Lexer::T_CASE) {
|
||||
$expression = $this->CaseExpression();
|
||||
} else {
|
||||
$this->syntaxError('IdentificationVariable | StateFieldPathExpression'
|
||||
. ' | AggregateExpression | "(" Subselect ")" | ScalarExpression',
|
||||
$this->_lexer->lookahead);
|
||||
$this->syntaxError(
|
||||
'IdentificationVariable | StateFieldPathExpression | AggregateExpression | "(" Subselect ")" | ScalarExpression',
|
||||
$this->_lexer->lookahead
|
||||
);
|
||||
}
|
||||
|
||||
if ($supportsAlias) {
|
||||
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
|
||||
$this->match(Lexer::T_AS);
|
||||
}
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_HIDDEN)) {
|
||||
$this->match(Lexer::T_HIDDEN);
|
||||
|
||||
$hiddenAliasResultVariable = true;
|
||||
}
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
||||
$token = $this->_lexer->lookahead;
|
||||
@ -1760,10 +1921,12 @@ class Parser
|
||||
}
|
||||
}
|
||||
|
||||
$expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
|
||||
if (!$supportsAlias) {
|
||||
$expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable, $hiddenAliasResultVariable);
|
||||
|
||||
if ( ! $supportsAlias) {
|
||||
$this->_identVariableExpressions[$identVariable] = $expr;
|
||||
}
|
||||
|
||||
return $expr;
|
||||
}
|
||||
|
||||
@ -2229,7 +2392,7 @@ class Parser
|
||||
/**
|
||||
* ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")"
|
||||
* | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
|
||||
* | FunctionsReturningDatetime | IdentificationVariable
|
||||
* | FunctionsReturningDatetime | IdentificationVariable | ResultVariable | CaseExpression
|
||||
*/
|
||||
public function ArithmeticPrimary()
|
||||
{
|
||||
@ -2243,6 +2406,11 @@ class Parser
|
||||
}
|
||||
|
||||
switch ($this->_lexer->lookahead['type']) {
|
||||
case Lexer::T_COALESCE:
|
||||
case Lexer::T_NULLIF:
|
||||
case Lexer::T_CASE:
|
||||
return $this->CaseExpression();
|
||||
|
||||
case Lexer::T_IDENTIFIER:
|
||||
$peek = $this->_lexer->glimpse();
|
||||
|
||||
@ -2253,7 +2421,11 @@ class Parser
|
||||
if ($peek['value'] == '.') {
|
||||
return $this->SingleValuedPathExpression();
|
||||
}
|
||||
|
||||
|
||||
if (isset($this->_queryComponents[$this->_lexer->lookahead['value']]['resultVariable'])) {
|
||||
return $this->ResultVariable();
|
||||
}
|
||||
|
||||
return $this->StateFieldPathExpression();
|
||||
|
||||
case Lexer::T_INPUT_PARAMETER:
|
||||
@ -2298,7 +2470,7 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression
|
||||
* StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
|
||||
*/
|
||||
public function StringPrimary()
|
||||
{
|
||||
@ -2321,6 +2493,8 @@ class Parser
|
||||
return $this->InputParameter();
|
||||
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
|
||||
return $this->AggregateExpression();
|
||||
} else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) {
|
||||
return $this->CaseExpression();
|
||||
}
|
||||
|
||||
$this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression');
|
||||
@ -2517,7 +2691,7 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (AbstractSchemaName | InputParameter)
|
||||
* InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\InstanceOfExpression
|
||||
*/
|
||||
@ -2531,22 +2705,50 @@ class Parser
|
||||
}
|
||||
|
||||
$this->match(Lexer::T_INSTANCE);
|
||||
$this->match(Lexer::T_OF);
|
||||
|
||||
$exprValues = array();
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
|
||||
$this->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$exprValues[] = $this->InstanceOfParameter();
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_OF)) {
|
||||
$this->match(Lexer::T_OF);
|
||||
while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
|
||||
$this->match(Lexer::T_COMMA);
|
||||
|
||||
$exprValues[] = $this->InstanceOfParameter();
|
||||
}
|
||||
|
||||
$this->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
|
||||
$instanceOfExpression->value = $exprValues;
|
||||
|
||||
return $instanceOfExpression;
|
||||
}
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
|
||||
$this->match(Lexer::T_INPUT_PARAMETER);
|
||||
$exprValue = new AST\InputParameter($this->_lexer->token['value']);
|
||||
} else {
|
||||
$exprValue = $this->AliasIdentificationVariable();
|
||||
}
|
||||
$exprValues[] = $this->InstanceOfParameter();
|
||||
|
||||
$instanceOfExpression->value = $exprValue;
|
||||
$instanceOfExpression->value = $exprValues;
|
||||
|
||||
return $instanceOfExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* InstanceOfParameter ::= AbstractSchemaName | InputParameter
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function InstanceOfParameter()
|
||||
{
|
||||
if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
|
||||
$this->match(Lexer::T_INPUT_PARAMETER);
|
||||
|
||||
return new AST\InputParameter($this->_lexer->token['value']);
|
||||
}
|
||||
|
||||
return $this->AliasIdentificationVariable();
|
||||
}
|
||||
|
||||
/**
|
||||
* LikeExpression ::= StringExpression ["NOT"] "LIKE" (string | input_parameter) ["ESCAPE" char]
|
||||
|
@ -135,4 +135,10 @@ class QueryException extends \Doctrine\ORM\ORMException
|
||||
"in the query."
|
||||
);
|
||||
}
|
||||
|
||||
public static function instanceOfUnrelatedClass($className, $rootClass)
|
||||
{
|
||||
return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " .
|
||||
"inheritance hierachy exists between these two classes.");
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
|
||||
/**
|
||||
* A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields
|
||||
@ -52,21 +53,7 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
public function addRootEntityFromClassMetadata($class, $alias, $renamedColumns = array())
|
||||
{
|
||||
$this->addEntityResult($class, $alias);
|
||||
$classMetadata = $this->em->getClassMetadata($class);
|
||||
if ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()) {
|
||||
throw new \InvalidArgumentException('ResultSetMapping builder does not currently support inheritance.');
|
||||
}
|
||||
$platform = $this->em->getConnection()->getDatabasePlatform();
|
||||
foreach ($classMetadata->getColumnNames() AS $columnName) {
|
||||
$propertyName = $classMetadata->getFieldName($columnName);
|
||||
if (isset($renamedColumns[$columnName])) {
|
||||
$columnName = $renamedColumns[$columnName];
|
||||
}
|
||||
if (isset($this->fieldMappings[$columnName])) {
|
||||
throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper.");
|
||||
}
|
||||
$this->addFieldResult($alias, $platform->getSQLResultCasing($columnName), $propertyName);
|
||||
}
|
||||
$this->addAllClassFields($class, $alias, $renamedColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,6 +68,14 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
public function addJoinedEntityFromClassMetadata($class, $alias, $parentAlias, $relation, $renamedColumns = array())
|
||||
{
|
||||
$this->addJoinedEntityResult($class, $alias, $parentAlias, $relation);
|
||||
$this->addAllClassFields($class, $alias, $renamedColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all fields of the given class to the result set mapping (columns and meta fields)
|
||||
*/
|
||||
protected function addAllClassFields($class, $alias, $renamedColumns = array())
|
||||
{
|
||||
$classMetadata = $this->em->getClassMetadata($class);
|
||||
if ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()) {
|
||||
throw new \InvalidArgumentException('ResultSetMapping builder does not currently support inheritance.');
|
||||
@ -96,5 +91,17 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
}
|
||||
$this->addFieldResult($alias, $platform->getSQLResultCasing($columnName), $propertyName);
|
||||
}
|
||||
foreach ($classMetadata->associationMappings AS $associationMapping) {
|
||||
if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
|
||||
foreach ($associationMapping['joinColumns'] AS $joinColumn) {
|
||||
$columnName = $joinColumn['name'];
|
||||
$renamedColumnName = isset($renamedColumns[$columnName]) ? $renamedColumns[$columnName] : $columnName;
|
||||
if (isset($this->metaMappings[$renamedColumnName])) {
|
||||
throw new \InvalidArgumentException("The column '$renamedColumnName' conflicts with another column in the mapper.");
|
||||
}
|
||||
$this->addMetaResult($alias, $platform->getSQLResultCasing($renamedColumnName), $platform->getSQLResultCasing($columnName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -244,24 +244,23 @@ class SqlWalker implements TreeWalker
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
$baseTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
$baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
|
||||
// INNER JOIN parent class tables
|
||||
foreach ($class->parentClasses as $parentClassName) {
|
||||
$parentClass = $this->_em->getClassMetadata($parentClassName);
|
||||
$tableAlias = $this->getSQLTableAlias($parentClass->table['name'], $dqlAlias);
|
||||
$tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
|
||||
|
||||
// If this is a joined association we must use left joins to preserve the correct result.
|
||||
$sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
|
||||
$sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform)
|
||||
. ' ' . $tableAlias . ' ON ';
|
||||
$sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
|
||||
$first = true;
|
||||
|
||||
foreach ($class->identifier as $idField) {
|
||||
if ($first) $first = false; else $sql .= ' AND ';
|
||||
|
||||
$columnName = $class->getQuotedColumnName($idField, $this->_platform);
|
||||
$sql .= $baseTableAlias . '.' . $columnName
|
||||
. ' = '
|
||||
. $tableAlias . '.' . $columnName;
|
||||
$sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,17 +268,15 @@ class SqlWalker implements TreeWalker
|
||||
if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
|
||||
foreach ($class->subClasses as $subClassName) {
|
||||
$subClass = $this->_em->getClassMetadata($subClassName);
|
||||
$tableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias);
|
||||
$sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform)
|
||||
. ' ' . $tableAlias . ' ON ';
|
||||
$tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
|
||||
$sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
|
||||
$first = true;
|
||||
|
||||
foreach ($class->identifier as $idField) {
|
||||
if ($first) $first = false; else $sql .= ' AND ';
|
||||
|
||||
$columnName = $class->getQuotedColumnName($idField, $this->_platform);
|
||||
$sql .= $baseTableAlias . '.' . $columnName
|
||||
. ' = '
|
||||
. $tableAlias . '.' . $columnName;
|
||||
$sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -290,24 +287,26 @@ class SqlWalker implements TreeWalker
|
||||
private function _generateOrderedCollectionOrderByItems()
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
foreach ($this->_selectedClasses AS $dqlAlias => $class) {
|
||||
$qComp = $this->_queryComponents[$dqlAlias];
|
||||
|
||||
if (isset($qComp['relation']['orderBy'])) {
|
||||
foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) {
|
||||
if ($qComp['metadata']->isInheritanceTypeJoined()) {
|
||||
$tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName);
|
||||
} else {
|
||||
$tableName = $qComp['metadata']->table['name'];
|
||||
}
|
||||
$tableName = ($qComp['metadata']->isInheritanceTypeJoined())
|
||||
? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
|
||||
: $qComp['metadata']->getTableName();
|
||||
|
||||
if ($sql != '') {
|
||||
$sql .= ', ';
|
||||
}
|
||||
$sql .= $this->getSQLTableAlias($tableName, $dqlAlias) . '.' .
|
||||
$qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " $orientation";
|
||||
|
||||
$sql .= $this->getSQLTableAlias($tableName, $dqlAlias) . '.'
|
||||
. $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . ' ' . $orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
@ -328,6 +327,7 @@ class SqlWalker implements TreeWalker
|
||||
if ($class->isInheritanceTypeSingleTable()) {
|
||||
$conn = $this->_em->getConnection();
|
||||
$values = array();
|
||||
|
||||
if ($class->discriminatorValue !== null) { // discrimnators can be 0
|
||||
$values[] = $conn->quote($class->discriminatorValue);
|
||||
}
|
||||
@ -342,7 +342,7 @@ class SqlWalker implements TreeWalker
|
||||
}
|
||||
|
||||
$sql .= ($sql != '' ? ' AND ' : '')
|
||||
. (($this->_useSqlTableAliases) ? $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.' : '')
|
||||
. (($this->_useSqlTableAliases) ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' : '')
|
||||
. $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
|
||||
}
|
||||
}
|
||||
@ -357,34 +357,27 @@ class SqlWalker implements TreeWalker
|
||||
*/
|
||||
public function walkSelectStatement(AST\SelectStatement $AST)
|
||||
{
|
||||
$sql = $this->walkSelectClause($AST->selectClause);
|
||||
$sql = $this->walkSelectClause($AST->selectClause);
|
||||
$sql .= $this->walkFromClause($AST->fromClause);
|
||||
|
||||
if (($whereClause = $AST->whereClause) !== null) {
|
||||
$sql .= $this->walkWhereClause($whereClause);
|
||||
} else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
|
||||
$sql .= ' WHERE ' . $discSql;
|
||||
}
|
||||
|
||||
$sql .= $this->walkWhereClause($AST->whereClause);
|
||||
$sql .= $AST->groupByClause ? $this->walkGroupByClause($AST->groupByClause) : '';
|
||||
$sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : '';
|
||||
|
||||
if (($orderByClause = $AST->orderByClause) !== null) {
|
||||
$sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : '';
|
||||
} else if (($orderBySql = $this->_generateOrderedCollectionOrderByItems()) !== '') {
|
||||
$sql .= ' ORDER BY '.$orderBySql;
|
||||
$sql .= ' ORDER BY ' . $orderBySql;
|
||||
}
|
||||
|
||||
|
||||
$sql = $this->_platform->modifyLimitQuery(
|
||||
$sql, $this->_query->getMaxResults(), $this->_query->getFirstResult()
|
||||
);
|
||||
|
||||
if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) {
|
||||
if ($lockMode == LockMode::PESSIMISTIC_READ) {
|
||||
$sql .= " " . $this->_platform->getReadLockSQL();
|
||||
$sql .= ' ' . $this->_platform->getReadLockSQL();
|
||||
} else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
|
||||
$sql .= " " . $this->_platform->getWriteLockSQL();
|
||||
$sql .= ' ' . $this->_platform->getWriteLockSQL();
|
||||
} else if ($lockMode == LockMode::OPTIMISTIC) {
|
||||
foreach ($this->_selectedClasses AS $class) {
|
||||
if ( ! $class->isVersioned) {
|
||||
@ -406,15 +399,8 @@ class SqlWalker implements TreeWalker
|
||||
public function walkUpdateStatement(AST\UpdateStatement $AST)
|
||||
{
|
||||
$this->_useSqlTableAliases = false;
|
||||
$sql = $this->walkUpdateClause($AST->updateClause);
|
||||
|
||||
if (($whereClause = $AST->whereClause) !== null) {
|
||||
$sql .= $this->walkWhereClause($whereClause);
|
||||
} else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
|
||||
$sql .= ' WHERE ' . $discSql;
|
||||
}
|
||||
|
||||
return $sql;
|
||||
|
||||
return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -426,15 +412,8 @@ class SqlWalker implements TreeWalker
|
||||
public function walkDeleteStatement(AST\DeleteStatement $AST)
|
||||
{
|
||||
$this->_useSqlTableAliases = false;
|
||||
$sql = $this->walkDeleteClause($AST->deleteClause);
|
||||
|
||||
if (($whereClause = $AST->whereClause) !== null) {
|
||||
$sql .= $this->walkWhereClause($whereClause);
|
||||
} else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
|
||||
$sql .= ' WHERE ' . $discSql;
|
||||
}
|
||||
|
||||
return $sql;
|
||||
|
||||
return $this->walkDeleteClause($AST->deleteClause) . $this->walkWhereClause($AST->whereClause);
|
||||
}
|
||||
|
||||
|
||||
@ -456,7 +435,7 @@ class SqlWalker implements TreeWalker
|
||||
$class = $this->_em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
|
||||
}
|
||||
|
||||
return $this->getSQLTableAlias($class->table['name'], $identificationVariable);
|
||||
return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -495,20 +474,20 @@ class SqlWalker implements TreeWalker
|
||||
|
||||
$assoc = $class->associationMappings[$fieldName];
|
||||
|
||||
if ($assoc['isOwningSide']) {
|
||||
// COMPOSITE KEYS NOT (YET?) SUPPORTED
|
||||
if (count($assoc['sourceToTargetKeyColumns']) > 1) {
|
||||
throw QueryException::associationPathCompositeKeyNotSupported();
|
||||
}
|
||||
|
||||
if ($this->_useSqlTableAliases) {
|
||||
$sql .= $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.';
|
||||
}
|
||||
|
||||
$sql .= reset($assoc['targetToSourceKeyColumns']);
|
||||
} else {
|
||||
if ( ! $assoc['isOwningSide']) {
|
||||
throw QueryException::associationPathInverseSideNotSupported();
|
||||
}
|
||||
|
||||
// COMPOSITE KEYS NOT (YET?) SUPPORTED
|
||||
if (count($assoc['sourceToTargetKeyColumns']) > 1) {
|
||||
throw QueryException::associationPathCompositeKeyNotSupported();
|
||||
}
|
||||
|
||||
if ($this->_useSqlTableAliases) {
|
||||
$sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
|
||||
}
|
||||
|
||||
$sql .= reset($assoc['targetToSourceKeyColumns']);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -526,9 +505,8 @@ class SqlWalker implements TreeWalker
|
||||
*/
|
||||
public function walkSelectClause($selectClause)
|
||||
{
|
||||
$sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '') . implode(
|
||||
', ', array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions)
|
||||
);
|
||||
$sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
|
||||
$sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions));
|
||||
|
||||
$addMetaColumns = ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
|
||||
$this->_query->getHydrationMode() == Query::HYDRATE_OBJECT
|
||||
@ -551,14 +529,15 @@ class SqlWalker implements TreeWalker
|
||||
if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
|
||||
// Add discriminator columns to SQL
|
||||
$rootClass = $this->_em->getClassMetadata($class->rootEntityName);
|
||||
$tblAlias = $this->getSQLTableAlias($rootClass->table['name'], $dqlAlias);
|
||||
$tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
|
||||
$discrColumn = $rootClass->discriminatorColumn;
|
||||
$columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
|
||||
$sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias;
|
||||
|
||||
$sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
|
||||
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
|
||||
$this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $discrColumn['fieldName']);
|
||||
$this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']);
|
||||
|
||||
// Add foreign key columns to SQL, if necessary
|
||||
if ($addMetaColumns) {
|
||||
@ -567,14 +546,16 @@ class SqlWalker implements TreeWalker
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
if (isset($assoc['inherited'])) {
|
||||
$owningClass = $this->_em->getClassMetadata($assoc['inherited']);
|
||||
$sqlTableAlias = $this->getSQLTableAlias($owningClass->table['name'], $dqlAlias);
|
||||
$sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
|
||||
} else {
|
||||
$sqlTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
$sqlTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
}
|
||||
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
$columnAlias = $this->getSQLColumnAlias($srcColumn);
|
||||
$sql .= ", $sqlTableAlias." . $srcColumn . ' AS ' . $columnAlias;
|
||||
|
||||
$sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
|
||||
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true));
|
||||
}
|
||||
@ -584,12 +565,15 @@ class SqlWalker implements TreeWalker
|
||||
} else {
|
||||
// Add foreign key columns to SQL, if necessary
|
||||
if ($addMetaColumns) {
|
||||
$sqlTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
$sqlTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
$columnAlias = $this->getSQLColumnAlias($srcColumn);
|
||||
$sql .= ', ' . $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
|
||||
|
||||
$sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
|
||||
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true));
|
||||
}
|
||||
@ -598,6 +582,8 @@ class SqlWalker implements TreeWalker
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sql .= implode(', ', $sqlSelectExpressions);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
@ -622,7 +608,7 @@ class SqlWalker implements TreeWalker
|
||||
|
||||
$class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
|
||||
$sql .= $class->getQuotedTableName($this->_platform) . ' '
|
||||
. $this->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
. $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
|
||||
if ($class->isInheritanceTypeJoined()) {
|
||||
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
|
||||
@ -733,13 +719,15 @@ class SqlWalker implements TreeWalker
|
||||
}
|
||||
|
||||
$joinAssocPathExpr = $join->joinAssociationPathExpression;
|
||||
$joinedDqlAlias = $join->aliasIdentificationVariable;
|
||||
$relation = $this->_queryComponents[$joinedDqlAlias]['relation'];
|
||||
$targetClass = $this->_em->getClassMetadata($relation['targetEntity']);
|
||||
$sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']);
|
||||
$joinedDqlAlias = $join->aliasIdentificationVariable;
|
||||
|
||||
$relation = $this->_queryComponents[$joinedDqlAlias]['relation'];
|
||||
$targetClass = $this->_em->getClassMetadata($relation['targetEntity']);
|
||||
$sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']);
|
||||
$targetTableName = $targetClass->getQuotedTableName($this->_platform);
|
||||
$targetTableAlias = $this->getSQLTableAlias($targetClass->table['name'], $joinedDqlAlias);
|
||||
$sourceTableAlias = $this->getSQLTableAlias($sourceClass->table['name'], $joinAssocPathExpr->identificationVariable);
|
||||
|
||||
$targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
|
||||
$sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $joinAssocPathExpr->identificationVariable);
|
||||
|
||||
// Ensure we got the owning side, since it has all mapping info
|
||||
$assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
|
||||
@ -821,8 +809,7 @@ class SqlWalker implements TreeWalker
|
||||
}
|
||||
|
||||
// Join target table
|
||||
$sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
|
||||
? ' LEFT JOIN ' : ' INNER JOIN ';
|
||||
$sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
|
||||
$sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
|
||||
|
||||
$first = true;
|
||||
@ -873,6 +860,112 @@ class SqlWalker implements TreeWalker
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks down a CaseExpression AST node and generates the corresponding SQL.
|
||||
*
|
||||
* @param CoalesceExpression|NullIfExpression|GeneralCaseExpression|SimpleCaseExpression $expression
|
||||
* @return string The SQL.
|
||||
*/
|
||||
public function walkCaseExpression($expression)
|
||||
{
|
||||
switch (true) {
|
||||
case ($expression instanceof AST\CoalesceExpression):
|
||||
return $this->walkCoalesceExpression($expression);
|
||||
|
||||
case ($expression instanceof AST\NullIfExpression):
|
||||
return $this->walkNullIfExpression($expression);
|
||||
|
||||
case ($expression instanceof AST\GeneralCaseExpression):
|
||||
return $this->walkGeneralCaseExpression($expression);
|
||||
|
||||
case ($expression instanceof AST\SimpleCaseExpression):
|
||||
return $this->walkSimpleCaseExpression($expression);
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks down a CoalesceExpression AST node and generates the corresponding SQL.
|
||||
*
|
||||
* @param CoalesceExpression $coalesceExpression
|
||||
* @return string The SQL.
|
||||
*/
|
||||
public function walkCoalesceExpression($coalesceExpression)
|
||||
{
|
||||
$sql = 'COALESCE(';
|
||||
|
||||
$scalarExpressions = array();
|
||||
|
||||
foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
|
||||
$scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
|
||||
}
|
||||
|
||||
$sql .= implode(', ', $scalarExpressions) . ')';
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks down a NullIfExpression AST node and generates the corresponding SQL.
|
||||
*
|
||||
* @param NullIfExpression $nullIfExpression
|
||||
* @return string The SQL.
|
||||
*/
|
||||
public function walkNullIfExpression($nullIfExpression)
|
||||
{
|
||||
$firstExpression = is_string($nullIfExpression->firstExpression)
|
||||
? $this->_conn->quote($nullIfExpression->firstExpression)
|
||||
: $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
|
||||
|
||||
$secondExpression = is_string($nullIfExpression->secondExpression)
|
||||
? $this->_conn->quote($nullIfExpression->secondExpression)
|
||||
: $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
|
||||
|
||||
return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
|
||||
*
|
||||
* @param GeneralCaseExpression $generalCaseExpression
|
||||
* @return string The SQL.
|
||||
*/
|
||||
public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
|
||||
{
|
||||
$sql = 'CASE';
|
||||
|
||||
foreach ($generalCaseExpression->whenClauses as $whenClause) {
|
||||
$sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
|
||||
$sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
|
||||
}
|
||||
|
||||
$sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
|
||||
*
|
||||
* @param SimpleCaseExpression $simpleCaseExpression
|
||||
* @return string The SQL.
|
||||
*/
|
||||
public function walkSimpleCaseExpression($simpleCaseExpression)
|
||||
{
|
||||
$sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
|
||||
|
||||
foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
|
||||
$sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
|
||||
$sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
|
||||
}
|
||||
|
||||
$sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks down a SelectExpression AST node and generates the corresponding SQL.
|
||||
@ -882,40 +975,43 @@ class SqlWalker implements TreeWalker
|
||||
*/
|
||||
public function walkSelectExpression($selectExpression)
|
||||
{
|
||||
$sql = '';
|
||||
$expr = $selectExpression->expression;
|
||||
$sql = '';
|
||||
$expr = $selectExpression->expression;
|
||||
$hidden = $selectExpression->hiddenAliasResultVariable;
|
||||
|
||||
if ($expr instanceof AST\PathExpression) {
|
||||
if ($expr->type == AST\PathExpression::TYPE_STATE_FIELD) {
|
||||
$fieldName = $expr->field;
|
||||
$dqlAlias = $expr->identificationVariable;
|
||||
$qComp = $this->_queryComponents[$dqlAlias];
|
||||
$class = $qComp['metadata'];
|
||||
|
||||
if ( ! $selectExpression->fieldIdentificationVariable) {
|
||||
$resultAlias = $fieldName;
|
||||
} else {
|
||||
$resultAlias = $selectExpression->fieldIdentificationVariable;
|
||||
}
|
||||
|
||||
if ($class->isInheritanceTypeJoined()) {
|
||||
$tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName);
|
||||
} else {
|
||||
$tableName = $class->getTableName();
|
||||
}
|
||||
|
||||
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
|
||||
$columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
|
||||
|
||||
$columnAlias = $this->getSQLColumnAlias($columnName);
|
||||
$sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
} else {
|
||||
if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
|
||||
throw QueryException::invalidPathExpression($expr->type);
|
||||
}
|
||||
}
|
||||
else if ($expr instanceof AST\AggregateExpression) {
|
||||
|
||||
$fieldName = $expr->field;
|
||||
$dqlAlias = $expr->identificationVariable;
|
||||
$qComp = $this->_queryComponents[$dqlAlias];
|
||||
$class = $qComp['metadata'];
|
||||
|
||||
if ( ! $selectExpression->fieldIdentificationVariable) {
|
||||
$resultAlias = $fieldName;
|
||||
} else {
|
||||
$resultAlias = $selectExpression->fieldIdentificationVariable;
|
||||
}
|
||||
|
||||
if ($class->isInheritanceTypeJoined()) {
|
||||
$tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName);
|
||||
} else {
|
||||
$tableName = $class->getTableName();
|
||||
}
|
||||
|
||||
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
|
||||
$columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
|
||||
|
||||
$columnAlias = $this->getSQLColumnAlias($columnName);
|
||||
$sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
|
||||
if ( ! $hidden) {
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
}
|
||||
} else if ($expr instanceof AST\AggregateExpression) {
|
||||
if ( ! $selectExpression->fieldIdentificationVariable) {
|
||||
$resultAlias = $this->_scalarResultCounter++;
|
||||
} else {
|
||||
@ -927,9 +1023,11 @@ class SqlWalker implements TreeWalker
|
||||
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
|
||||
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
}
|
||||
else if ($expr instanceof AST\Subselect) {
|
||||
|
||||
if ( ! $hidden) {
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
}
|
||||
} else if ($expr instanceof AST\Subselect) {
|
||||
if ( ! $selectExpression->fieldIdentificationVariable) {
|
||||
$resultAlias = $this->_scalarResultCounter++;
|
||||
} else {
|
||||
@ -941,9 +1039,11 @@ class SqlWalker implements TreeWalker
|
||||
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
|
||||
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
}
|
||||
else if ($expr instanceof AST\Functions\FunctionNode) {
|
||||
|
||||
if ( ! $hidden) {
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
}
|
||||
} else if ($expr instanceof AST\Functions\FunctionNode) {
|
||||
if ( ! $selectExpression->fieldIdentificationVariable) {
|
||||
$resultAlias = $this->_scalarResultCounter++;
|
||||
} else {
|
||||
@ -955,9 +1055,11 @@ class SqlWalker implements TreeWalker
|
||||
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
|
||||
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
}
|
||||
else if (
|
||||
|
||||
if ( ! $hidden) {
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
}
|
||||
} else if (
|
||||
$expr instanceof AST\SimpleArithmeticExpression ||
|
||||
$expr instanceof AST\ArithmeticTerm ||
|
||||
$expr instanceof AST\ArithmeticFactor ||
|
||||
@ -971,15 +1073,43 @@ class SqlWalker implements TreeWalker
|
||||
}
|
||||
|
||||
$columnAlias = 'sclr' . $this->_aliasCounter++;
|
||||
|
||||
if ($expr instanceof AST\Literal) {
|
||||
$sql .= $this->walkLiteral($expr) . ' AS ' .$columnAlias;
|
||||
} else {
|
||||
$sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
|
||||
}
|
||||
|
||||
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
|
||||
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
|
||||
if ( ! $hidden) {
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
}
|
||||
} else if (
|
||||
$expr instanceof AST\NullIfExpression ||
|
||||
$expr instanceof AST\CoalesceExpression ||
|
||||
$expr instanceof AST\GeneralCaseExpression ||
|
||||
$expr instanceof AST\SimpleCaseExpression
|
||||
) {
|
||||
if ( ! $selectExpression->fieldIdentificationVariable) {
|
||||
$resultAlias = $this->_scalarResultCounter++;
|
||||
} else {
|
||||
$resultAlias = $selectExpression->fieldIdentificationVariable;
|
||||
}
|
||||
|
||||
$columnAlias = 'sclr' . $this->_aliasCounter++;
|
||||
|
||||
$sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias;
|
||||
|
||||
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
|
||||
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
|
||||
if ( ! $hidden) {
|
||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||
}
|
||||
} else {
|
||||
// IdentificationVariable or PartialObjectExpression
|
||||
if ($expr instanceof AST\PartialObjectExpression) {
|
||||
@ -996,19 +1126,18 @@ class SqlWalker implements TreeWalker
|
||||
if ( ! isset($this->_selectedClasses[$dqlAlias])) {
|
||||
$this->_selectedClasses[$dqlAlias] = $class;
|
||||
}
|
||||
|
||||
|
||||
$beginning = true;
|
||||
|
||||
// Select all fields from the queried class
|
||||
foreach ($class->fieldMappings as $fieldName => $mapping) {
|
||||
if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($mapping['inherited'])) {
|
||||
$tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name'];
|
||||
} else {
|
||||
$tableName = $class->table['name'];
|
||||
}
|
||||
$tableName = (isset($mapping['inherited']))
|
||||
? $this->_em->getClassMetadata($mapping['inherited'])->getTableName()
|
||||
: $class->getTableName();
|
||||
|
||||
if ($beginning) $beginning = false; else $sql .= ', ';
|
||||
|
||||
@ -1018,6 +1147,7 @@ class SqlWalker implements TreeWalker
|
||||
. ' AS ' . $columnAlias;
|
||||
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
|
||||
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
|
||||
}
|
||||
|
||||
@ -1028,7 +1158,8 @@ class SqlWalker implements TreeWalker
|
||||
if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
|
||||
foreach ($class->subClasses as $subClassName) {
|
||||
$subClass = $this->_em->getClassMetadata($subClassName);
|
||||
$sqlTableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias);
|
||||
$sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
|
||||
|
||||
foreach ($subClass->fieldMappings as $fieldName => $mapping) {
|
||||
if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
|
||||
continue;
|
||||
@ -1050,8 +1181,10 @@ class SqlWalker implements TreeWalker
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
if ($beginning) $beginning = false; else $sql .= ', ';
|
||||
|
||||
$columnAlias = $this->getSQLColumnAlias($srcColumn);
|
||||
$sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
|
||||
|
||||
$this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
|
||||
}
|
||||
}
|
||||
@ -1084,15 +1217,19 @@ class SqlWalker implements TreeWalker
|
||||
public function walkSubselect($subselect)
|
||||
{
|
||||
$useAliasesBefore = $this->_useSqlTableAliases;
|
||||
$rootAliasesBefore = $this->_rootAliases;
|
||||
|
||||
$this->_rootAliases = array(); // reset the rootAliases for the subselect
|
||||
$this->_useSqlTableAliases = true;
|
||||
|
||||
$sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
|
||||
$sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
|
||||
$sql .= $subselect->whereClause ? $this->walkWhereClause($subselect->whereClause) : '';
|
||||
$sql .= $this->walkWhereClause($subselect->whereClause);
|
||||
$sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
|
||||
$sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
|
||||
$sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
|
||||
|
||||
$this->_rootAliases = $rootAliasesBefore; // put the main aliases back
|
||||
$this->_useSqlTableAliases = $useAliasesBefore;
|
||||
|
||||
return $sql;
|
||||
@ -1117,7 +1254,9 @@ class SqlWalker implements TreeWalker
|
||||
|
||||
$class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
|
||||
$sql .= $class->getQuotedTableName($this->_platform) . ' '
|
||||
. $this->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
. $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
|
||||
$this->_rootAliases[] = $dqlAlias;
|
||||
|
||||
if ($class->isInheritanceTypeJoined()) {
|
||||
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
|
||||
@ -1200,6 +1339,22 @@ class SqlWalker implements TreeWalker
|
||||
|
||||
$columnAlias = 'sclr' . $this->_aliasCounter++;
|
||||
$sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
|
||||
$this->_scalarResultAliasMap[$alias] = $columnAlias;
|
||||
} else if (
|
||||
$expr instanceof AST\NullIfExpression ||
|
||||
$expr instanceof AST\CoalesceExpression ||
|
||||
$expr instanceof AST\GeneralCaseExpression ||
|
||||
$expr instanceof AST\SimpleCaseExpression
|
||||
) {
|
||||
if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
|
||||
$alias = $this->_scalarResultCounter++;
|
||||
} else {
|
||||
$alias = $simpleSelectExpression->fieldIdentificationVariable;
|
||||
}
|
||||
|
||||
$columnAlias = 'sclr' . $this->_aliasCounter++;
|
||||
$sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias;
|
||||
|
||||
$this->_scalarResultAliasMap[$alias] = $columnAlias;
|
||||
} else {
|
||||
// IdentificationVariable
|
||||
@ -1340,16 +1495,23 @@ class SqlWalker implements TreeWalker
|
||||
|
||||
/**
|
||||
* Walks down a WhereClause AST node, thereby generating the appropriate SQL.
|
||||
* WhereClause or not, the appropriate discriminator sql is added.
|
||||
*
|
||||
* @param WhereClause
|
||||
* @return string The SQL.
|
||||
*/
|
||||
public function walkWhereClause($whereClause)
|
||||
{
|
||||
$condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
|
||||
$discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases);
|
||||
$condSql = $this->walkConditionalExpression($whereClause->conditionalExpression);
|
||||
|
||||
return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
|
||||
if ($condSql) {
|
||||
return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
|
||||
} else if ($discrSql) {
|
||||
return ' WHERE ' . $discrSql;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1462,15 +1624,13 @@ class SqlWalker implements TreeWalker
|
||||
$assoc = $class->associationMappings[$fieldName];
|
||||
|
||||
if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
|
||||
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
|
||||
$targetTableAlias = $this->getSQLTableAlias($targetClass->table['name']);
|
||||
$sourceTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
|
||||
$targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
|
||||
$sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
|
||||
$sql .= $targetClass->getQuotedTableName($this->_platform)
|
||||
. ' ' . $targetTableAlias . ' WHERE ';
|
||||
$sql .= $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' WHERE ';
|
||||
|
||||
$owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
|
||||
|
||||
$first = true;
|
||||
|
||||
foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
|
||||
@ -1498,15 +1658,13 @@ class SqlWalker implements TreeWalker
|
||||
$joinTable = $owningAssoc['joinTable'];
|
||||
|
||||
// SQL table aliases
|
||||
$joinTableAlias = $this->getSQLTableAlias($joinTable['name']);
|
||||
$targetTableAlias = $this->getSQLTableAlias($targetClass->table['name']);
|
||||
$sourceTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
$joinTableAlias = $this->getSQLTableAlias($joinTable['name']);
|
||||
$targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
|
||||
$sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
|
||||
// join to target table
|
||||
$sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform)
|
||||
. ' ' . $joinTableAlias . ' INNER JOIN '
|
||||
. $targetClass->getQuotedTableName($this->_platform)
|
||||
. ' ' . $targetTableAlias . ' ON ';
|
||||
$sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform) . ' ' . $joinTableAlias
|
||||
. ' INNER JOIN ' . $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' ON ';
|
||||
|
||||
// join conditions
|
||||
$joinColumns = $assoc['isOwningSide']
|
||||
@ -1518,25 +1676,19 @@ class SqlWalker implements TreeWalker
|
||||
if ($first) $first = false; else $sql .= ' AND ';
|
||||
|
||||
$sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = '
|
||||
. $targetTableAlias . '.' . $targetClass->getQuotedColumnName(
|
||||
$targetClass->fieldNames[$joinColumn['referencedColumnName']],
|
||||
$this->_platform);
|
||||
. $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $this->_platform);
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ';
|
||||
|
||||
$joinColumns = $assoc['isOwningSide']
|
||||
? $joinTable['joinColumns']
|
||||
: $joinTable['inverseJoinColumns'];
|
||||
|
||||
$joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
|
||||
$first = true;
|
||||
|
||||
foreach ($joinColumns as $joinColumn) {
|
||||
if ($first) $first = false; else $sql .= ' AND ';
|
||||
|
||||
$sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = '
|
||||
. $sourceTableAlias . '.' . $class->getQuotedColumnName(
|
||||
$class->fieldNames[$joinColumn['referencedColumnName']],
|
||||
$this->_platform);
|
||||
. $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $this->_platform);
|
||||
}
|
||||
|
||||
$sql .= ' AND ';
|
||||
@ -1633,32 +1785,43 @@ class SqlWalker implements TreeWalker
|
||||
}
|
||||
|
||||
if ($this->_useSqlTableAliases) {
|
||||
$sql .= $this->getSQLTableAlias($discrClass->table['name'], $dqlAlias) . '.';
|
||||
$sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
|
||||
}
|
||||
|
||||
$sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
|
||||
|
||||
$sqlParameterList = array();
|
||||
|
||||
foreach ($instanceOfExpr->value as $parameter) {
|
||||
if ($parameter instanceof AST\InputParameter) {
|
||||
// We need to modify the parameter value to be its correspondent mapped value
|
||||
$dqlParamKey = $parameter->name;
|
||||
$paramValue = $this->_query->getParameter($dqlParamKey);
|
||||
|
||||
$sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' <> ' : ' = ');
|
||||
if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) {
|
||||
throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue));
|
||||
}
|
||||
|
||||
if ($instanceOfExpr->value instanceof AST\InputParameter) {
|
||||
// We need to modify the parameter value to be its correspondent mapped value
|
||||
$dqlParamKey = $instanceOfExpr->value->name;
|
||||
$paramValue = $this->_query->getParameter($dqlParamKey);
|
||||
|
||||
if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) {
|
||||
throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue));
|
||||
$entityClassName = $paramValue->name;
|
||||
} else {
|
||||
// Get name from ClassMetadata to resolve aliases.
|
||||
$entityClassName = $this->_em->getClassMetadata($parameter)->name;
|
||||
}
|
||||
|
||||
$entityClassName = $paramValue->name;
|
||||
} else {
|
||||
// Get name from ClassMetadata to resolve aliases.
|
||||
$entityClassName = $this->_em->getClassMetadata($instanceOfExpr->value)->name;
|
||||
}
|
||||
|
||||
if ($entityClassName == $class->name) {
|
||||
$sql .= $this->_conn->quote($class->discriminatorValue);
|
||||
} else {
|
||||
$discrMap = array_flip($class->discriminatorMap);
|
||||
$sql .= $this->_conn->quote($discrMap[$entityClassName]);
|
||||
if ($entityClassName == $class->name) {
|
||||
$sqlParameterList[] = $this->_conn->quote($class->discriminatorValue);
|
||||
} else {
|
||||
$discrMap = array_flip($class->discriminatorMap);
|
||||
|
||||
if (!isset($discrMap[$entityClassName])) {
|
||||
throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName);
|
||||
}
|
||||
|
||||
$sqlParameterList[] = $this->_conn->quote($discrMap[$entityClassName]);
|
||||
}
|
||||
}
|
||||
|
||||
$sql .= '(' . implode(', ', $sqlParameterList) . ')';
|
||||
|
||||
return $sql;
|
||||
}
|
||||
@ -1687,12 +1850,16 @@ class SqlWalker implements TreeWalker
|
||||
switch ($literal->type) {
|
||||
case AST\Literal::STRING:
|
||||
return $this->_conn->quote($literal->value);
|
||||
|
||||
case AST\Literal::BOOLEAN:
|
||||
$bool = strtolower($literal->value) == 'true' ? true : false;
|
||||
$boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool);
|
||||
return is_string($boolVal) ? $this->_conn->quote($boolVal) : $boolVal;
|
||||
|
||||
return $boolVal;
|
||||
|
||||
case AST\Literal::NUMERIC:
|
||||
return $literal->value;
|
||||
|
||||
default:
|
||||
throw QueryException::invalidLiteral($literal);
|
||||
}
|
||||
@ -1833,6 +2000,12 @@ class SqlWalker implements TreeWalker
|
||||
public function walkArithmeticTerm($term)
|
||||
{
|
||||
if (is_string($term)) {
|
||||
if (isset($this->_queryComponents[$term])) {
|
||||
$columnName = $this->_queryComponents[$term]['token']['value'];
|
||||
|
||||
return $this->_scalarResultAliasMap[$columnName];
|
||||
}
|
||||
|
||||
return $term;
|
||||
}
|
||||
|
||||
|
@ -240,7 +240,7 @@ class QueryBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root alias of the query. This is the first entity alias involved
|
||||
* Gets the root aliases of the query. This is the entity aliases involved
|
||||
* in the construction of the query.
|
||||
*
|
||||
* <code>
|
||||
@ -251,7 +251,7 @@ class QueryBuilder
|
||||
* $qb->getRootAliases(); // array('u')
|
||||
* </code>
|
||||
*
|
||||
* @return string $rootAlias
|
||||
* @return array $rootAliases
|
||||
*/
|
||||
public function getRootAliases()
|
||||
{
|
||||
@ -272,6 +272,39 @@ class QueryBuilder
|
||||
return $aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root entities of the query. This is the entity aliases involved
|
||||
* in the construction of the query.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $em->createQueryBuilder()
|
||||
* ->select('u')
|
||||
* ->from('User', 'u');
|
||||
*
|
||||
* $qb->getRootEntities(); // array('User')
|
||||
* </code>
|
||||
*
|
||||
* @return array $rootEntities
|
||||
*/
|
||||
public function getRootEntities()
|
||||
{
|
||||
$entities = array();
|
||||
|
||||
foreach ($this->_dqlParts['from'] as &$fromClause) {
|
||||
if (is_string($fromClause)) {
|
||||
$spacePos = strrpos($fromClause, ' ');
|
||||
$from = substr($fromClause, 0, $spacePos);
|
||||
$alias = substr($fromClause, $spacePos + 1);
|
||||
|
||||
$fromClause = new Query\Expr\From($from, $alias);
|
||||
}
|
||||
|
||||
$entities[] = $fromClause->getFrom();
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a query parameter for the query being constructed.
|
||||
*
|
||||
@ -290,6 +323,8 @@ class QueryBuilder
|
||||
*/
|
||||
public function setParameter($key, $value, $type = null)
|
||||
{
|
||||
$key = trim($key, ':');
|
||||
|
||||
if ($type === null) {
|
||||
$type = Query\ParameterTypeInferer::inferType($value);
|
||||
}
|
||||
@ -560,11 +595,12 @@ class QueryBuilder
|
||||
*
|
||||
* @param string $from The class name.
|
||||
* @param string $alias The alias of the class.
|
||||
* @param string $indexBy The index for the from.
|
||||
* @return QueryBuilder This QueryBuilder instance.
|
||||
*/
|
||||
public function from($from, $alias)
|
||||
public function from($from, $alias, $indexBy = null)
|
||||
{
|
||||
return $this->add('from', new Expr\From($from, $alias), true);
|
||||
return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -953,7 +989,7 @@ class QueryBuilder
|
||||
foreach ($fromParts as $from) {
|
||||
$fromClause = (string) $from;
|
||||
|
||||
if (isset($joinParts[$from->getAlias()])) {
|
||||
if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
|
||||
foreach ($joinParts[$from->getAlias()] as $join) {
|
||||
$fromClause .= ' ' . ((string) $join);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* 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
|
||||
@ -23,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console;
|
||||
Symfony\Component\Console,
|
||||
Doctrine\Common\Cache;
|
||||
|
||||
/**
|
||||
* Command to clear the metadata cache of the various cache drivers.
|
||||
@ -31,7 +30,6 @@ use Symfony\Component\Console\Input\InputArgument,
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @version $Revision$
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
@ -47,9 +45,30 @@ class MetadataCommand extends Console\Command\Command
|
||||
$this
|
||||
->setName('orm:clear-cache:metadata')
|
||||
->setDescription('Clear all metadata cache of the various cache drivers.')
|
||||
->setDefinition(array())
|
||||
->setHelp(<<<EOT
|
||||
Clear all metadata cache of the various cache drivers.
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'flush', null, InputOption::VALUE_NONE,
|
||||
'If defined, cache entries will be flushed instead of deleted/invalidated.'
|
||||
)
|
||||
));
|
||||
|
||||
$fullName = $this->getName();
|
||||
$this->setHelp(<<<EOT
|
||||
The <info>$fullName</info> command is meant to clear the metadata cache of associated Entity Manager.
|
||||
It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider
|
||||
instance completely.
|
||||
|
||||
The execution type differ on how you execute the command.
|
||||
If you want to invalidate the entries (and not delete from cache instance), this command would do the work:
|
||||
|
||||
<info>$fullName</info>
|
||||
|
||||
Alternatively, if you want to flush the cache provider using this command:
|
||||
|
||||
<info>$fullName --flush</info>
|
||||
|
||||
Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries,
|
||||
because of a limitation of its execution nature.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
@ -65,21 +84,21 @@ EOT
|
||||
if ( ! $cacheDriver) {
|
||||
throw new \InvalidArgumentException('No Metadata cache driver is configured on given EntityManager.');
|
||||
}
|
||||
|
||||
if ($cacheDriver instanceof \Doctrine\Common\Cache\ApcCache) {
|
||||
|
||||
if ($cacheDriver instanceof Cache\ApcCache) {
|
||||
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
|
||||
}
|
||||
|
||||
$output->write('Clearing ALL Metadata cache entries' . PHP_EOL);
|
||||
|
||||
$cacheIds = $cacheDriver->deleteAll();
|
||||
|
||||
if ($cacheIds) {
|
||||
foreach ($cacheIds as $cacheId) {
|
||||
$output->write(' - ' . $cacheId . PHP_EOL);
|
||||
}
|
||||
} else {
|
||||
$output->write('No entries to be deleted.' . PHP_EOL);
|
||||
$result = $cacheDriver->deleteAll();
|
||||
$message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.';
|
||||
|
||||
if (true === $input->getOption('flush')) {
|
||||
$result = $cacheDriver->flushAll();
|
||||
$message = ($result) ? 'Successfully flushed cache entries.' : $message;
|
||||
}
|
||||
|
||||
$output->write($message . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* 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
|
||||
@ -23,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console;
|
||||
Symfony\Component\Console,
|
||||
Doctrine\Common\Cache;
|
||||
|
||||
/**
|
||||
* Command to clear the query cache of the various cache drivers.
|
||||
@ -31,7 +30,6 @@ use Symfony\Component\Console\Input\InputArgument,
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @version $Revision$
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
@ -47,9 +45,30 @@ class QueryCommand extends Console\Command\Command
|
||||
$this
|
||||
->setName('orm:clear-cache:query')
|
||||
->setDescription('Clear all query cache of the various cache drivers.')
|
||||
->setDefinition(array())
|
||||
->setHelp(<<<EOT
|
||||
Clear all query cache of the various cache drivers.
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'flush', null, InputOption::VALUE_NONE,
|
||||
'If defined, cache entries will be flushed instead of deleted/invalidated.'
|
||||
)
|
||||
));
|
||||
|
||||
$fullName = $this->getName();
|
||||
$this->setHelp(<<<EOT
|
||||
The <info>$fullName</info> command is meant to clear the query cache of associated Entity Manager.
|
||||
It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider
|
||||
instance completely.
|
||||
|
||||
The execution type differ on how you execute the command.
|
||||
If you want to invalidate the entries (and not delete from cache instance), this command would do the work:
|
||||
|
||||
<info>$fullName</info>
|
||||
|
||||
Alternatively, if you want to flush the cache provider using this command:
|
||||
|
||||
<info>$fullName --flush</info>
|
||||
|
||||
Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries,
|
||||
because of a limitation of its execution nature.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
@ -65,21 +84,21 @@ EOT
|
||||
if ( ! $cacheDriver) {
|
||||
throw new \InvalidArgumentException('No Query cache driver is configured on given EntityManager.');
|
||||
}
|
||||
|
||||
if ($cacheDriver instanceof \Doctrine\Common\Cache\ApcCache) {
|
||||
|
||||
if ($cacheDriver instanceof Cache\ApcCache) {
|
||||
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
|
||||
}
|
||||
|
||||
$output->write('Clearing ALL Query cache entries' . PHP_EOL);
|
||||
|
||||
$cacheIds = $cacheDriver->deleteAll();
|
||||
|
||||
if ($cacheIds) {
|
||||
foreach ($cacheIds as $cacheId) {
|
||||
$output->write(' - ' . $cacheId . PHP_EOL);
|
||||
}
|
||||
} else {
|
||||
$output->write('No entries to be deleted.' . PHP_EOL);
|
||||
$result = $cacheDriver->deleteAll();
|
||||
$message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.';
|
||||
|
||||
if (true === $input->getOption('flush')) {
|
||||
$result = $cacheDriver->flushAll();
|
||||
$message = ($result) ? 'Successfully flushed cache entries.' : $message;
|
||||
}
|
||||
|
||||
$output->write($message . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* 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
|
||||
@ -23,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console;
|
||||
Symfony\Component\Console,
|
||||
Doctrine\Common\Cache;
|
||||
|
||||
/**
|
||||
* Command to clear the result cache of the various cache drivers.
|
||||
@ -31,7 +30,6 @@ use Symfony\Component\Console\Input\InputArgument,
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @version $Revision$
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
@ -46,28 +44,31 @@ class ResultCommand extends Console\Command\Command
|
||||
{
|
||||
$this
|
||||
->setName('orm:clear-cache:result')
|
||||
->setDescription('Clear result cache of the various cache drivers.')
|
||||
->setDescription('Clear all result cache of the various cache drivers.')
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'id', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'ID(s) of the cache entry to delete (accepts * wildcards).', array()
|
||||
),
|
||||
new InputOption(
|
||||
'regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'Delete cache entries that match the given regular expression(s).', array()
|
||||
),
|
||||
new InputOption(
|
||||
'prefix', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'Delete cache entries that have the given prefix(es).', array()
|
||||
),
|
||||
new InputOption(
|
||||
'suffix', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'Delete cache entries that have the given suffix(es).', array()
|
||||
),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
Clear result cache of the various cache drivers.
|
||||
If none of the options are defined, all cache entries will be removed.
|
||||
'flush', null, InputOption::VALUE_NONE,
|
||||
'If defined, cache entries will be flushed instead of deleted/invalidated.'
|
||||
)
|
||||
));
|
||||
|
||||
$fullName = $this->getName();
|
||||
$this->setHelp(<<<EOT
|
||||
The <info>$fullName</info> command is meant to clear the result cache of associated Entity Manager.
|
||||
It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider
|
||||
instance completely.
|
||||
|
||||
The execution type differ on how you execute the command.
|
||||
If you want to invalidate the entries (and not delete from cache instance), this command would do the work:
|
||||
|
||||
<info>$fullName</info>
|
||||
|
||||
Alternatively, if you want to flush the cache provider using this command:
|
||||
|
||||
<info>$fullName --flush</info>
|
||||
|
||||
Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries,
|
||||
because of a limitation of its execution nature.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
@ -83,86 +84,21 @@ EOT
|
||||
if ( ! $cacheDriver) {
|
||||
throw new \InvalidArgumentException('No Result cache driver is configured on given EntityManager.');
|
||||
}
|
||||
|
||||
if ($cacheDriver instanceof \Doctrine\Common\Cache\ApcCache) {
|
||||
|
||||
if ($cacheDriver instanceof Cache\ApcCache) {
|
||||
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
|
||||
}
|
||||
|
||||
$outputed = false;
|
||||
$output->write('Clearing ALL Result cache entries' . PHP_EOL);
|
||||
|
||||
// Removing based on --id
|
||||
if (($ids = $input->getOption('id')) !== null && $ids) {
|
||||
foreach ($ids as $id) {
|
||||
$output->write($outputed ? PHP_EOL : '');
|
||||
$output->write(sprintf('Clearing Result cache entries that match the id "<info>%s</info>"', $id) . PHP_EOL);
|
||||
|
||||
$deleted = $cacheDriver->delete($id);
|
||||
|
||||
if (is_array($deleted)) {
|
||||
$this->_printDeleted($output, $deleted);
|
||||
} else if (is_bool($deleted) && $deleted) {
|
||||
$this->_printDeleted($output, array($id));
|
||||
}
|
||||
|
||||
$outputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Removing based on --regex
|
||||
if (($regexps = $input->getOption('regex')) !== null && $regexps) {
|
||||
foreach($regexps as $regex) {
|
||||
$output->write($outputed ? PHP_EOL : '');
|
||||
$output->write(sprintf('Clearing Result cache entries that match the regular expression "<info>%s</info>"', $regex) . PHP_EOL);
|
||||
|
||||
$this->_printDeleted($output, $cacheDriver->deleteByRegex('/' . $regex. '/'));
|
||||
|
||||
$outputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Removing based on --prefix
|
||||
if (($prefixes = $input->getOption('prefix')) !== null & $prefixes) {
|
||||
foreach ($prefixes as $prefix) {
|
||||
$output->write($outputed ? PHP_EOL : '');
|
||||
$output->write(sprintf('Clearing Result cache entries that have the prefix "<info>%s</info>"', $prefix) . PHP_EOL);
|
||||
|
||||
$this->_printDeleted($output, $cacheDriver->deleteByPrefix($prefix));
|
||||
|
||||
$outputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Removing based on --suffix
|
||||
if (($suffixes = $input->getOption('suffix')) !== null && $suffixes) {
|
||||
foreach ($suffixes as $suffix) {
|
||||
$output->write($outputed ? PHP_EOL : '');
|
||||
$output->write(sprintf('Clearing Result cache entries that have the suffix "<info>%s</info>"', $suffix) . PHP_EOL);
|
||||
|
||||
$this->_printDeleted($output, $cacheDriver->deleteBySuffix($suffix));
|
||||
|
||||
$outputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Removing ALL entries
|
||||
if ( ! $ids && ! $regexps && ! $prefixes && ! $suffixes) {
|
||||
$output->write($outputed ? PHP_EOL : '');
|
||||
$output->write('Clearing ALL Result cache entries' . PHP_EOL);
|
||||
|
||||
$this->_printDeleted($output, $cacheDriver->deleteAll());
|
||||
|
||||
$outputed = true;
|
||||
$result = $cacheDriver->deleteAll();
|
||||
$message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.';
|
||||
|
||||
if (true === $input->getOption('flush')) {
|
||||
$result = $cacheDriver->flushAll();
|
||||
$message = ($result) ? 'Successfully flushed cache entries.' : $message;
|
||||
}
|
||||
|
||||
$output->write($message . PHP_EOL);
|
||||
}
|
||||
|
||||
private function _printDeleted(Console\Output\OutputInterface $output, array $items)
|
||||
{
|
||||
if ($items) {
|
||||
foreach ($items as $item) {
|
||||
$output->write(' - ' . $item . PHP_EOL);
|
||||
}
|
||||
} else {
|
||||
$output->write('No entries to be deleted.' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -78,6 +78,10 @@ class ConvertMappingCommand extends Console\Command\Command
|
||||
'num-spaces', null, InputOption::VALUE_OPTIONAL,
|
||||
'Defines the number of indentation spaces', 4
|
||||
),
|
||||
new InputOption(
|
||||
'namespace', null, InputOption::VALUE_OPTIONAL,
|
||||
'Defines a namespace for the generated entity classes, if converted from database.'
|
||||
),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
Convert mapping information between supported formats.
|
||||
@ -107,11 +111,17 @@ EOT
|
||||
$em = $this->getHelper('em')->getEntityManager();
|
||||
|
||||
if ($input->getOption('from-database') === true) {
|
||||
$em->getConfiguration()->setMetadataDriverImpl(
|
||||
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
|
||||
$em->getConnection()->getSchemaManager()
|
||||
)
|
||||
$databaseDriver = new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
|
||||
$em->getConnection()->getSchemaManager()
|
||||
);
|
||||
|
||||
$em->getConfiguration()->setMetadataDriverImpl(
|
||||
$databaseDriver
|
||||
);
|
||||
|
||||
if (($namespace = $input->getOption('namespace')) !== null) {
|
||||
$databaseDriver->setNamespace($namespace);
|
||||
}
|
||||
}
|
||||
|
||||
$cmf = new DisconnectedClassMetadataFactory();
|
||||
@ -127,7 +137,7 @@ EOT
|
||||
|
||||
if ( ! file_exists($destPath)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf("Mapping destination directory '<info>%s</info>' does not exist.", $destPath)
|
||||
sprintf("Mapping destination directory '<info>%s</info>' does not exist.", $input->getArgument('dest-path'))
|
||||
);
|
||||
} else if ( ! is_writable($destPath)) {
|
||||
throw new \InvalidArgumentException(
|
||||
@ -137,8 +147,7 @@ EOT
|
||||
|
||||
$toType = strtolower($input->getArgument('to-type'));
|
||||
|
||||
$cme = new ClassMetadataExporter();
|
||||
$exporter = $cme->getExporter($toType, $destPath);
|
||||
$exporter = $this->getExporter($toType, $destPath);
|
||||
$exporter->setOverwriteExistingFiles( ($input->getOption('force') !== false) );
|
||||
|
||||
if ($toType == 'annotation') {
|
||||
@ -167,4 +176,11 @@ EOT
|
||||
$output->write('No Metadata Classes to process.' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getExporter($toType, $destPath)
|
||||
{
|
||||
$cme = new ClassMetadataExporter();
|
||||
|
||||
return $cme->getExporter($toType, $destPath);
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ EOT
|
||||
|
||||
if ( ! file_exists($destPath)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf("Entities destination directory '<info>%s</info>' does not exist.", $destPath)
|
||||
sprintf("Entities destination directory '<info>%s</info>' does not exist.", $input->getArgument('dest-path'))
|
||||
);
|
||||
} else if ( ! is_writable($destPath)) {
|
||||
throw new \InvalidArgumentException(
|
||||
|
@ -87,7 +87,7 @@ EOT
|
||||
|
||||
if ( ! file_exists($destPath)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf("Proxies destination directory '<info>%s</info>' does not exist.", $destPath)
|
||||
sprintf("Proxies destination directory '<info>%s</info>' does not exist.", $em->getConfiguration()->getProxyDir())
|
||||
);
|
||||
} else if ( ! is_writable($destPath)) {
|
||||
throw new \InvalidArgumentException(
|
||||
|
@ -79,7 +79,7 @@ EOT
|
||||
|
||||
if ( ! file_exists($destPath)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf("Entities destination directory '<info>%s</info>' does not exist.", $destPath)
|
||||
sprintf("Entities destination directory '<info>%s</info>' does not exist.", $input->getArgument('dest-path'))
|
||||
);
|
||||
} else if ( ! is_writable($destPath)) {
|
||||
throw new \InvalidArgumentException(
|
||||
|
@ -28,7 +28,8 @@ use Symfony\Component\Console\Input\InputArgument,
|
||||
Doctrine\ORM\Tools\SchemaTool;
|
||||
|
||||
/**
|
||||
* Command to update the database schema for a set of classes based on their mappings.
|
||||
* Command to generate the SQL needed to update the database schema to match
|
||||
* the current mapping information.
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
@ -38,37 +39,58 @@ use Symfony\Component\Console\Input\InputArgument,
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Ryan Weaver <ryan@thatsquality.com>
|
||||
*/
|
||||
class UpdateCommand extends AbstractCommand
|
||||
{
|
||||
protected $name = 'orm:schema-tool:update';
|
||||
|
||||
/**
|
||||
* @see Console\Command\Command
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('orm:schema-tool:update')
|
||||
->setName($this->name)
|
||||
->setDescription(
|
||||
'Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.'
|
||||
'Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata.'
|
||||
)
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'complete', null, InputOption::VALUE_NONE,
|
||||
'If defined, all assets of the database which are not relevant to the current metadata will be dropped.'
|
||||
),
|
||||
|
||||
new InputOption(
|
||||
'dump-sql', null, InputOption::VALUE_NONE,
|
||||
'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.'
|
||||
'Dumps the generated SQL statements to the screen (does not execute them).'
|
||||
),
|
||||
new InputOption(
|
||||
'force', null, InputOption::VALUE_NONE,
|
||||
"Don't ask for the incremental update of the database, but force the operation to run."
|
||||
'Causes the generated SQL statements to be physically executed against your database.'
|
||||
),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.
|
||||
Beware that if --complete is not defined, it will do a save update, which does not delete any tables, sequences or affected foreign keys.
|
||||
If defined, all assets of the database which are not relevant to the current metadata are dropped by this command.
|
||||
));
|
||||
|
||||
$fullName = $this->getName();
|
||||
$this->setHelp(<<<EOT
|
||||
The <info>$fullName</info> command generates the SQL needed to
|
||||
synchronize the database schema with the current mapping metadata of the
|
||||
default entity manager.
|
||||
|
||||
For example, if you add metadata for a new column to an entity, this command
|
||||
would generate and output the SQL needed to add the new column to the database:
|
||||
|
||||
<info>$fullName --dump-sql</info>
|
||||
|
||||
Alternatively, you can execute the generated queries:
|
||||
|
||||
<info>$fullName --force</info>
|
||||
|
||||
Finally, be aware that if the <info>--complete</info> option is passed, this
|
||||
task will drop all database assets (e.g. tables, etc) that are *not* described
|
||||
by the current metadata. In other words, without this option, this task leaves
|
||||
untouched any "extra" tables that exist in the database, but which aren't
|
||||
described by any metadata.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
@ -78,26 +100,36 @@ EOT
|
||||
// Defining if update is complete or not (--complete not defined means $saveMode = true)
|
||||
$saveMode = ($input->getOption('complete') !== true);
|
||||
|
||||
if ($input->getOption('dump-sql') === true) {
|
||||
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
|
||||
$output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);
|
||||
} else if ($input->getOption('force') === true) {
|
||||
$output->write('Updating database schema...' . PHP_EOL);
|
||||
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
|
||||
if (0 == count($sqls)) {
|
||||
$output->writeln('Nothing to update - your database is already in sync with the current entity metadata.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$dumpSql = (true === $input->getOption('dump-sql'));
|
||||
$force = (true === $input->getOption('force'));
|
||||
if ($dumpSql && $force) {
|
||||
throw new \InvalidArgumentException('You can pass either the --dump-sql or the --force option (but not both simultaneously).');
|
||||
}
|
||||
|
||||
if ($dumpSql) {
|
||||
$output->writeln(implode(';' . PHP_EOL, $sqls));
|
||||
} else if ($force) {
|
||||
$output->writeln('Updating database schema...');
|
||||
$schemaTool->updateSchema($metadatas, $saveMode);
|
||||
$output->write('Database schema updated successfully!' . PHP_EOL);
|
||||
$output->writeln(sprintf('Database schema updated successfully! "<info>%s</info>" queries were executed', count($sqls)));
|
||||
} else {
|
||||
$output->write('ATTENTION: This operation should not be executed in a production environment.' . PHP_EOL);
|
||||
$output->write('Use the incremental update to detect changes during development and use' . PHP_EOL);
|
||||
$output->write('this SQL DDL to manually update your database in production.' . PHP_EOL . PHP_EOL);
|
||||
$output->writeln('<comment>ATTENTION</comment>: This operation should not be executed in a production environment.');
|
||||
$output->writeln(' Use the incremental update to detect changes during development and use');
|
||||
$output->writeln(' the SQL DDL provided to manually update your database in production.');
|
||||
$output->writeln('');
|
||||
|
||||
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
|
||||
|
||||
if (count($sqls)) {
|
||||
$output->write('Schema-Tool would execute ' . count($sqls) . ' queries to update the database.' . PHP_EOL);
|
||||
$output->write('Please run the operation with --force to execute these queries or use --dump-sql to see them.' . PHP_EOL);
|
||||
} else {
|
||||
$output->write('Nothing to update. The database is in sync with the current entity metadata.' . PHP_EOL);
|
||||
}
|
||||
$output->writeln(sprintf('The Schema-Tool would execute <info>"%s"</info> queries to update the database.', count($sqls)));
|
||||
$output->writeln('Please run the operation by passing one of the following options:');
|
||||
|
||||
$output->writeln(sprintf(' <info>%s --force</info> to execute the command', $this->getName()));
|
||||
$output->writeln(sprintf(' <info>%s --dump-sql</info> to dump the SQL statements to the screen', $this->getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class ValidateSchemaCommand extends Console\Command\Command
|
||||
{
|
||||
$this
|
||||
->setName('orm:validate-schema')
|
||||
->setDescription('Validate that the mapping files.')
|
||||
->setDescription('Validate the mapping files.')
|
||||
->setHelp(<<<EOT
|
||||
'Validate that the mapping files are correct and in sync with the database.'
|
||||
EOT
|
||||
|
@ -70,10 +70,10 @@ class ConvertDoctrine1Schema
|
||||
if (is_dir($path)) {
|
||||
$files = glob($path . '/*.yml');
|
||||
foreach ($files as $file) {
|
||||
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::load($file));
|
||||
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::parse($file));
|
||||
}
|
||||
} else {
|
||||
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::load($path));
|
||||
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::parse($path));
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,6 +101,7 @@ class ConvertDoctrine1Schema
|
||||
{
|
||||
if (isset($model['tableName']) && $model['tableName']) {
|
||||
$e = explode('.', $model['tableName']);
|
||||
|
||||
if (count($e) > 1) {
|
||||
$metadata->table['schema'] = $e[0];
|
||||
$metadata->table['name'] = $e[1];
|
||||
@ -248,7 +249,6 @@ class ConvertDoctrine1Schema
|
||||
'name' => $relation['local'],
|
||||
'referencedColumnName' => $relation['foreign'],
|
||||
'onDelete' => isset($relation['onDelete']) ? $relation['onDelete'] : null,
|
||||
'onUpdate' => isset($relation['onUpdate']) ? $relation['onUpdate'] : null,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
151
lib/Doctrine/ORM/Tools/DebugUnitOfWorkListener.php
Normal file
151
lib/Doctrine/ORM/Tools/DebugUnitOfWorkListener.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Tools;
|
||||
|
||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
|
||||
/**
|
||||
* Use this logger to dump the identity map during the onFlush event. This is useful for debugging
|
||||
* weird UnitOfWork behavior with complex operations.
|
||||
*/
|
||||
class DebugUnitOfWorkListener
|
||||
{
|
||||
private $file;
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Pass a stream and contet information for the debugging session.
|
||||
*
|
||||
* The stream can be php://output to print to the screen.
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $context
|
||||
*/
|
||||
public function __construct($file = 'php://output', $context = '')
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function onFlush(OnFlushEventArgs $args)
|
||||
{
|
||||
$this->dumpIdentityMap($args->getEntityManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the contents of the identity map into a stream.
|
||||
*
|
||||
* @param EntityManager $em
|
||||
* @return void
|
||||
*/
|
||||
public function dumpIdentityMap(EntityManager $em)
|
||||
{
|
||||
$uow = $em->getUnitOfWork();
|
||||
$identityMap = $uow->getIdentityMap();
|
||||
|
||||
$fh = fopen($this->file, "x+");
|
||||
if (count($identityMap) == 0) {
|
||||
fwrite($fh, "Flush Operation [".$this->context."] - Empty identity map.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
fwrite($fh, "Flush Operation [".$this->context."] - Dumping identity map:\n");
|
||||
foreach ($identityMap AS $className => $map) {
|
||||
fwrite($fh, "Class: ". $className . "\n");
|
||||
foreach ($map AS $idHash => $entity) {
|
||||
fwrite($fh, " Entity: " . $this->getIdString($entity, $uow) . " " . spl_object_hash($entity)."\n");
|
||||
fwrite($fh, " Associations:\n");
|
||||
|
||||
$cm = $em->getClassMetadata($className);
|
||||
foreach ($cm->associationMappings AS $field => $assoc) {
|
||||
fwrite($fh, " " . $field . " ");
|
||||
$value = $cm->reflFields[$field]->getValue($entity);
|
||||
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
if ($value === null) {
|
||||
fwrite($fh, " NULL\n");
|
||||
} else {
|
||||
if ($value instanceof Proxy && !$value->__isInitialized__) {
|
||||
fwrite($fh, "[PROXY] ");
|
||||
}
|
||||
|
||||
fwrite($fh, $this->getIdString($value, $uow) . " " . spl_object_hash($value) . "\n");
|
||||
}
|
||||
} else {
|
||||
$initialized = !($value instanceof PersistentCollection) || $value->isInitialized();
|
||||
if ($value === null) {
|
||||
fwrite($fh, " NULL\n");
|
||||
} else if ($initialized) {
|
||||
fwrite($fh, "[INITIALIZED] " . $this->getType($value). " " . count($value) . " elements\n");
|
||||
foreach ($value AS $obj) {
|
||||
fwrite($fh, " " . $this->getIdString($obj, $uow) . " " . spl_object_hash($obj)."\n");
|
||||
}
|
||||
} else {
|
||||
fwrite($fh, "[PROXY] " . $this->getType($value) . " unknown element size\n");
|
||||
foreach ($value->unwrap() AS $obj) {
|
||||
fwrite($fh, " " . $this->getIdString($obj, $uow) . " " . spl_object_hash($obj)."\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
private function getType($var)
|
||||
{
|
||||
if (is_object($var)) {
|
||||
$refl = new \ReflectionObject($var);
|
||||
return $refl->getShortname();
|
||||
} else {
|
||||
return gettype($var);
|
||||
}
|
||||
}
|
||||
|
||||
private function getIdString($entity, $uow)
|
||||
{
|
||||
if ($uow->isInIdentityMap($entity)) {
|
||||
$ids = $uow->getEntityIdentifier($entity);
|
||||
$idstring = "";
|
||||
foreach ($ids AS $k => $v) {
|
||||
$idstring .= $k."=".$v;
|
||||
}
|
||||
} else {
|
||||
$idstring = "NEWOBJECT ";
|
||||
}
|
||||
|
||||
$state = $uow->getEntityState($entity);
|
||||
if ($state == UnitOfWork::STATE_NEW) {
|
||||
$idstring .= " [NEW]";
|
||||
} else if ($state == UnitOfWork::STATE_REMOVED) {
|
||||
$idstring .= " [REMOVED]";
|
||||
} else if ($state == UnitOfWork::STATE_MANAGED) {
|
||||
$idstring .= " [MANAGED]";
|
||||
} else if ($state == UnitOfwork::STATE_DETACHED) {
|
||||
$idstring .= " [DETACHED]";
|
||||
}
|
||||
|
||||
return $idstring;
|
||||
}
|
||||
}
|
@ -52,6 +52,17 @@ class DisconnectedClassMetadataFactory extends ClassMetadataFactory
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate runtime metadata is correctly defined.
|
||||
*
|
||||
* @param ClassMetadata $class
|
||||
* @param ClassMetadata $parent
|
||||
*/
|
||||
protected function validateRuntimeMetadata($class, $parent)
|
||||
{
|
||||
// validate nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
|
@ -91,6 +91,8 @@ class EntityGenerator
|
||||
|
||||
<namespace>
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
<entityAnnotation>
|
||||
<entityClassName>
|
||||
{
|
||||
@ -101,7 +103,7 @@ class EntityGenerator
|
||||
'/**
|
||||
* <description>
|
||||
*
|
||||
* @return <variableType>$<variableName>
|
||||
* @return <variableType>
|
||||
*/
|
||||
public function <methodName>()
|
||||
{
|
||||
@ -146,11 +148,18 @@ public function <methodName>()
|
||||
}
|
||||
';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
|
||||
$this->_annotationsPrefix = 'ORM\\';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and write entity classes for the given array of ClassMetadataInfo instances
|
||||
*
|
||||
* @param array $metadatas
|
||||
* @param string $outputDirectory
|
||||
* @param string $outputDirectory
|
||||
* @return void
|
||||
*/
|
||||
public function generate(array $metadatas, $outputDirectory)
|
||||
@ -164,7 +173,7 @@ public function <methodName>()
|
||||
* Generated and write entity class to disk for the given ClassMetadataInfo instance
|
||||
*
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @param string $outputDirectory
|
||||
* @param string $outputDirectory
|
||||
* @return void
|
||||
*/
|
||||
public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
|
||||
@ -201,7 +210,7 @@ public function <methodName>()
|
||||
/**
|
||||
* Generate a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance
|
||||
*
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @return string $code
|
||||
*/
|
||||
public function generateEntityClass(ClassMetadataInfo $metadata)
|
||||
@ -227,8 +236,8 @@ public function <methodName>()
|
||||
/**
|
||||
* Generate the updated code for the given ClassMetadataInfo and entity at path
|
||||
*
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @param string $path
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @param string $path
|
||||
* @return string $code;
|
||||
*/
|
||||
public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
|
||||
@ -245,7 +254,7 @@ public function <methodName>()
|
||||
/**
|
||||
* Set the number of spaces the exported class should have
|
||||
*
|
||||
* @param integer $numSpaces
|
||||
* @param integer $numSpaces
|
||||
* @return void
|
||||
*/
|
||||
public function setNumSpaces($numSpaces)
|
||||
@ -257,7 +266,7 @@ public function <methodName>()
|
||||
/**
|
||||
* Set the extension to use when writing php files to disk
|
||||
*
|
||||
* @param string $extension
|
||||
* @param string $extension
|
||||
* @return void
|
||||
*/
|
||||
public function setExtension($extension)
|
||||
@ -278,7 +287,7 @@ public function <methodName>()
|
||||
/**
|
||||
* Set whether or not to generate annotations for the entity
|
||||
*
|
||||
* @param bool $bool
|
||||
* @param bool $bool
|
||||
* @return void
|
||||
*/
|
||||
public function setGenerateAnnotations($bool)
|
||||
@ -293,13 +302,16 @@ public function <methodName>()
|
||||
*/
|
||||
public function setAnnotationPrefix($prefix)
|
||||
{
|
||||
if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
|
||||
return;
|
||||
}
|
||||
$this->_annotationsPrefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not to try and update the entity if it already exists
|
||||
*
|
||||
* @param bool $bool
|
||||
* @param bool $bool
|
||||
* @return void
|
||||
*/
|
||||
public function setUpdateEntityIfExists($bool)
|
||||
@ -387,14 +399,17 @@ public function <methodName>()
|
||||
}
|
||||
|
||||
$collections = array();
|
||||
|
||||
foreach ($metadata->associationMappings AS $mapping) {
|
||||
if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
|
||||
$collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
|
||||
}
|
||||
}
|
||||
|
||||
if ($collections) {
|
||||
return $this->_prefixCodeWithSpaces(str_replace("<collections>", implode("\n", $collections), self::$_constructorMethodTemplate));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -407,7 +422,7 @@ public function <methodName>()
|
||||
$tokens = token_get_all($src);
|
||||
$lastSeenNamespace = "";
|
||||
$lastSeenClass = false;
|
||||
|
||||
|
||||
$inNamespace = false;
|
||||
$inClass = false;
|
||||
for ($i = 0; $i < count($tokens); $i++) {
|
||||
@ -426,7 +441,7 @@ public function <methodName>()
|
||||
|
||||
if ($inClass) {
|
||||
$inClass = false;
|
||||
$lastSeenClass = $lastSeenNamespace . '\\' . $token[1];
|
||||
$lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
|
||||
$this->_staticReflection[$lastSeenClass]['properties'] = array();
|
||||
$this->_staticReflection[$lastSeenClass]['methods'] = array();
|
||||
}
|
||||
@ -439,7 +454,7 @@ public function <methodName>()
|
||||
} else if ($token[0] == T_FUNCTION) {
|
||||
if ($tokens[$i+2][0] == T_STRING) {
|
||||
$this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+2][1];
|
||||
} else if ($tokens[$i+2][0] == T_AMPERSAND && $tokens[$i+3][0] == T_STRING) {
|
||||
} else if ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) {
|
||||
$this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+3][1];
|
||||
}
|
||||
} else if (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) {
|
||||
@ -450,6 +465,14 @@ public function <methodName>()
|
||||
|
||||
private function _hasProperty($property, ClassMetadataInfo $metadata)
|
||||
{
|
||||
if ($this->_extendsClass()) {
|
||||
// don't generate property if its already on the base class.
|
||||
$reflClass = new \ReflectionClass($this->_getClassToExtend());
|
||||
if ($reflClass->hasProperty($property)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
isset($this->_staticReflection[$metadata->name]) &&
|
||||
in_array($property, $this->_staticReflection[$metadata->name]['properties'])
|
||||
@ -458,6 +481,14 @@ public function <methodName>()
|
||||
|
||||
private function _hasMethod($method, ClassMetadataInfo $metadata)
|
||||
{
|
||||
if ($this->_extendsClass()) {
|
||||
// don't generate method if its already on the base class.
|
||||
$reflClass = new \ReflectionClass($this->_getClassToExtend());
|
||||
if ($reflClass->hasMethod($method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
isset($this->_staticReflection[$metadata->name]) &&
|
||||
in_array($method, $this->_staticReflection[$metadata->name]['methods'])
|
||||
@ -542,7 +573,12 @@ public function <methodName>()
|
||||
private function _generateTableAnnotation($metadata)
|
||||
{
|
||||
$table = array();
|
||||
if ($metadata->table['name']) {
|
||||
|
||||
if (isset($metadata->table['schema'])) {
|
||||
$table[] = 'schema="' . $metadata->table['schema'] . '"';
|
||||
}
|
||||
|
||||
if (isset($metadata->table['name'])) {
|
||||
$table[] = 'name="' . $metadata->table['name'] . '"';
|
||||
}
|
||||
|
||||
@ -674,11 +710,18 @@ public function <methodName>()
|
||||
|
||||
private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null)
|
||||
{
|
||||
$methodName = $type . Inflector::classify($fieldName);
|
||||
if ($type == "add") {
|
||||
$addMethod = explode("\\", $typeHint);
|
||||
$addMethod = end($addMethod);
|
||||
$methodName = $type . $addMethod;
|
||||
} else {
|
||||
$methodName = $type . Inflector::classify($fieldName);
|
||||
}
|
||||
|
||||
if ($this->_hasMethod($methodName, $metadata)) {
|
||||
return;
|
||||
}
|
||||
$this->_staticReflection[$metadata->name]['methods'][] = $methodName;
|
||||
|
||||
$var = sprintf('_%sMethodTemplate', $type);
|
||||
$template = self::$$var;
|
||||
@ -711,6 +754,7 @@ public function <methodName>()
|
||||
if ($this->_hasMethod($methodName, $metadata)) {
|
||||
return;
|
||||
}
|
||||
$this->_staticReflection[$metadata->name]['methods'][] = $methodName;
|
||||
|
||||
$replacements = array(
|
||||
'<name>' => $this->_annotationsPrefix . $name,
|
||||
@ -750,10 +794,6 @@ public function <methodName>()
|
||||
$joinColumnAnnot[] = 'onDelete=' . ($joinColumn['onDelete'] ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if (isset($joinColumn['onUpdate'])) {
|
||||
$joinColumnAnnot[] = 'onUpdate=' . ($joinColumn['onUpdate'] ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if (isset($joinColumn['columnDefinition'])) {
|
||||
$joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
|
||||
}
|
||||
@ -808,7 +848,7 @@ public function <methodName>()
|
||||
if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
|
||||
if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
|
||||
|
||||
$typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
|
||||
$typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
|
||||
}
|
||||
|
||||
if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
|
||||
@ -862,7 +902,7 @@ public function <methodName>()
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'OrderBy({';
|
||||
|
||||
foreach ($associationMapping['orderBy'] as $name => $direction) {
|
||||
$lines[] = $this->_spaces . ' * "' . $name . '"="' . $direction . '",';
|
||||
$lines[] = $this->_spaces . ' * "' . $name . '"="' . $direction . '",';
|
||||
}
|
||||
|
||||
$lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
|
||||
|
@ -120,22 +120,30 @@ class PhpExporter extends AbstractExporter
|
||||
|
||||
$associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray);
|
||||
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
|
||||
$method = 'mapOneToMany';
|
||||
$oneToManyMappingArray = array(
|
||||
'mappedBy' => $associationMapping['mappedBy'],
|
||||
'orphanRemoval' => $associationMapping['orphanRemoval'],
|
||||
'orderBy' => $associationMapping['orderBy']
|
||||
$method = 'mapOneToMany';
|
||||
$potentialAssociationMappingIndexes = array(
|
||||
'mappedBy',
|
||||
'orphanRemoval',
|
||||
'orderBy',
|
||||
);
|
||||
|
||||
foreach ($potentialAssociationMappingIndexes as $index) {
|
||||
if (isset($associationMapping[$index])) {
|
||||
$oneToManyMappingArray[$index] = $associationMapping[$index];
|
||||
}
|
||||
}
|
||||
$associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray);
|
||||
} else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) {
|
||||
$method = 'mapManyToMany';
|
||||
$manyToManyMappingArray = array(
|
||||
'mappedBy' => $associationMapping['mappedBy'],
|
||||
'joinTable' => $associationMapping['joinTable'],
|
||||
'orderBy' => $associationMapping['orderBy']
|
||||
$method = 'mapManyToMany';
|
||||
$potentialAssociationMappingIndexes = array(
|
||||
'mappedBy',
|
||||
'joinTable',
|
||||
'orderBy',
|
||||
);
|
||||
|
||||
foreach ($potentialAssociationMappingIndexes as $index) {
|
||||
if (isset($associationMapping[$index])) {
|
||||
$manyToManyMappingArray[$index] = $associationMapping[$index];
|
||||
}
|
||||
}
|
||||
$associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray);
|
||||
}
|
||||
|
||||
|
@ -215,9 +215,6 @@ class XmlExporter extends AbstractExporter
|
||||
if (isset($joinColumn['onDelete'])) {
|
||||
$joinColumnXml->addAttribute('on-delete', $joinColumn['onDelete']);
|
||||
}
|
||||
if (isset($joinColumn['onUpdate'])) {
|
||||
$joinColumnXml->addAttribute('on-update', $joinColumn['onUpdate']);
|
||||
}
|
||||
}
|
||||
$inverseJoinColumnsXml = $joinTableXml->addChild('inverse-join-columns');
|
||||
foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $inverseJoinColumn) {
|
||||
@ -227,9 +224,6 @@ class XmlExporter extends AbstractExporter
|
||||
if (isset($inverseJoinColumn['onDelete'])) {
|
||||
$inverseJoinColumnXml->addAttribute('on-delete', $inverseJoinColumn['onDelete']);
|
||||
}
|
||||
if (isset($inverseJoinColumn['onUpdate'])) {
|
||||
$inverseJoinColumnXml->addAttribute('on-update', $inverseJoinColumn['onUpdate']);
|
||||
}
|
||||
if (isset($inverseJoinColumn['columnDefinition'])) {
|
||||
$inverseJoinColumnXml->addAttribute('column-definition', $inverseJoinColumn['columnDefinition']);
|
||||
}
|
||||
@ -250,9 +244,6 @@ class XmlExporter extends AbstractExporter
|
||||
if (isset($joinColumn['onDelete'])) {
|
||||
$joinColumnXml->addAttribute('on-delete', $joinColumn['onDelete']);
|
||||
}
|
||||
if (isset($joinColumn['onUpdate'])) {
|
||||
$joinColumnXml->addAttribute('on-update', $joinColumn['onUpdate']);
|
||||
}
|
||||
if (isset($joinColumn['columnDefinition'])) {
|
||||
$joinColumnXml->addAttribute('column-definition', $joinColumn['columnDefinition']);
|
||||
}
|
||||
|
@ -43,17 +43,19 @@ class YamlExporter extends AbstractExporter
|
||||
*
|
||||
* TODO: Should this code be pulled out in to a toArray() method in ClassMetadata
|
||||
*
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @return mixed $exported
|
||||
*/
|
||||
public function exportClassMetadata(ClassMetadataInfo $metadata)
|
||||
{
|
||||
$array = array();
|
||||
|
||||
if ($metadata->isMappedSuperclass) {
|
||||
$array['type'] = 'mappedSuperclass';
|
||||
} else {
|
||||
$array['type'] = 'entity';
|
||||
}
|
||||
|
||||
$array['table'] = $metadata->table['name'];
|
||||
|
||||
if (isset($metadata->table['schema'])) {
|
||||
@ -81,12 +83,16 @@ class YamlExporter extends AbstractExporter
|
||||
$array['indexes'] = $metadata->table['indexes'];
|
||||
}
|
||||
|
||||
if ($metadata->customRepositoryClassName) {
|
||||
$array['repositoryClass'] = $metadata->customRepositoryClassName;
|
||||
}
|
||||
|
||||
if (isset($metadata->table['uniqueConstraints'])) {
|
||||
$array['uniqueConstraints'] = $metadata->table['uniqueConstraints'];
|
||||
}
|
||||
|
||||
|
||||
$fieldMappings = $metadata->fieldMappings;
|
||||
|
||||
|
||||
$ids = array();
|
||||
foreach ($fieldMappings as $name => $fieldMapping) {
|
||||
$fieldMapping['column'] = $fieldMapping['columnName'];
|
||||
@ -94,7 +100,7 @@ class YamlExporter extends AbstractExporter
|
||||
$fieldMapping['columnName'],
|
||||
$fieldMapping['fieldName']
|
||||
);
|
||||
|
||||
|
||||
if ($fieldMapping['column'] == $name) {
|
||||
unset($fieldMapping['column']);
|
||||
}
|
||||
@ -111,7 +117,7 @@ class YamlExporter extends AbstractExporter
|
||||
if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
|
||||
$ids[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $this->_getIdGeneratorTypeString($metadata->generatorType);
|
||||
}
|
||||
|
||||
|
||||
if ($ids) {
|
||||
$array['fields'] = $ids;
|
||||
}
|
||||
@ -145,7 +151,7 @@ class YamlExporter extends AbstractExporter
|
||||
'targetEntity' => $associationMapping['targetEntity'],
|
||||
'cascade' => $cascade,
|
||||
);
|
||||
|
||||
|
||||
if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
|
||||
$joinColumns = $associationMapping['joinColumns'];
|
||||
$newJoinColumns = array();
|
||||
@ -154,9 +160,6 @@ class YamlExporter extends AbstractExporter
|
||||
if (isset($joinColumn['onDelete'])) {
|
||||
$newJoinColumns[$joinColumn['name']]['onDelete'] = $joinColumn['onDelete'];
|
||||
}
|
||||
if (isset($joinColumn['onUpdate'])) {
|
||||
$newJoinColumns[$joinColumn['name']]['onUpdate'] = $joinColumn['onUpdate'];
|
||||
}
|
||||
}
|
||||
$oneToOneMappingArray = array(
|
||||
'mappedBy' => $associationMapping['mappedBy'],
|
||||
@ -164,7 +167,7 @@ class YamlExporter extends AbstractExporter
|
||||
'joinColumns' => $newJoinColumns,
|
||||
'orphanRemoval' => $associationMapping['orphanRemoval'],
|
||||
);
|
||||
|
||||
|
||||
$associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray);
|
||||
$array['oneToOne'][$name] = $associationMappingArray;
|
||||
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
|
||||
@ -172,7 +175,7 @@ class YamlExporter extends AbstractExporter
|
||||
'mappedBy' => $associationMapping['mappedBy'],
|
||||
'inversedBy' => $associationMapping['inversedBy'],
|
||||
'orphanRemoval' => $associationMapping['orphanRemoval'],
|
||||
'orderBy' => $associationMapping['orderBy']
|
||||
'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null
|
||||
);
|
||||
|
||||
$associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray);
|
||||
@ -181,10 +184,10 @@ class YamlExporter extends AbstractExporter
|
||||
$manyToManyMappingArray = array(
|
||||
'mappedBy' => $associationMapping['mappedBy'],
|
||||
'inversedBy' => $associationMapping['inversedBy'],
|
||||
'joinTable' => $associationMapping['joinTable'],
|
||||
'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null
|
||||
'joinTable' => isset($associationMapping['joinTable']) ? $associationMapping['joinTable'] : null,
|
||||
'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null
|
||||
);
|
||||
|
||||
|
||||
$associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray);
|
||||
$array['manyToMany'][$name] = $associationMappingArray;
|
||||
}
|
||||
|
@ -523,10 +523,6 @@ class SchemaTool
|
||||
$uniqueConstraints[] = array('columns' => array($columnName));
|
||||
}
|
||||
|
||||
if (isset($joinColumn['onUpdate'])) {
|
||||
$fkOptions['onUpdate'] = $joinColumn['onUpdate'];
|
||||
}
|
||||
|
||||
if (isset($joinColumn['onDelete'])) {
|
||||
$fkOptions['onDelete'] = $joinColumn['onDelete'];
|
||||
}
|
||||
@ -619,6 +615,24 @@ class SchemaTool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_platform->supportsSequences()) {
|
||||
foreach ($schema->getSequences() AS $sequence) {
|
||||
$visitor->acceptSequence($sequence);
|
||||
}
|
||||
foreach ($schema->getTables() AS $table) {
|
||||
/* @var $sequence Table */
|
||||
if ($table->hasPrimaryKey()) {
|
||||
$columns = $table->getPrimaryKey()->getColumns();
|
||||
if (count($columns) == 1) {
|
||||
$checkSequence = $table->getName() . "_" . $columns[0] . "_seq";
|
||||
if ($fullSchema->hasSequence($checkSequence)) {
|
||||
$visitor->acceptSequence($fullSchema->getSequence($checkSequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $visitor->getQueries();
|
||||
}
|
||||
|
194
lib/Doctrine/ORM/Tools/Setup.php
Normal file
194
lib/Doctrine/ORM/Tools/Setup.php
Normal file
@ -0,0 +1,194 @@
|
||||
<?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 LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Tools;
|
||||
|
||||
use Doctrine\Common\ClassLoader;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\Mapping\Driver\XmlDriver;
|
||||
use Doctrine\ORM\Mapping\Driver\YamlDriver;
|
||||
|
||||
/**
|
||||
* Convenience class for setting up Doctrine from different installations and configurations.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class Setup
|
||||
{
|
||||
/**
|
||||
* Use this method to register all autoloaders for a setup where Doctrine is checked out from
|
||||
* its github repository at {@link http://github.com/doctrine/doctrine2}
|
||||
*
|
||||
* @param string $gitCheckoutRootPath
|
||||
* @return void
|
||||
*/
|
||||
static public function registerAutoloadGit($gitCheckoutRootPath)
|
||||
{
|
||||
if (!class_exists('Doctrine\Common\ClassLoader', false)) {
|
||||
require_once $gitCheckoutRootPath . "/lib/vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php";
|
||||
}
|
||||
|
||||
$loader = new ClassLoader("Doctrine\Common", $gitCheckoutRootPath . "/lib/vendor/doctrine-common/lib");
|
||||
$loader->register();
|
||||
|
||||
$loader = new ClassLoader("Doctrine\DBAL", $gitCheckoutRootPath . "/lib/vendor/doctrine-dbal/lib");
|
||||
$loader->register();
|
||||
|
||||
$loader = new ClassLoader("Doctrine\ORM", $gitCheckoutRootPath . "/lib");
|
||||
$loader->register();
|
||||
|
||||
$loader = new ClassLoader("Symfony\Component", $gitCheckoutRootPath . "/lib/vendor");
|
||||
$loader->register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to register all autoloaders for a setup where Doctrine is installed
|
||||
* though {@link http://pear.doctrine-project.org}.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
static public function registerAutoloadPEAR()
|
||||
{
|
||||
if (!class_exists('Doctrine\Common\ClassLoader', false)) {
|
||||
require_once "Doctrine/Common/ClassLoader.php";
|
||||
}
|
||||
|
||||
$loader = new ClassLoader("Doctrine");
|
||||
$loader->register();
|
||||
|
||||
$parts = explode(PATH_SEPARATOR, get_include_path());
|
||||
|
||||
foreach ($parts AS $includePath) {
|
||||
if ($includePath != "." && file_exists($includePath . "/Doctrine")) {
|
||||
$loader = new ClassLoader("Symfony\Component", $includePath . "/Doctrine");
|
||||
$loader->register();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to register all autoloads for a downloaded Doctrine library.
|
||||
* Pick the directory the library was uncompressed into.
|
||||
*
|
||||
* @param string $directory
|
||||
*/
|
||||
static public function registerAutoloadDirectory($directory)
|
||||
{
|
||||
if (!class_exists('Doctrine\Common\ClassLoader', false)) {
|
||||
require_once $directory . "/Doctrine/Common/ClassLoader.php";
|
||||
}
|
||||
|
||||
$loader = new ClassLoader("Doctrine", $directory);
|
||||
$loader->register();
|
||||
|
||||
$loader = new ClassLoader("Symfony\Component", $directory . "/Doctrine");
|
||||
$loader->register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configuration with an annotation metadata driver.
|
||||
*
|
||||
* @param array $paths
|
||||
* @param boolean $isDevMode
|
||||
* @param string $proxyDir
|
||||
* @param Cache $cache
|
||||
* @return Configuration
|
||||
*/
|
||||
static public function createAnnotationMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
|
||||
{
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver($paths));
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configuration with a xml metadata driver.
|
||||
*
|
||||
* @param array $paths
|
||||
* @param boolean $isDevMode
|
||||
* @param string $proxyDir
|
||||
* @param Cache $cache
|
||||
* @return Configuration
|
||||
*/
|
||||
static public function createXMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
|
||||
{
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new XmlDriver($paths));
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configuration with a yaml metadata driver.
|
||||
*
|
||||
* @param array $paths
|
||||
* @param boolean $isDevMode
|
||||
* @param string $proxyDir
|
||||
* @param Cache $cache
|
||||
* @return Configuration
|
||||
*/
|
||||
static public function createYAMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
|
||||
{
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new YamlDriver($paths));
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configuration without a metadata driver.
|
||||
*
|
||||
* @param bool $isDevMode
|
||||
* @param string $proxyDir
|
||||
* @param Cache $cache
|
||||
* @return Configuration
|
||||
*/
|
||||
static public function createConfiguration($isDevMode = false, $proxyDir = null, Cache $cache = null)
|
||||
{
|
||||
$proxyDir = $proxyDir ?: sys_get_temp_dir();
|
||||
if ($isDevMode === false && $cache === null) {
|
||||
if (extension_loaded('apc')) {
|
||||
$cache = new \Doctrine\Common\Cache\ApcCache;
|
||||
} else if (extension_loaded('xcache')) {
|
||||
$cache = new \Doctrine\Common\Cache\XcacheCache;
|
||||
} else if (extension_loaded('memcache')) {
|
||||
$memcache = new \Memcache();
|
||||
$memcache->connect('127.0.0.1');
|
||||
$cache = new \Doctrine\Common\Cache\MemcacheCache();
|
||||
$cache->setMemcache($memcache);
|
||||
} else {
|
||||
$cache = new ArrayCache;
|
||||
}
|
||||
} else if ($cache === null) {
|
||||
$cache = new ArrayCache;
|
||||
}
|
||||
$cache->setNamespace("dc2_" . md5($proxyDir) . "_"); // to avoid collisions
|
||||
|
||||
$config = new Configuration();
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$config->setQueryCacheImpl($cache);
|
||||
$config->setResultCacheImpl($cache);
|
||||
$config->setProxyDir( $proxyDir );
|
||||
$config->setProxyNamespace('DoctrineProxies');
|
||||
$config->setAutoGenerateProxyClasses($isDevMode);
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
@ -215,8 +215,13 @@ class UnitOfWork implements PropertyChangedListener
|
||||
* @var array
|
||||
*/
|
||||
private $orphanRemovals = array();
|
||||
|
||||
//private $_readOnlyObjects = array();
|
||||
|
||||
/**
|
||||
* Read-Only objects are never evaluated
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $readOnlyObjects = array();
|
||||
|
||||
/**
|
||||
* Map of Entity Class-Names and corresponding IDs that should eager loaded when requested.
|
||||
@ -403,6 +408,11 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
$oid = spl_object_hash($entity);
|
||||
|
||||
if (isset($this->readOnlyObjects[$oid])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$actualData = array();
|
||||
foreach ($class->reflFields as $name => $refProp) {
|
||||
$value = $refProp->getValue($entity);
|
||||
@ -455,10 +465,18 @@ class UnitOfWork implements PropertyChangedListener
|
||||
// and we have a copy of the original data
|
||||
$originalData = $this->originalEntityData[$oid];
|
||||
$isChangeTrackingNotify = $class->isChangeTrackingNotify();
|
||||
$changeSet = $isChangeTrackingNotify ? $this->entityChangeSets[$oid] : array();
|
||||
$changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array();
|
||||
|
||||
foreach ($actualData as $propName => $actualValue) {
|
||||
$orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
|
||||
if (isset($originalData[$propName])) {
|
||||
$orgValue = $originalData[$propName];
|
||||
} else if (array_key_exists($propName, $originalData)) {
|
||||
$orgValue = null;
|
||||
} else {
|
||||
// skip field, its a partially omitted one!
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($class->associationMappings[$propName])) {
|
||||
$assoc = $class->associationMappings[$propName];
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE && $orgValue !== $actualValue) {
|
||||
@ -528,7 +546,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
foreach ($entitiesToProcess as $entity) {
|
||||
// Ignore uninitialized proxy objects
|
||||
if (/* $entity is readOnly || */ $entity instanceof Proxy && ! $entity->__isInitialized__) {
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
|
||||
continue;
|
||||
}
|
||||
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here.
|
||||
@ -573,10 +591,12 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$oid = spl_object_hash($entry);
|
||||
if ($state == self::STATE_NEW) {
|
||||
if ( ! $assoc['isCascadePersist']) {
|
||||
throw new InvalidArgumentException("A new entity was found through a relationship that was not"
|
||||
. " configured to cascade persist operations: " . self::objToStr($entry) . "."
|
||||
throw new InvalidArgumentException("A new entity was found through the relationship '"
|
||||
. $assoc['sourceEntity'] . "#" . $assoc['fieldName'] . "' that was not"
|
||||
. " configured to cascade persist operations for entity: " . self::objToStr($entry) . "."
|
||||
. " Explicitly persist the new entity or configure cascading persist operations"
|
||||
. " on the relationship.");
|
||||
. " on the relationship. If you cannot find out which entity causes the problem"
|
||||
. " implement '" . $assoc['targetEntity'] . "#__toString()' to get a clue.");
|
||||
}
|
||||
$this->persistNew($targetClass, $entry);
|
||||
$this->computeChangeSet($targetClass, $entry);
|
||||
@ -746,7 +766,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
|
||||
|
||||
foreach ($this->entityUpdates as $oid => $entity) {
|
||||
if (get_class($entity) == $className || $entity instanceof Proxy && $entity instanceof $className) {
|
||||
if (get_class($entity) == $className || $entity instanceof Proxy && get_parent_class($entity) == $className) {
|
||||
|
||||
if ($hasPreUpdateLifecycleCallbacks) {
|
||||
$class->invokeLifecycleCallbacks(Events::preUpdate, $entity);
|
||||
@ -759,7 +779,9 @@ class UnitOfWork implements PropertyChangedListener
|
||||
);
|
||||
}
|
||||
|
||||
$persister->update($entity);
|
||||
if ($this->entityChangeSets[$oid]) {
|
||||
$persister->update($entity);
|
||||
}
|
||||
unset($this->entityUpdates[$oid]);
|
||||
|
||||
if ($hasPostUpdateLifecycleCallbacks) {
|
||||
@ -786,7 +808,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$hasListeners = $this->evm->hasListeners(Events::postRemove);
|
||||
|
||||
foreach ($this->entityDeletions as $oid => $entity) {
|
||||
if (get_class($entity) == $className || $entity instanceof Proxy && $entity instanceof $className) {
|
||||
if (get_class($entity) == $className || $entity instanceof Proxy && get_parent_class($entity) == $className) {
|
||||
$persister->delete($entity);
|
||||
unset(
|
||||
$this->entityDeletions[$oid],
|
||||
@ -830,6 +852,8 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
// See if there are any new classes in the changeset, that are not in the
|
||||
// commit order graph yet (dont have a node).
|
||||
|
||||
// TODO: Can we know the know the possible $newNodes based on something more efficient? IdentityMap?
|
||||
$newNodes = array();
|
||||
foreach ($entityChangeSet as $oid => $entity) {
|
||||
$className = get_class($entity);
|
||||
@ -841,7 +865,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
// Calculate dependencies for new nodes
|
||||
foreach ($newNodes as $class) {
|
||||
while ($class = array_pop($newNodes)) {
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
@ -856,6 +880,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$targetSubClass = $this->em->getClassMetadata($subClassName);
|
||||
if ( ! $calc->hasClass($subClassName)) {
|
||||
$calc->addClass($targetSubClass);
|
||||
$newNodes[] = $targetSubClass;
|
||||
}
|
||||
$calc->addDependency($targetSubClass, $class);
|
||||
}
|
||||
@ -980,7 +1005,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
if ($this->isInIdentityMap($entity)) {
|
||||
$this->removeFromIdentityMap($entity);
|
||||
}
|
||||
unset($this->entityInsertions[$oid]);
|
||||
unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
|
||||
return; // entity has not been persisted yet, so nothing more to do.
|
||||
}
|
||||
|
||||
@ -995,6 +1020,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
if ( ! isset($this->entityDeletions[$oid])) {
|
||||
$this->entityDeletions[$oid] = $entity;
|
||||
$this->entityStates[$oid] = self::STATE_REMOVED;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1097,6 +1123,22 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!$class->idGenerator->isPostInsertGenerator()) {
|
||||
// if we have a pre insert generator we can't be sure that having an id
|
||||
// really means that the entity exists. We have to verify this through
|
||||
// the last resort: a db lookup
|
||||
|
||||
// Last try before db lookup: check the identity map.
|
||||
if ($this->tryGetById($id, $class->rootEntityName)) {
|
||||
return self::STATE_DETACHED;
|
||||
} else {
|
||||
// db lookup
|
||||
if ($this->getEntityPersister(get_class($entity))->exists($entity)) {
|
||||
return self::STATE_DETACHED;
|
||||
} else {
|
||||
return self::STATE_NEW;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return self::STATE_DETACHED;
|
||||
}
|
||||
@ -1290,6 +1332,10 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
$visited[$oid] = $entity; // mark visited
|
||||
|
||||
// Cascade first, because scheduleForDelete() removes the entity from the identity map, which
|
||||
// can cause problems when a lazy proxy has to be initialized for the cascade operation.
|
||||
$this->cascadeRemove($entity, $visited);
|
||||
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
$entityState = $this->getEntityState($entity);
|
||||
@ -1313,7 +1359,6 @@ class UnitOfWork implements PropertyChangedListener
|
||||
throw new UnexpectedValueException("Unexpected entity state: $entityState.");
|
||||
}
|
||||
|
||||
$this->cascadeRemove($entity, $visited);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1360,6 +1405,10 @@ class UnitOfWork implements PropertyChangedListener
|
||||
if ($this->getEntityState($entity, self::STATE_DETACHED) == self::STATE_MANAGED) {
|
||||
$managedCopy = $entity;
|
||||
} else {
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
|
||||
$entity->__load();
|
||||
}
|
||||
|
||||
// Try to look the entity up in the identity map.
|
||||
$id = $class->getIdentifierValues($entity);
|
||||
|
||||
@ -1398,7 +1447,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
|
||||
// Throw exception if versions dont match.
|
||||
if ($managedCopyVersion != $entityVersion) {
|
||||
throw OptimisticLockException::lockFailedVersionMissmatch($entityVersion, $managedCopyVersion);
|
||||
throw OptimisticLockException::lockFailedVersionMissmatch($entity, $entityVersion, $managedCopyVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1448,7 +1497,8 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
if ($assoc2['isCascadeMerge']) {
|
||||
$managedCol->initialize();
|
||||
if (!$managedCol->isEmpty()) {
|
||||
// clear and set dirty a managed collection if its not also the same collection to merge from.
|
||||
if (!$managedCol->isEmpty() && $managedCol != $mergeCol) {
|
||||
$managedCol->unwrap()->clear();
|
||||
$managedCol->setDirty(true);
|
||||
if ($assoc2['isOwningSide'] && $assoc2['type'] == ClassMetadata::MANY_TO_MANY && $class->isChangeTrackingNotify()) {
|
||||
@ -1647,6 +1697,10 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
||||
if ($relatedEntities instanceof Collection) {
|
||||
if ($relatedEntities === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($relatedEntities instanceof PersistentCollection) {
|
||||
// Unwrap so that foreach() does not initialize
|
||||
$relatedEntities = $relatedEntities->unwrap();
|
||||
@ -1674,6 +1728,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
if ( ! $assoc['isCascadePersist']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
||||
if (($relatedEntities instanceof Collection || is_array($relatedEntities))) {
|
||||
if ($relatedEntities instanceof PersistentCollection) {
|
||||
@ -1702,7 +1757,11 @@ class UnitOfWork implements PropertyChangedListener
|
||||
if ( ! $assoc['isCascadeRemove']) {
|
||||
continue;
|
||||
}
|
||||
//TODO: If $entity instanceof Proxy => Initialize ?
|
||||
|
||||
if ($entity instanceof Proxy && !$entity->__isInitialized__) {
|
||||
$entity->__load();
|
||||
}
|
||||
|
||||
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
||||
if ($relatedEntities instanceof Collection || is_array($relatedEntities)) {
|
||||
// If its a PersistentCollection initialization is intended! No unwrap!
|
||||
@ -1724,7 +1783,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
public function lock($entity, $lockMode, $lockVersion = null)
|
||||
{
|
||||
if ($this->getEntityState($entity) != self::STATE_MANAGED) {
|
||||
if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) {
|
||||
throw new InvalidArgumentException("Entity is not MANAGED.");
|
||||
}
|
||||
|
||||
@ -1865,20 +1924,23 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
$id = array($class->identifier[0] => $idHash);
|
||||
}
|
||||
|
||||
if (isset($this->identityMap[$class->rootEntityName][$idHash])) {
|
||||
|
||||
if (isset($this->identityMap[$class->rootEntityName][$idHash])) {
|
||||
$entity = $this->identityMap[$class->rootEntityName][$idHash];
|
||||
$oid = spl_object_hash($entity);
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
|
||||
$entity->__isInitialized__ = true;
|
||||
$overrideLocalValues = true;
|
||||
$this->originalEntityData[$oid] = $data;
|
||||
if ($entity instanceof NotifyPropertyChanged) {
|
||||
$entity->addPropertyChangedListener($this);
|
||||
}
|
||||
} else {
|
||||
$overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
|
||||
}
|
||||
|
||||
if ($overrideLocalValues) {
|
||||
$this->originalEntityData[$oid] = $data;
|
||||
}
|
||||
} else {
|
||||
$entity = $class->newInstance();
|
||||
$oid = spl_object_hash($entity);
|
||||
@ -1960,7 +2022,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
// a way to solve this with deferred eager loading, which means putting
|
||||
// an entity with subclasses at a *-to-one location is really bad! (performance-wise)
|
||||
$newValue = $this->getEntityPersister($assoc['targetEntity'])
|
||||
->loadOneToOneEntity($assoc, $entity, null, $associatedId);
|
||||
->loadOneToOneEntity($assoc, $entity, $associatedId);
|
||||
} else {
|
||||
// Deferred eager load only works for single identifier classes
|
||||
|
||||
@ -1996,7 +2058,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
} else {
|
||||
// Inverse side of x-to-one can never be lazy
|
||||
$class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])
|
||||
->loadOneToOneEntity($assoc, $entity, null));
|
||||
->loadOneToOneEntity($assoc, $entity));
|
||||
}
|
||||
} else {
|
||||
// Inject collection
|
||||
@ -2127,7 +2189,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
* @return array The identifier values.
|
||||
*/
|
||||
public function getEntityIdentifier($entity)
|
||||
{
|
||||
{
|
||||
return $this->entityIdentifiers[spl_object_hash($entity)];
|
||||
}
|
||||
|
||||
@ -2143,9 +2205,11 @@ class UnitOfWork implements PropertyChangedListener
|
||||
public function tryGetById($id, $rootClassName)
|
||||
{
|
||||
$idHash = implode(' ', (array) $id);
|
||||
|
||||
if (isset($this->identityMap[$rootClassName][$idHash])) {
|
||||
return $this->identityMap[$rootClassName][$idHash];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2255,7 +2319,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
public function clearEntityChangeSet($oid)
|
||||
{
|
||||
unset($this->entityChangeSets[$oid]);
|
||||
$this->entityChangeSets[$oid] = array();
|
||||
}
|
||||
|
||||
/* PropertyChangedListener implementation */
|
||||
@ -2335,9 +2399,63 @@ class UnitOfWork implements PropertyChangedListener
|
||||
{
|
||||
return $this->collectionUpdates;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method to initialize a lazy loading proxy or persistent collection.
|
||||
*
|
||||
* @param object
|
||||
* @return void
|
||||
*/
|
||||
public function initializeObject($obj)
|
||||
{
|
||||
if ($obj instanceof Proxy) {
|
||||
$obj->__load();
|
||||
} else if ($obj instanceof PersistentCollection) {
|
||||
$obj->initialize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to show an object as string.
|
||||
*
|
||||
* @param object $obj
|
||||
* @return string
|
||||
*/
|
||||
private static function objToStr($obj)
|
||||
{
|
||||
return method_exists($obj, '__toString') ? (string)$obj : get_class($obj).'@'.spl_object_hash($obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
|
||||
*
|
||||
* This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
|
||||
* on this object that might be necessary to perform a correct udpate.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @param $object
|
||||
* @return void
|
||||
*/
|
||||
public function markReadOnly($object)
|
||||
{
|
||||
if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
|
||||
throw new InvalidArgumentException("Managed entity required");
|
||||
}
|
||||
$this->readOnlyObjects[spl_object_hash($object)] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this entity read only?
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @param $object
|
||||
* @return void
|
||||
*/
|
||||
public function isReadOnly($object)
|
||||
{
|
||||
if ( ! is_object($object) ) {
|
||||
throw new InvalidArgumentException("Managed entity required");
|
||||
}
|
||||
return isset($this->readOnlyObjects[spl_object_hash($object)]);
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class Version
|
||||
/**
|
||||
* Current Doctrine Version
|
||||
*/
|
||||
const VERSION = '2.1.0BETA2-DEV';
|
||||
const VERSION = '2.2.0-DEV';
|
||||
|
||||
/**
|
||||
* Compares a Doctrine version with the current one.
|
||||
|
2
lib/vendor/Symfony/Component/Console
vendored
2
lib/vendor/Symfony/Component/Console
vendored
@ -1 +1 @@
|
||||
Subproject commit 4200b4bc95ae3c1b03d943cd875277e35a17898a
|
||||
Subproject commit 3762cec59aaecf1e55ed92b2b0b3e7f2d602d09a
|
2
lib/vendor/Symfony/Component/Yaml
vendored
2
lib/vendor/Symfony/Component/Yaml
vendored
@ -1 +1 @@
|
||||
Subproject commit 3d864452ca73ae5409f1434b65f0c7204a50e061
|
||||
Subproject commit c3e1d03effe339de6940f69a4d0278ea34665702
|
2
lib/vendor/doctrine-common
vendored
2
lib/vendor/doctrine-common
vendored
@ -1 +1 @@
|
||||
Subproject commit ea434bbea37e067aa52272816c6dcda5ff826154
|
||||
Subproject commit ef431a14852d7e8f2d0ea789487509ab266e5ce2
|
2
lib/vendor/doctrine-dbal
vendored
2
lib/vendor/doctrine-dbal
vendored
@ -1 +1 @@
|
||||
Subproject commit 0a99438729e59bcb5262b22c06c782de4dfacbb0
|
||||
Subproject commit f91395b6f469b5076f52fefd64574c443b076485
|
59
phpunit.xml.dist
Normal file
59
phpunit.xml.dist
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Use this configuration file as a template to run the tests against any dbms.
|
||||
Procedure:
|
||||
1) Save a copy of this file with a name of your chosing. It doesn't matter
|
||||
where you place it as long as you know where it is.
|
||||
i.e. "mysqlconf.xml" (It needs the ending .xml).
|
||||
2) Edit the file and fill in your settings (database name, type, username, etc.)
|
||||
Just change the "value"s, not the names of the var elements.
|
||||
3) To run the tests against the database type the following from within the
|
||||
tests/ folder: phpunit -c <filename> ...
|
||||
Example: phpunit -c mysqlconf.xml AllTests
|
||||
-->
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="./tests/Doctrine/Tests/TestInit.php"
|
||||
>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Doctrine ORM Test Suite">
|
||||
<directory>./tests/Doctrine/Tests/ORM</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>performance</group>
|
||||
<group>locking_functional</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
|
||||
<php>
|
||||
<!-- "Real" test database -->
|
||||
<!-- uncomment, otherwise sqlite memory runs
|
||||
<var name="db_type" value="pdo_mysql"/>
|
||||
<var name="db_host" value="localhost" />
|
||||
<var name="db_username" value="root" />
|
||||
<var name="db_password" value="" />
|
||||
<var name="db_name" value="doctrine_tests" />
|
||||
<var name="db_port" value="3306"/>-->
|
||||
<!--<var name="db_event_subscribers" value="Doctrine\DBAL\Event\Listeners\OracleSessionInit">-->
|
||||
|
||||
<!-- Database for temporary connections (i.e. to drop/create the main database) -->
|
||||
<var name="tmpdb_type" value="pdo_mysql"/>
|
||||
<var name="tmpdb_host" value="localhost" />
|
||||
<var name="tmpdb_username" value="root" />
|
||||
<var name="tmpdb_password" value="" />
|
||||
<var name="tmpdb_name" value="doctrine_tests_tmp" />
|
||||
<var name="tmpdb_port" value="3306"/>
|
||||
</php>
|
||||
|
||||
</phpunit>
|
21
run-all.sh
Executable file
21
run-all.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is a small convenience wrapper for running the doctrine testsuite against a large bunch of databases.
|
||||
# Just create the phpunit.xmls as described in the array below and configure the specific files <php /> section
|
||||
# to connect to that database. Just omit a file if you dont have that database and the tests will be skipped.
|
||||
|
||||
configs[1]="mysql.phpunit.xml"
|
||||
configs[2]='postgres.phpunit.xml'
|
||||
configs[3]='sqlite.phpunit.xml'
|
||||
configs[4]='oracle.phpunit.xml'
|
||||
configs[5]='db2.phpunit.xml'
|
||||
configs[6]='pdo-ibm.phpunit.xml'
|
||||
configs[7]='sqlsrv.phpunit.xml'
|
||||
|
||||
for i in "${configs[@]}"; do
|
||||
if [ -f "$i" ];
|
||||
then
|
||||
echo "RUNNING TESTS WITH CONFIG $i"
|
||||
phpunit -c "$i" "$@"
|
||||
fi;
|
||||
done
|
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests;
|
||||
|
||||
use Doctrine\Tests\Common;
|
||||
use Doctrine\Tests\ORM;
|
||||
use Doctrine\Tests\DBAL;
|
||||
|
||||
if (!defined('PHPUnit_MAIN_METHOD')) {
|
||||
define('PHPUnit_MAIN_METHOD', 'AllTests::main');
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/TestInit.php';
|
||||
|
||||
class AllTests
|
||||
{
|
||||
public static function main()
|
||||
{
|
||||
\PHPUnit_TextUI_TestRunner::run(self::suite());
|
||||
}
|
||||
|
||||
public static function suite()
|
||||
{
|
||||
$suite = new DoctrineTestSuite('Doctrine Tests');
|
||||
$suite->addTest(ORM\AllTests::suite());
|
||||
|
||||
return $suite;
|
||||
}
|
||||
}
|
||||
|
||||
if (PHPUnit_MAIN_METHOD == 'AllTests::main') {
|
||||
AllTests::main();
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests;
|
||||
|
||||
class DbalFunctionalTestSuite extends DbalTestSuite
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
if ( ! isset($this->sharedFixture['conn'])) {
|
||||
$this->sharedFixture['conn'] = TestUtil::getConnection();
|
||||
}
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->sharedFixture['conn']->close();
|
||||
$this->sharedFixture = null;
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests;
|
||||
|
||||
/**
|
||||
* The outermost test suite for all dbal related testcases & suites.
|
||||
*/
|
||||
class DbalTestSuite extends DoctrineTestSuite
|
||||
{
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests;
|
||||
|
||||
/**
|
||||
* Doctrine's basic test suite implementation. Provides functionality needed by all
|
||||
* test suites.
|
||||
*/
|
||||
class DoctrineTestSuite extends \PHPUnit_Framework_TestSuite
|
||||
{
|
||||
}
|
@ -78,7 +78,7 @@ class EntityManagerMock extends \Doctrine\ORM\EntityManager
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setProxyDir(__DIR__ . '/../Proxies');
|
||||
$config->setProxyNamespace('Doctrine\Tests\Proxies');
|
||||
$config->setMetadataDriverImpl(\Doctrine\ORM\Mapping\Driver\AnnotationDriver::create());
|
||||
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver());
|
||||
}
|
||||
if (is_null($eventManager)) {
|
||||
$eventManager = new \Doctrine\Common\EventManager();
|
||||
|
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