pax_global_header00006660000000000000000000000064125710521050014510gustar00rootroot0000000000000052 comment=5aedac1e5c5caaeac14798822c70325dc242d467 doctrine2-2.4.8/000077500000000000000000000000001257105210500134145ustar00rootroot00000000000000doctrine2-2.4.8/.coveralls.yml000066400000000000000000000001401257105210500162020ustar00rootroot00000000000000# for php-coveralls service_name: travis-ci src_dir: lib coverage_clover: build/logs/clover.xml doctrine2-2.4.8/LICENSE000066400000000000000000000020511257105210500144170ustar00rootroot00000000000000Copyright (c) 2006-2012 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. doctrine2-2.4.8/README.markdown000066400000000000000000000032711257105210500161200ustar00rootroot00000000000000# Doctrine 2 ORM Master: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=master)](http://travis-ci.org/doctrine/doctrine2) 2.3: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.3)](http://travis-ci.org/doctrine/doctrine2) 2.2: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.2)](http://travis-ci.org/doctrine/doctrine2) 2.1: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.1.x)](http://travis-ci.org/doctrine/doctrine2) Master: [![Coverage Status](https://coveralls.io/repos/doctrine/doctrine2/badge.png?branch=master)](https://coveralls.io/r/doctrine/doctrine2?branch=master) [![Latest Stable Version](https://poser.pugx.org/doctrine/orm/v/stable.png)](https://packagist.org/packages/doctrine/orm) [![Total Downloads](https://poser.pugx.org/doctrine/orm/downloads.png)](https://packagist.org/packages/doctrine/orm) Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication. ## More resources: * [Website](http://www.doctrine-project.org) * [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html) * [Issue Tracker](http://www.doctrine-project.org/jira/browse/DDC) * [Downloads](http://github.com/doctrine/doctrine2/downloads) doctrine2-2.4.8/UPGRADE.md000066400000000000000000000561461257105210500150410ustar00rootroot00000000000000# Upgrade to 2.4 ## BC BREAK: Compatibility Bugfix in PersistentCollection#matching() In Doctrine 2.3 it was possible to use the new ``matching($criteria)`` functionality by adding constraints for assocations based on ID: Criteria::expr()->eq('association', $assocation->getId()); This functionality does not work on InMemory collections however, because in memory criteria compares object values based on reference. As of 2.4 the above code will throw an exception. You need to change offending code to pass the ``$assocation`` reference directly: Criteria::expr()->eq('association', $assocation); ## Composer is now the default autoloader The test suite now runs with composer autoloading. Support for PEAR, and tarball autoloading is deprecated. Support for GIT submodules is removed. ## OnFlush and PostFlush event always called Before 2.4 the postFlush and onFlush events were only called when there were actually entities that changed. Now these events are called no matter if there are entities in the UoW or changes are found. ## Parenthesis are now considered in arithmetic expression Before 2.4 parenthesis are not considered in arithmetic primary expression. That's conceptually wrong, since it might result in wrong values. For example: The DQL: SELECT 100 / ( 2 * 2 ) FROM MyEntity Before 2.4 it generates the SQL: SELECT 100 / 2 * 2 FROM my_entity Now parenthesis are considered, the previous DQL will generate: SELECT 100 / (2 * 2) FROM my_entity # Upgrade to 2.3 ## Auto Discriminator Map breaks userland implementations with Listener The new feature to detect discriminator maps automatically when none are provided breaks userland implementations doing this with a listener in ``loadClassMetadata`` event. ## EntityManager#find() not calls EntityRepository#find() anymore Previous to 2.3, calling ``EntityManager#find()`` would be delegated to ``EntityRepository#find()``. This has lead to some unexpected behavior in the core of Doctrine when people have overwritten the find method in their repositories. That is why this behavior has been reversed in 2.3, and ``EntityRepository#find()`` calls ``EntityManager#find()`` instead. ## EntityGenerator add*() method generation When generating an add*() method for a collection the EntityGenerator will now not use the Type-Hint to get the singular for the collection name, but use the field-name and strip a trailing "s" character if there is one. ## Merge copies non persisted properties too When merging an entity in UoW not only mapped properties are copied, but also others. ## Query, QueryBuilder and NativeQuery parameters *BC break* From now on, parameters in queries is an ArrayCollection instead of a simple array. This affects heavily the usage of setParameters(), because it will not append anymore parameters to query, but will actually override the already defined ones. Whenever you are retrieving a parameter (ie. $query->getParameter(1)), you will receive an instance of Query\Parameter, which contains the methods "getName", "getValue" and "getType". Parameters are also only converted to when necessary, and not when they are set. Also, related functions were affected: * execute($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance * iterate($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance * setParameters($parameters) the argument $parameters can be either an key=>value array or an ArrayCollection instance * getParameters() now returns ArrayCollection instead of array * getParameter($key) now returns Parameter instance instead of parameter value ## Query TreeWalker method renamed Internal changes were made to DQL and SQL generation. If you have implemented your own TreeWalker, you probably need to update it. The method walkJoinVariableDeclaration is now named walkJoin. ## New methods in TreeWalker interface *BC break* Two methods getQueryComponents() and setQueryComponent() were added to the TreeWalker interface and all its implementations including TreeWalkerAdapter, TreeWalkerChain and SqlWalker. If you have your own implementation not inheriting from one of the above you must implement these new methods. ## Metadata Drivers Metadata drivers have been rewritten to reuse code from Doctrine\Common. Anyone who is using the `Doctrine\ORM\Mapping\Driver\Driver` interface should instead refer to `Doctrine\Common\Persistence\Mapping\Driver\MappingDriver`. Same applies to `Doctrine\ORM\Mapping\Driver\AbstractFileDriver`: you should now refer to `Doctrine\Common\Persistence\Mapping\Driver\FileDriver`. Also, following mapping drivers have been deprecated, please use their replacements in Doctrine\Common as listed: * `Doctrine\ORM\Mapping\Driver\DriverChain` => `Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain` * `Doctrine\ORM\Mapping\Driver\PHPDriver` => `Doctrine\Common\Persistence\Mapping\Driver\PHPDriver` * `Doctrine\ORM\Mapping\Driver\StaticPHPDriver` => `Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver` # Upgrade to 2.2 ## ResultCache implementation rewritten The result cache is completely rewritten and now works on the database result level, not inside the ORM AbstractQuery anymore. This means that for result cached queries the hydration will now always be performed again, regardless of the hydration mode. Affected areas are: 1. Fixes the problem that entities coming from the result cache were not registered in the UnitOfWork leading to problems during EntityManager#flush. Calls to EntityManager#merge are not necessary anymore. 2. Affects the array hydrator which now includes the overhead of hydration compared to caching the final result. The API is backwards compatible however most of the getter methods on the `AbstractQuery` object are now deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile` instance with access to result cache driver, lifetime and cache key. ## 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 absolutely 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); ## Scalar mappings can now be omitted from DQL result You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore. Example: SELECT u, SUM(a.id) AS HIDDEN numArticles FROM User u LEFT JOIN u.Articles a ORDER BY numArticles DESC HAVING numArticles > 10 Your result will be a collection of Users, and not an array with key 0 as User object instance and "numArticles" as the number of articles per user ## Map entities as scalars in DQL result When hydrating to array or even a mixed result in object hydrator, previously you had the 0 index holding you entity instance. You are now allowed to alias this, providing more flexibility for you code. Example: SELECT u AS user FROM User u Will now return a collection of arrays with index "user" pointing to the User object instance. ## Performance optimizations Thousands of lines were completely reviewed and optimized for best performance. Removed redundancy and improved code readability made now internal Doctrine code easier to understand. Also, Doctrine 2.2 now is around 10-15% faster than 2.1. ## EntityManager#find(null) Previously EntityManager#find(null) returned null. It now throws an exception. # Upgrade 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. # Update from 2.0-BETA3 to 2.0-BETA4 ## XML Driver element demoted to attribute We changed how the XML Driver allows to define the change-tracking-policy. The working case is now: # Update from 2.0-BETA2 to 2.0-BETA3 ## Serialization of Uninitialized Proxies As of Beta3 you can now serialize uninitialized proxies, an exception will only be thrown when trying to access methods on the unserialized proxy as long as it has not been re-attached to the EntityManager using `EntityManager#merge()`. See this example: $proxy = $em->getReference('User', 1); $serializedProxy = serialize($proxy); $detachedProxy = unserialized($serializedProxy); echo $em->contains($detachedProxy); // FALSE try { $detachedProxy->getId(); // uninitialized detached proxy } catch(Exception $e) { } $attachedProxy = $em->merge($detachedProxy); echo $attackedProxy->getId(); // works! ## Changed SQL implementation of Postgres and Oracle DateTime types The DBAL Type "datetime" included the Timezone Offset in both Postgres and Oracle. As of this version they are now generated without Timezone (TIMESTAMP WITHOUT TIME ZONE instead of TIMESTAMP WITH TIME ZONE). See [this comment to Ticket DBAL-22](http://www.doctrine-project.org/jira/browse/DBAL-22?focusedCommentId=13396&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_13396) for more details as well as migration issues for PostgreSQL and Oracle. Both Postgres and Oracle will throw Exceptions during hydration of Objects with "DateTime" fields unless migration steps are taken! ## Removed multi-dot/deep-path expressions in DQL The support for implicit joins in DQL through the multi-dot/Deep Path Expressions was dropped. For example: SELECT u FROM User u WHERE u.group.name = ?1 See the "u.group.id" here is using multi dots (deep expression) to walk through the graph of objects and properties. Internally the DQL parser would rewrite these queries to: SELECT u FROM User u JOIN u.group g WHERE g.name = ?1 This explicit notation will be the only supported notation as of now. The internal handling of multi-dots in the DQL Parser was very complex, error prone in edge cases and required special treatment for several features we added. Additionally it had edge cases that could not be solved without making the DQL Parser even much more complex. For this reason we will drop the support for the deep path expressions to increase maintainability and overall performance of the DQL parsing process. This will benefit any DQL query being parsed, even those not using deep path expressions. Note that the generated SQL of both notations is exactly the same! You don't loose anything through this. ## Default Allocation Size for Sequences The default allocation size for sequences has been changed from 10 to 1. This step was made to not cause confusion with users and also because it is partly some kind of premature optimization. # Update from 2.0-BETA1 to 2.0-BETA2 There are no backwards incompatible changes in this release. # Upgrade from 2.0-ALPHA4 to 2.0-BETA1 ## EntityRepository deprecates access to protected variables Instead of accessing protected variables for the EntityManager in a custom EntityRepository it is now required to use the getter methods for all the three instance variables: * `$this->_em` now accessible through `$this->getEntityManager()` * `$this->_class` now accessible through `$this->getClassMetadata()` * `$this->_entityName` now accessible through `$this->getEntityName()` Important: For Beta 2 the protected visibility of these three properties will be changed to private! ## Console migrated to Symfony Console The Doctrine CLI has been replaced by Symfony Console Configuration Instead of having to specify: [php] $cliConfig = new CliConfiguration(); $cliConfig->setAttribute('em', $entityManager); You now have to configure the script like: [php] $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); ## Console: No need for Mapping Paths anymore In previous versions you had to specify the --from and --from-path options to show where your mapping paths are from the console. However this information is already known from the Mapping Driver configuration, so the requirement for this options were dropped. Instead for each console command all the entities are loaded and to restrict the operation to one or more sub-groups you can use the --filter flag. ## AnnotationDriver is not a default mapping driver anymore In conjunction with the recent changes to Console we realized that the annotations driver being a default metadata driver lead to lots of glue code in the console components to detect where entities lie and how to load them for batch updates like SchemaTool and other commands. However the annotations driver being a default driver does not really help that much anyways. Therefore we decided to break backwards compatibility in this issue and drop the support for Annotations as Default Driver and require our users to specify the driver explicitly (which allows us to ask for the path to all entities). If you are using the annotations metadata driver as default driver, you have to add the following lines to your bootstrap code: $driverImpl = $config->newDefaultAnnotationDriver(array(__DIR__."/Entities")); $config->setMetadataDriverImpl($driverImpl); You have to specify the path to your entities as either string of a single path or array of multiple paths to your entities. This information will be used by all console commands to access all entities. Xml and Yaml Drivers work as before! ## New inversedBy attribute It is now *mandatory* that the owning side of a bidirectional association specifies the 'inversedBy' attribute that points to the name of the field on the inverse side that completes the association. Example: [php] // BEFORE (ALPHA4 AND EARLIER) class User { //... /** @OneToOne(targetEntity="Address", mappedBy="user") */ private $address; //... } class Address { //... /** @OneToOne(targetEntity="User") */ private $user; //... } // SINCE BETA1 // User class DOES NOT CHANGE class Address { //... /** @OneToOne(targetEntity="User", inversedBy="address") */ private $user; //... } Thus, the inversedBy attribute is the counterpart to the mappedBy attribute. This change was necessary to enable some simplifications and further performance improvements. We apologize for the inconvenience. ## Default Property for Field Mappings The "default" option for database column defaults has been removed. If desired, database column defaults can be implemented by using the columnDefinition attribute of the @Column annotation (or the appropriate XML and YAML equivalents). Prefer PHP default values, if possible. ## Selecting Partial Objects Querying for partial objects now has a new syntax. The old syntax to query for partial objects now has a different meaning. This is best illustrated by an example. If you previously had a DQL query like this: [sql] SELECT u.id, u.name FROM User u Since BETA1, simple state field path expressions in the select clause are used to select object fields as plain scalar values (something that was not possible before). To achieve the same result as previously (that is, a partial object with only id and name populated) you need to use the following, explicit syntax: [sql] SELECT PARTIAL u.{id,name} FROM User u ## XML Mapping Driver The 'inheritance-type' attribute changed to take last bit of ClassMetadata constant names, i.e. NONE, SINGLE_TABLE, INHERITANCE_TYPE_JOINED ## YAML Mapping Driver The way to specify lifecycle callbacks in YAML Mapping driver was changed to allow for multiple callbacks per event. The Old syntax ways: [yaml] lifecycleCallbacks: doStuffOnPrePersist: prePersist doStuffOnPostPersist: postPersist The new syntax is: [yaml] lifecycleCallbacks: prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] postPersist: [ doStuffOnPostPersist ] ## PreUpdate Event Listeners Event Listeners listening to the 'preUpdate' event can only affect the primitive values of entity changesets by using the API on the `PreUpdateEventArgs` instance passed to the preUpdate listener method. Any changes to the state of the entitys properties won't affect the database UPDATE statement anymore. This gives drastic performance benefits for the preUpdate event. ## Collection API The Collection interface in the Common package has been updated with some missing methods that were present only on the default implementation, ArrayCollection. Custom collection implementations need to be updated to adhere to the updated interface. # Upgrade from 2.0-ALPHA3 to 2.0-ALPHA4 ## CLI Controller changes CLI main object changed its name and namespace. Renamed from Doctrine\ORM\Tools\Cli to Doctrine\Common\Cli\CliController. Doctrine\Common\Cli\CliController now only deals with namespaces. Ready to go, Core, Dbal and Orm are available and you can subscribe new tasks by retrieving the namespace and including new task. Example: [php] $cli->getNamespace('Core')->addTask('my-example', '\MyProject\Tools\Cli\Tasks\MyExampleTask'); ## CLI Tasks documentation Tasks have implemented a new way to build documentation. Although it is still possible to define the help manually by extending the basicHelp and extendedHelp, they are now optional. With new required method AbstractTask::buildDocumentation, its implementation defines the TaskDocumentation instance (accessible through AbstractTask::getDocumentation()), basicHelp and extendedHelp are now not necessary to be implemented. ## Changes in Method Signatures * A bunch of Methods on both Doctrine\DBAL\Platforms\AbstractPlatform and Doctrine\DBAL\Schema\AbstractSchemaManager have changed quite significantly by adopting the new Schema instance objects. ## Renamed Methods * Doctrine\ORM\AbstractQuery::setExpireResultCache() -> expireResultCache() * Doctrine\ORM\Query::setExpireQueryCache() -> expireQueryCache() ## SchemaTool Changes * "doctrine schema-tool --drop" now always drops the complete database instead of only those tables defined by the current database model. The previous method had problems when foreign keys of orphaned tables pointed to tables that were scheduled for deletion. * Use "doctrine schema-tool --update" to get a save incremental update for your database schema without deleting any unused tables, sequences or foreign keys. * Use "doctrine schema-tool --complete-update" to do a full incremental update of your schema. # Upgrade from 2.0-ALPHA2 to 2.0-ALPHA3 This section details the changes made to Doctrine 2.0-ALPHA3 to make it easier for you to upgrade your projects to use this version. ## CLI Changes The $args variable used in the cli-config.php for configuring the Doctrine CLI has been renamed to $globalArguments. ## Proxy class changes You are now required to make supply some minimalist configuration with regards to proxy objects. That involves 2 new configuration options. First, the directory where generated proxy classes should be placed needs to be specified. Secondly, you need to configure the namespace used for proxy classes. The following snippet shows an example: [php] // step 1: configure directory for proxy classes // $config instanceof Doctrine\ORM\Configuration $config->setProxyDir('/path/to/myproject/lib/MyProject/Generated/Proxies'); $config->setProxyNamespace('MyProject\Generated\Proxies'); Note that proxy classes behave exactly like any other classes when it comes to class loading. Therefore you need to make sure the proxy classes can be loaded by some class loader. If you place the generated proxy classes in a namespace and directory under your projects class files, like in the example above, it would be sufficient to register the MyProject namespace on a class loader. Since the proxy classes are contained in that namespace and adhere to the standards for class loading, no additional work is required. Generating the proxy classes into a namespace within your class library is the recommended setup. Entities with initialized proxy objects can now be serialized and unserialized properly from within the same application. For more details refer to the Configuration section of the manual. ## Removed allowPartialObjects configuration option The allowPartialObjects configuration option together with the `Configuration#getAllowPartialObjects` and `Configuration#setAllowPartialObjects` methods have been removed. The new behavior is as if the option were set to FALSE all the time, basically disallowing partial objects globally. However, you can still use the `Query::HINT_FORCE_PARTIAL_LOAD` query hint to force a query to return partial objects for optimization purposes. ## Renamed Methods * Doctrine\ORM\Configuration#getCacheDir() to getProxyDir() * Doctrine\ORM\Configuration#setCacheDir($dir) to setProxyDir($dir) doctrine2-2.4.8/bin/000077500000000000000000000000001257105210500141645ustar00rootroot00000000000000doctrine2-2.4.8/bin/doctrine000077500000000000000000000000631257105210500157200ustar00rootroot00000000000000#!/usr/bin/env php . */ require_once 'Doctrine/Common/ClassLoader.php'; $classLoader = new \Doctrine\Common\ClassLoader('Doctrine'); $classLoader->register(); $classLoader = new \Doctrine\Common\ClassLoader('Symfony'); $classLoader->register(); $configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php'; $helperSet = null; if (file_exists($configFile)) { if ( ! is_readable($configFile)) { trigger_error( 'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR ); } require $configFile; foreach ($GLOBALS as $helperSetCandidate) { if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) { $helperSet = $helperSetCandidate; break; } } } $helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet(); \Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet); doctrine2-2.4.8/bin/doctrine.bat000066400000000000000000000003341257105210500164630ustar00rootroot00000000000000@echo off if "%PHPBIN%" == "" set PHPBIN=@php_bin@ if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH GOTO RUN :USE_PEAR_PATH set PHPBIN=%PHP_PEAR_PHP_BIN% :RUN "%PHPBIN%" "@bin_dir@\doctrine" %* doctrine2-2.4.8/bin/doctrine.php000077500000000000000000000040071257105210500165100ustar00rootroot00000000000000. */ use Symfony\Component\Console\Helper\HelperSet; use Doctrine\ORM\Tools\Console\ConsoleRunner; (@include_once __DIR__ . '/../vendor/autoload.php') || @include_once __DIR__ . '/../../../autoload.php'; $directories = array(getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config'); $configFile = null; foreach ($directories as $directory) { $configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php'; if (file_exists($configFile)) { break; } } if ( ! file_exists($configFile)) { ConsoleRunner::printCliConfigTemplate(); exit(1); } if ( ! is_readable($configFile)) { echo 'Configuration file [' . $configFile . '] does not have read permission.' . "\n"; exit(1); } $commands = array(); $helperSet = require $configFile; if ( ! ($helperSet instanceof HelperSet)) { foreach ($GLOBALS as $helperSetCandidate) { if ($helperSetCandidate instanceof HelperSet) { $helperSet = $helperSetCandidate; break; } } } \Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet, $commands); doctrine2-2.4.8/composer.json000066400000000000000000000024641257105210500161440ustar00rootroot00000000000000{ "name": "doctrine/orm", "type": "library", "description": "Object-Relational-Mapper for PHP", "keywords": ["orm", "database"], "homepage": "http://www.doctrine-project.org", "license": "MIT", "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/collections": "~1.1", "doctrine/dbal": "~2.4", "symfony/console": "~2.0" }, "require-dev": { "symfony/yaml": "~2.1", "satooshi/php-coveralls": "dev-master" }, "suggest": { "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" }, "autoload": { "psr-0": { "Doctrine\\ORM\\": "lib/" } }, "bin": ["bin/doctrine", "bin/doctrine.php"], "extra": { "branch-alias": { "dev-master": "2.4.x-dev" } }, "archive": { "exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp", "*coveralls.yml"] } } doctrine2-2.4.8/docs/000077500000000000000000000000001257105210500143445ustar00rootroot00000000000000doctrine2-2.4.8/docs/README.md000066400000000000000000000003021257105210500156160ustar00rootroot00000000000000# Doctrine ORM Documentation ## How to Generate 1. Run ./bin/install-dependencies.sh 2. Run ./bin/generate-docs.sh It will generate the documentation into the build directory of the checkout.doctrine2-2.4.8/docs/bin/000077500000000000000000000000001257105210500151145ustar00rootroot00000000000000doctrine2-2.4.8/docs/bin/generate-docs.sh000077500000000000000000000002651257105210500201760ustar00rootroot00000000000000#!/bin/bash EXECPATH=`dirname $0` cd $EXECPATH cd .. rm build -Rf sphinx-build en build sphinx-build -b latex en build/pdf rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.texdoctrine2-2.4.8/docs/bin/install-dependencies.sh000066400000000000000000000001761257105210500215460ustar00rootroot00000000000000#!/bin/bash sudo apt-get install python25 python25-dev texlive-full rubber sudo easy_install pygments sudo easy_install sphinxdoctrine2-2.4.8/docs/en/000077500000000000000000000000001257105210500147465ustar00rootroot00000000000000doctrine2-2.4.8/docs/en/Makefile000066400000000000000000000061021257105210500164050ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORM.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORM.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." doctrine2-2.4.8/docs/en/_exts/000077500000000000000000000000001257105210500160705ustar00rootroot00000000000000doctrine2-2.4.8/docs/en/_exts/configurationblock.py000066400000000000000000000066621257105210500223360ustar00rootroot00000000000000#Copyright (c) 2010 Fabien Potencier # #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is furnished #to do so, subject to the following conditions: # #The above copyright notice and this permission notice shall be included in all #copies or substantial portions of the Software. # #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. from docutils.parsers.rst import Directive, directives from docutils import nodes from string import upper class configurationblock(nodes.General, nodes.Element): pass class ConfigurationBlock(Directive): has_content = True required_arguments = 0 optional_arguments = 0 final_argument_whitespace = True option_spec = {} formats = { 'html': 'HTML', 'xml': 'XML', 'php': 'PHP', 'yaml': 'YAML', 'jinja': 'Twig', 'html+jinja': 'Twig', 'jinja+html': 'Twig', 'php+html': 'PHP', 'html+php': 'PHP', 'ini': 'INI', 'php-annotations': 'Annotations', } def run(self): env = self.state.document.settings.env node = nodes.Element() node.document = self.state.document self.state.nested_parse(self.content, self.content_offset, node) entries = [] for i, child in enumerate(node): if isinstance(child, nodes.literal_block): # add a title (the language name) before each block #targetid = "configuration-block-%d" % env.new_serialno('configuration-block') #targetnode = nodes.target('', '', ids=[targetid]) #targetnode.append(child) innernode = nodes.emphasis(self.formats[child['language']], self.formats[child['language']]) para = nodes.paragraph() para += [innernode, child] entry = nodes.list_item('') entry.append(para) entries.append(entry) resultnode = configurationblock() resultnode.append(nodes.bullet_list('', *entries)) return [resultnode] def visit_configurationblock_html(self, node): self.body.append(self.starttag(node, 'div', CLASS='configuration-block')) def depart_configurationblock_html(self, node): self.body.append('\n') def visit_configurationblock_latex(self, node): pass def depart_configurationblock_latex(self, node): pass def setup(app): app.add_node(configurationblock, html=(visit_configurationblock_html, depart_configurationblock_html), latex=(visit_configurationblock_latex, depart_configurationblock_latex)) app.add_directive('configuration-block', ConfigurationBlock) doctrine2-2.4.8/docs/en/_theme/000077500000000000000000000000001257105210500162075ustar00rootroot00000000000000doctrine2-2.4.8/docs/en/conf.py000066400000000000000000000145411257105210500162520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Doctrine 2 ORM documentation build configuration file, created by # sphinx-quickstart on Fri Dec 3 18:10:24 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.append(os.path.abspath('_exts')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['configurationblock'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Doctrine 2 ORM' copyright = u'2010-12, Doctrine Project Team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '2' # The full version, including alpha/beta/rc tags. release = '2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. show_authors = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'doctrine' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_theme'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Doctrine2ORMdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Doctrine2ORM.tex', u'Doctrine 2 ORM Documentation', u'Doctrine Project Team', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True primary_domain = "dcorm" def linkcode_resolve(domain, info): if domain == 'dcorm': return 'http://' return None doctrine2-2.4.8/docs/en/cookbook/000077500000000000000000000000001257105210500165545ustar00rootroot00000000000000doctrine2-2.4.8/docs/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst000066400000000000000000000161131257105210500326610ustar00rootroot00000000000000Advanced field value conversion using custom mapping types ========================================================== .. sectionauthor:: Jan Sorgalla When creating entities, you sometimes have the need to transform field values before they are saved to the database. In Doctrine you can use Custom Mapping Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`). There are several ways to achieve this: converting the value inside the Type class, converting the value on the database-level or a combination of both. This article describes the third way by implementing the MySQL specific column type `Point `_. The ``Point`` type is part of the `Spatial extension `_ of MySQL and enables you to store a single location in a coordinate space by using x and y coordinates. You can use the Point type to store a longitude/latitude pair to represent a geographic location. The entity ---------- We create a simple entity with a field ``$point`` which holds a value object ``Point`` representing the latitude and longitude of the position. The entity class: .. code-block:: php point = $point; } /** * @return \Geo\ValueObject\Point */ public function getPoint() { return $this->point; } /** * @param string $address */ public function setAddress($address) { $this->address = $address; } /** * @return string */ public function getAddress() { return $this->address; } } We use the custom type ``point`` in the ``@Column`` docblock annotation of the ``$point`` field. We will create this custom mapping type in the next chapter. The point class: .. code-block:: php latitude = $latitude; $this->longitude = $longitude; } /** * @return float */ public function getLatitude() { return $this->latitude; } /** * @return float */ public function getLongitude() { return $this->longitude; } } The mapping type ---------------- Now we're going to create the ``point`` type and implement all required methods. .. code-block:: php getLongitude(), $value->getLatitude()); } return $value; } public function canRequireSQLConversion() { return true; } public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform) { return sprintf('AsText(%s)', $sqlExpr); } public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) { return sprintf('PointFromText(%s)', $sqlExpr); } } We do a 2-step conversion here. In the first step, we convert the ``Point`` object into a string representation before saving to the database (in the ``convertToDatabaseValue`` method) and back into an object after fetching the value from the database (in the ``convertToPHPValue`` method). The format of the string representation format is called `Well-known text (WKT) `_. The advantage of this format is, that it is both human readable and parsable by MySQL. Internally, MySQL stores geometry values in a binary format that is not identical to the WKT format. So, we need to let MySQL transform the WKT representation into its internal format. This is where the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL`` methods come into play. This methods wrap a sql expression (the WKT representation of the Point) into MySQL functions `PointFromText `_ and `AsText `_ which convert WKT strings to and from the internal format of MySQL. .. note:: When using DQL queries, the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL`` methods only apply to identification variables and path expressions in SELECT clauses. Expressions in WHERE clauses are **not** wrapped! If you want to use Point values in WHERE clauses, you have to implement a :doc:`user defined function ` for ``PointFromText``. Example usage ------------- .. code-block:: php getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('point', 'point'); // Store a Location object use Geo\Entity\Location; use Geo\ValueObject\Point; $location = new Location(); $location->setAddress('1600 Amphitheatre Parkway, Mountain View, CA'); $location->setPoint(new Point(37.4220761, -122.0845187)); $em->persist($location); $em->flush(); $em->clear(); // Fetch the Location object $query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'"); $location = $query->getSingleResult(); /* @var Geo\ValueObject\Point */ $point = $location->getPoint(); doctrine2-2.4.8/docs/en/cookbook/aggregate-fields.rst000066400000000000000000000260311257105210500225020ustar00rootroot00000000000000Aggregate Fields ================ .. sectionauthor:: Benjamin Eberlei You will often come across the requirement to display aggregate values of data that can be computed by using the MIN, MAX, COUNT or SUM SQL functions. For any ORM this is a tricky issue traditionally. Doctrine 2 offers several ways to get access to these values and this article will describe all of them from different perspectives. You will see that aggregate fields can become very explicit features in your domain model and how this potentially complex business rules can be easily tested. An example model ---------------- Say you want to model a bank account and all their entries. Entries into the account can either be of positive or negative money values. Each account has a credit limit and the account is never allowed to have a balance below that value. For simplicity we live in a world were money is composed of integers only. Also we omit the receiver/sender name, stated reason for transfer and the execution date. These all would have to be added on the ``Entry`` object. Our entities look like: .. code-block:: php no = $no; $this->maxCredit = $maxCredit; $this->entries = new \Doctrine\Common\Collections\ArrayCollection(); } } /** * @Entity */ class Entry { /** @Id @GeneratedValue @Column(type="integer") */ private $id; /** * @ManyToOne(targetEntity="Account", inversedBy="entries") */ private $account; /** * @Column(type="integer") */ private $amount; public function __construct($account, $amount) { $this->account = $account; $this->amount = $amount; // more stuff here, from/to whom, stated reason, execution date and such } public function getAmount() { return $this->amount; } } Using DQL --------- The Doctrine Query Language allows you to select for aggregate values computed from fields of your Domain Model. You can select the current balance of your account by calling: .. code-block:: php createQuery($dql) ->setParameter(1, $myAccountId) ->getSingleScalarResult(); The ``$em`` variable in this (and forthcoming) example holds the Doctrine ``EntityManager``. We create a query for the SUM of all amounts (negative amounts are withdraws) and retrieve them as a single scalar result, essentially return only the first column of the first row. This approach is simple and powerful, however it has a serious drawback. We have to execute a specific query for the balance whenever we need it. To implement a powerful domain model we would rather have access to the balance from our ``Account`` entity during all times (even if the Account was not persisted in the database before!). Also an additional requirement is the max credit per ``Account`` rule. We cannot reliably enforce this rule in our ``Account`` entity with the DQL retrieval of the balance. There are many different ways to retrieve accounts. We cannot guarantee that we can execute the aggregation query for all these use-cases, let alone that a userland programmer checks this balance against newly added entries. Using your Domain Model ----------------------- ``Account`` and all the ``Entry`` instances are connected through a collection, which means we can compute this value at runtime: .. code-block:: php entries AS $entry) { $balance += $entry->getAmount(); } return $balance; } } Now we can always call ``Account::getBalance()`` to access the current account balance. To enforce the max credit rule we have to implement the "Aggregate Root" pattern as described in Eric Evans book on Domain Driven Design. Described with one sentence, an aggregate root controls the instance creation, access and manipulation of its children. In our case we want to enforce that new entries can only added to the ``Account`` by using a designated method. The ``Account`` is the aggregate root of this relation. We can also enforce the correctness of the bi-directional ``Account`` <-> ``Entry`` relation with this method: .. code-block:: php assertAcceptEntryAllowed($amount); $e = new Entry($this, $amount); $this->entries[] = $e; return $e; } } Now look at the following test-code for our entities: .. code-block:: php assertEquals(0, $account->getBalance()); $account->addEntry(500); $this->assertEquals(500, $account->getBalance()); $account->addEntry(-700); $this->assertEquals(-200, $account->getBalance()); } public function testExceedMaxLimit() { $account = new Account("123456", $maxCredit = 200); $this->setExpectedException("Exception"); $account->addEntry(-1000); } } To enforce our rule we can now implement the assertion in ``Account::addEntry``: .. code-block:: php getBalance() + $amount; $allowedMinimalBalance = ($this->maxCredit * -1); if ($futureBalance < $allowedMinimalBalance) { throw new Exception("Credit Limit exceeded, entry is not allowed!"); } } } We haven't talked to the entity manager for persistence of our account example before. You can call ``EntityManager::persist($account)`` and then ``EntityManager::flush()`` at any point to save the account to the database. All the nested ``Entry`` objects are automatically flushed to the database also. .. code-block:: php addEntry(500); $account->addEntry(-200); $em->persist($account); $em->flush(); The current implementation has a considerable drawback. To get the balance, we have to initialize the complete ``Account::$entries`` collection, possibly a very large one. This can considerably hurt the performance of your application. Using an Aggregate Field ------------------------ To overcome the previously mentioned issue (initializing the whole entries collection) we want to add an aggregate field called "balance" on the Account and adjust the code in ``Account::getBalance()`` and ``Account:addEntry()``: .. code-block:: php balance; } public function addEntry($amount) { $this->assertAcceptEntryAllowed($amount); $e = new Entry($this, $amount); $this->entries[] = $e; $this->balance += $amount; return $e; } } This is a very simple change, but all the tests still pass. Our account entities return the correct balance. Now calling the ``Account::getBalance()`` method will not occur the overhead of loading all entries anymore. Adding a new Entry to the ``Account::$entities`` will also not initialize the collection internally. Adding a new entry is therefore very performant and explicitly hooked into the domain model. It will only update the account with the current balance and insert the new entry into the database. Tackling Race Conditions with Aggregate Fields ---------------------------------------------- Whenever you denormalize your database schema race-conditions can potentially lead to inconsistent state. See this example: .. code-block:: php find('Bank\Entities\Account', $accId); // request 2 account $account2 = $em->find('Bank\Entities\Account', $accId); $account1->addEntry(-200); $account2->addEntry(-200); // now request 1 and 2 both flush the changes. The aggregate field ``Account::$balance`` is now -200, however the SUM over all entries amounts yields -400. A violation of our max credit rule. You can use both optimistic or pessimistic locking to save-guard your aggregate fields against this kind of race-conditions. Reading Eric Evans DDD carefully he mentions that the "Aggregate Root" (Account in our example) needs a locking mechanism. Optimistic locking is as easy as adding a version column: .. code-block:: php find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ); Keeping Updates and Deletes in Sync ----------------------------------- The example shown in this article does not allow changes to the value in ``Entry``, which considerably simplifies the effort to keep ``Account::$balance`` in sync. If your use-case allows fields to be updated or related entities to be removed you have to encapsulate this logic in your "Aggregate Root" entity and adjust the aggregate field accordingly. Conclusion ---------- This article described how to obtain aggregate values using DQL or your domain model. It showed how you can easily add an aggregate field that offers serious performance benefits over iterating all the related objects that make up an aggregate value. Finally I showed how you can ensure that your aggregate fields do not get out of sync due to race-conditions and concurrent access. doctrine2-2.4.8/docs/en/cookbook/decorator-pattern.rst000066400000000000000000000152261257105210500227510ustar00rootroot00000000000000Persisting the Decorator Pattern ================================ .. sectionauthor:: Chris Woodford This recipe will show you a simple example of how you can use Doctrine 2 to persist an implementation of the `Decorator Pattern `_ Component --------- The ``Component`` class needs to be persisted, so it's going to be an ``Entity``. As the top of the inheritance hierarchy, it's going to have to define the persistent inheritance. For this example, we will use Single Table Inheritance, but Class Table Inheritance would work as well. In the discriminator map, we will define two concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``. .. code-block:: php id; } /** * Set name * @param string $name */ public function setName($name) { $this->name = $name; } /** * Get name * @return string $name */ public function getName() { return $this->name; } } ConcreteComponent ----------------- The ``ConcreteComponent`` class is pretty simple and doesn't do much more than extend the abstract ``Component`` class (only for the purpose of keeping this example simple). .. code-block:: php setDecorates($c); } /** * (non-PHPdoc) * @see Test.Component::getName() */ public function getName() { return 'Decorated ' . $this->getDecorates()->getName(); } /** * the component being decorated * @return Component */ protected function getDecorates() { return $this->decorates; } /** * sets the component being decorated * @param Component $c */ protected function setDecorates(Component $c) { $this->decorates = $c; } } All operations on the ``Decorator`` (i.e. persist, remove, etc) will cascade from the ``Decorator`` to the ``Component``. This means that when we persist a ``Decorator``, Doctrine will take care of persisting the chain of decorated objects for us. A ``Decorator`` can be treated exactly as a ``Component`` when it comes time to persisting it. The ``Decorator's`` constructor accepts an instance of a ``Component``, as defined by the ``Decorator`` pattern. The setDecorates/getDecorates methods have been defined as protected to hide the fact that a ``Decorator`` is decorating a ``Component`` and keeps the ``Component`` interface and the ``Decorator`` interface identical. To illustrate the intended result of the ``Decorator`` pattern, the getName() method has been overridden to append a string to the ``Component's`` getName() method. ConcreteDecorator ----------------- The final class required to complete a simple implementation of the Decorator pattern is the ``ConcreteDecorator``. In order to further illustrate how the ``Decorator`` can alter data as it moves through the chain of decoration, a new field, "special", has been added to this class. The getName() has been overridden and appends the value of the getSpecial() method to its return value. .. code-block:: php special = $special; } /** * Get special * @return string $special */ public function getSpecial() { return $this->special; } /** * (non-PHPdoc) * @see Test.Component::getName() */ public function getName() { return '[' . $this->getSpecial() . '] ' . parent::getName(); } } Examples -------- Here is an example of how to persist and retrieve your decorated objects .. code-block:: php setName('Test Component 1'); $em->persist($c); // assigned unique ID = 1 // create a new concrete decorator $c = new ConcreteComponent(); $c->setName('Test Component 2'); $d = new ConcreteDecorator($c); $d->setSpecial('Really'); $em->persist($d); // assigns c as unique ID = 2, and d as unique ID = 3 $em->flush(); $c = $em->find('Test\Component', 1); $d = $em->find('Test\Component', 3); echo get_class($c); // prints: Test\Component\ConcreteComponent echo $c->getName(); // prints: Test Component 1 echo get_class($d) // prints: Test\Component\ConcreteDecorator echo $d->getName(); // prints: [Really] Decorated Test Component 2 doctrine2-2.4.8/docs/en/cookbook/dql-custom-walkers.rst000066400000000000000000000174021257105210500230500ustar00rootroot00000000000000Extending DQL in Doctrine 2: Custom AST Walkers =============================================== .. sectionauthor:: Benjamin Eberlei The Doctrine Query Language (DQL) is a proprietary sql-dialect that substitutes tables and columns for Entity names and their fields. Using DQL you write a query against the database using your entities. With the help of the metadata you can write very concise, compact and powerful queries that are then translated into SQL by the Doctrine ORM. In Doctrine 1 the DQL language was not implemented using a real parser. This made modifications of the DQL by the user impossible. Doctrine 2 in contrast has a real parser for the DQL language, which transforms the DQL statement into an `Abstract Syntax Tree `_ and generates the appropriate SQL statement for it. Since this process is deterministic Doctrine heavily caches the SQL that is generated from any given DQL query, which reduces the performance overhead of the parsing process to zero. You can modify the Abstract syntax tree by hooking into DQL parsing process by adding a Custom Tree Walker. A walker is an interface that walks each node of the Abstract syntax tree, thereby generating the SQL statement. There are two types of custom tree walkers that you can hook into the DQL parser: - An output walker. This one actually generates the SQL, and there is only ever one of them. We implemented the default SqlWalker implementation for it. - A tree walker. There can be many tree walkers, they cannot generate the sql, however they can modify the AST before its rendered to sql. Now this is all awfully technical, so let me come to some use-cases fast to keep you motivated. Using walker implementation you can for example: - Modify the AST to generate a Count Query to be used with a paginator for any given DQL query. - Modify the Output Walker to generate vendor-specific SQL (instead of ANSI). - Modify the AST to add additional where clauses for specific entities (example ACL, country-specific content...) - Modify the Output walker to pretty print the SQL for debugging purposes. In this cookbook-entry I will show examples on the first two points. There are probably much more use-cases. Generic count query for pagination ---------------------------------- Say you have a blog and posts all with one category and one author. A query for the front-page or any archive page might look something like: .. code-block:: sql SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... Now in this query the blog post is the root entity, meaning its the one that is hydrated directly from the query and returned as an array of blog posts. In contrast the comment and author are loaded for deeper use in the object tree. A pagination for this query would want to approximate the number of posts that match the WHERE clause of this query to be able to predict the number of pages to show to the user. A draft of the DQL query for pagination would look like: .. code-block:: sql SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... Now you could go and write each of these queries by hand, or you can use a tree walker to modify the AST for you. Lets see how the API would look for this use-case: .. code-block:: php createQuery($dql); $query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20); $totalResults = Paginate::count($query); $results = $query->getResult(); The ``Paginate::count(Query $query)`` looks like: .. code-block:: php setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker')); $countQuery->setFirstResult(null)->setMaxResults(null); return $countQuery->getSingleScalarResult(); } } It clones the query, resets the limit clause first and max results and registers the ``CountSqlWalker`` customer tree walker which will modify the AST to execute a count query. The walkers implementation is: .. code-block:: php _getQueryComponents() AS $dqlAlias => $qComp) { if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { $parent = $qComp; $parentName = $dqlAlias; break; } } $pathExpression = new PathExpression( PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, $parent['metadata']->getSingleIdentifierFieldName() ); $pathExpression->type = PathExpression::TYPE_STATE_FIELD; $AST->selectClause->selectExpressions = array( new SelectExpression( new AggregateExpression('count', $pathExpression, true), null ) ); } } This will delete any given select expressions and replace them with a distinct count query for the root entities primary key. This will only work if your entity has only one identifier field (composite keys won't work). Modify the Output Walker to generate Vendor specific SQL -------------------------------------------------------- Most RMDBS have vendor-specific features for optimizing select query execution plans. You can write your own output walker to introduce certain keywords using the Query Hint API. A query hint can be set via ``Query::setHint($name, $value)`` as shown in the previous example with the ``HINT_CUSTOM_TREE_WALKERS`` query hint. We will implement a custom Output Walker that allows to specify the SQL\_NO\_CACHE query hint. .. code-block:: php createQuery($dql); $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker'); $query->setHint("mysqlWalker.sqlNoCache", true); $results = $query->getResult(); Our ``MysqlWalker`` will extend the default ``SqlWalker``. We will modify the generation of the SELECT clause, adding the SQL\_NO\_CACHE on those queries that need it: .. code-block:: php getQuery()->getHint('mysqlWalker.sqlNoCache') === true) { if ($selectClause->isDistinct) { $sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql); } else { $sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql); } } return $sql; } } Writing extensions to the Output Walker requires a very deep understanding of the DQL Parser and Walkers, but may offer your huge benefits with using vendor specific features. This would still allow you write DQL queries instead of NativeQueries to make use of vendor specific features. doctrine2-2.4.8/docs/en/cookbook/dql-user-defined-functions.rst000066400000000000000000000221151257105210500244450ustar00rootroot00000000000000DQL User Defined Functions ========================== .. sectionauthor:: Benjamin Eberlei By default DQL supports a limited subset of all the vendor-specific SQL functions common between all the vendors. However in many cases once you have decided on a specific database vendor, you will never change it during the life of your project. This decision for a specific vendor potentially allows you to make use of powerful SQL features that are unique to the vendor. It is worth to mention that Doctrine 2 also allows you to handwrite your SQL instead of extending the DQL parser. Extending DQL is sort of an advanced extension point. You can map arbitrary SQL to your objects and gain access to vendor specific functionalities using the ``EntityManager#createNativeQuery()`` API as described in the :doc:`Native Query <../reference/native-sql>` chapter. The DQL Parser has hooks to register functions that can then be used in your DQL queries and transformed into SQL, allowing to extend Doctrines Query capabilities to the vendors strength. This post explains the Used-Defined Functions API (UDF) of the Dql Parser and shows some examples to give you some hints how you would extend DQL. There are three types of functions in DQL, those that return a numerical value, those that return a string and those that return a Date. Your custom method has to be registered as either one of those. The return type information is used by the DQL parser to check possible syntax errors during the parsing process, for example using a string function return value in a math expression. Registering your own DQL functions ---------------------------------- You can register your functions adding them to the ORM configuration: .. code-block:: php addCustomStringFunction($name, $class); $config->addCustomNumericFunction($name, $class); $config->addCustomDatetimeFunction($name, $class); $em = EntityManager::create($dbParams, $config); The ``$name`` is the name the function will be referred to in the DQL query. ``$class`` is a string of a class-name which has to extend ``Doctrine\ORM\Query\Node\FunctionNode``. This is a class that offers all the necessary API and methods to implement a UDF. In this post we will implement some MySql specific Date calculation methods, which are quite handy in my opinion: Date Diff --------- `Mysql's DateDiff function `_ takes two dates as argument and calculates the difference in days with ``date1-date2``. The DQL parser is a top-down recursive descent parser to generate the Abstract-Syntax Tree (AST) and uses a TreeWalker approach to generate the appropriate SQL from the AST. This makes reading the Parser/TreeWalker code manageable in a finite amount of time. The ``FunctionNode`` class I referred to earlier requires you to implement two methods, one for the parsing process (obviously) called ``parse`` and one for the TreeWalker process called ``getSql()``. I show you the code for the DateDiff method and discuss it step by step: .. code-block:: php match(Lexer::T_IDENTIFIER); // (2) $parser->match(Lexer::T_OPEN_PARENTHESIS); // (3) $this->firstDateExpression = $parser->ArithmeticPrimary(); // (4) $parser->match(Lexer::T_COMMA); // (5) $this->secondDateExpression = $parser->ArithmeticPrimary(); // (6) $parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3) } public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return 'DATEDIFF(' . $this->firstDateExpression->dispatch($sqlWalker) . ', ' . $this->secondDateExpression->dispatch($sqlWalker) . ')'; // (7) } } The Parsing process of the DATEDIFF function is going to find two expressions the date1 and the date2 values, whose AST Node representations will be saved in the variables of the DateDiff FunctionNode instance at (1). The parse() method has to cut the function call "DATEDIFF" and its argument into pieces. Since the parser detects the function using a lookahead the T\_IDENTIFIER of the function name has to be taken from the stack (2), followed by a detection of the arguments in (4)-(6). The opening and closing parenthesis have to be detected also. This happens during the Parsing process and leads to the generation of a DateDiff FunctionNode somewhere in the AST of the dql statement. The ``ArithmeticPrimary`` method call is the most common denominator of valid EBNF tokens taken from the `DQL EBNF grammar `_ that matches our requirements for valid input into the DateDiff Dql function. Picking the right tokens for your methods is a tricky business, but the EBNF grammar is pretty helpful finding it, as is looking at the Parser source code. Now in the TreeWalker process we have to pick up this node and generate SQL from it, which apparently is quite easy looking at the code in (7). Since we don't know which type of AST Node the first and second Date expression are we are just dispatching them back to the SQL Walker to generate SQL from and then wrap our DATEDIFF function call around this output. Now registering this DateDiff FunctionNode with the ORM using: .. code-block:: php addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff'); We can do fancy stuff like: .. code-block:: sql SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7 Date Add -------- Often useful it the ability to do some simple date calculations in your DQL query using `MySql's DATE\_ADD function `_. I'll skip the blah and show the code for this function: .. code-block:: php match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstDateExpression = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $parser->match(Lexer::T_IDENTIFIER); $this->intervalExpression = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_IDENTIFIER); /* @var $lexer Lexer */ $lexer = $parser->getLexer(); $this->unit = $lexer->token['value']; $parser->match(Lexer::T_CLOSE_PARENTHESIS); } public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return 'DATE_ADD(' . $this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' . $this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit . ')'; } } The only difference compared to the DATEDIFF here is, we additionally need the ``Lexer`` to access the value of the ``T_IDENTIFIER`` token for the Date Interval unit, for example the MONTH in: .. code-block:: sql SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created The above method now only supports the specification using ``INTERVAL``, to also allow a real date in DATE\_ADD we need to add some decision logic to the parsing process (makes up for a nice exercise). Now as you see, the Parsing process doesn't catch all the possible SQL errors, here we don't match for all the valid inputs for the interval unit. However where necessary we rely on the database vendors SQL parser to show us further errors in the parsing process, for example if the Unit would not be one of the supported values by MySql. Conclusion ---------- Now that you all know how you can implement vendor specific SQL functionalities in DQL, we would be excited to see user extensions that add vendor specific function packages, for example more math functions, XML + GIS Support, Hashing functions and so on. For 2.0 we will come with the current set of functions, however for a future version we will re-evaluate if we can abstract even more vendor sql functions and extend the DQL languages scope. Code for this Extension to DQL and other Doctrine Extensions can be found `in my Github DoctrineExtensions repository `_. doctrine2-2.4.8/docs/en/cookbook/entities-in-session.rst000066400000000000000000000044501257105210500232220ustar00rootroot00000000000000Entities in the Session ======================= There are several use-cases to save entities in the session, for example: 1. User object 2. Multi-step forms To achieve this with Doctrine you have to pay attention to some details to get this working. Merging entity into an EntityManager ------------------------------------ In Doctrine an entity objects has to be "managed" by an EntityManager to be updateable. Entities saved into the session are not managed in the next request anymore. This means that you have to register these entities with an EntityManager again if you want to change them or use them as part of references between other entities. You can achieve this by calling ``EntityManager#merge()``. For a representative User object the code to get turn an instance from the session into a managed Doctrine object looks like this: .. code-block:: php merge($user); } .. note:: A frequent mistake is not to get the merged user object from the return value of ``EntityManager#merge()``. The entity object passed to merge is not necessarily the same object that is returned from the method. Serializing entity into the session ----------------------------------- Entities that are serialized into the session normally contain references to other entities as well. Think of the user entity has a reference to his articles, groups, photos or many other different entities. If you serialize this object into the session then you don't want to serialize the related entities as well. This is why you should call ``EntityManager#detach()`` on this object or implement the __sleep() magic method on your entity. .. code-block:: php find("User", 1); $em->detach($user); $_SESSION['user'] = $user; .. note:: When you called detach on your objects they get "unmanaged" with that entity manager. This means you cannot use them as part of write operations during ``EntityManager#flush()`` anymore in this request. doctrine2-2.4.8/docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst000066400000000000000000000060771257105210500277660ustar00rootroot00000000000000Implementing ArrayAccess for Domain Objects =========================================== .. sectionauthor:: Roman Borschel (roman@code-factory.org) This recipe will show you how to implement ArrayAccess for your domain objects in order to allow more uniform access, for example in templates. In these examples we will implement ArrayAccess on a `Layer Supertype `_ for all our domain objects. Option 1 -------- In this implementation we will make use of PHPs highly dynamic nature to dynamically access properties of a subtype in a supertype at runtime. Note that this implementation has 2 main caveats: - It will not work with private fields - It will not go through any getters/setters .. code-block:: php $offset); } public function offsetSet($offset, $value) { $this->$offset = $value; } public function offsetGet($offset) { return $this->$offset; } public function offsetUnset($offset) { $this->$offset = null; } } Option 2 -------- In this implementation we will dynamically invoke getters/setters. Again we use PHPs dynamic nature to invoke methods on a subtype from a supertype at runtime. This implementation has the following caveats: - It relies on a naming convention - The semantics of offsetExists can differ - offsetUnset will not work with typehinted setters .. code-block:: php {"get$offset"}(); return $value !== null; } public function offsetSet($offset, $value) { $this->{"set$offset"}($value); } public function offsetGet($offset) { return $this->{"get$offset"}(); } public function offsetUnset($offset) { $this->{"set$offset"}(null); } } Read-only --------- You can slightly tweak option 1 or option 2 in order to make array access read-only. This will also circumvent some of the caveats of each option. Simply make offsetSet and offsetUnset throw an exception (i.e. BadMethodCallException). .. code-block:: php `_ for all our domain objects. Implementing NotifyPropertyChanged ---------------------------------- The NOTIFY policy is based on the assumption that the entities notify interested listeners of changes to their properties. For that purpose, a class that wants to use this policy needs to implement the ``NotifyPropertyChanged`` interface from the ``Doctrine\Common`` namespace. .. code-block:: php listeners[] = $listener; } /** Notifies listeners of a change. */ protected function onPropertyChanged($propName, $oldValue, $newValue) { if ($this->listeners) { foreach ($this->listeners as $listener) { $listener->propertyChanged($this, $propName, $oldValue, $newValue); } } } } Then, in each property setter of concrete, derived domain classes, you need to invoke onPropertyChanged as follows to notify listeners: .. code-block:: php data) { // check: is it actually modified? $this->onPropertyChanged('data', $this->data, $data); $this->data = $data; } } } The check whether the new value is different from the old one is not mandatory but recommended. That way you can avoid unnecessary updates and also have full control over when you consider a property changed. doctrine2-2.4.8/docs/en/cookbook/implementing-wakeup-or-clone.rst000066400000000000000000000043331257105210500250070ustar00rootroot00000000000000Implementing Wakeup or Clone ============================ .. sectionauthor:: Roman Borschel (roman@code-factory.org) As explained in the `restrictions for entity classes in the manual `_, it is usually not allowed for an entity to implement ``__wakeup`` or ``__clone``, because Doctrine makes special use of them. However, it is quite easy to make use of these methods in a safe way by guarding the custom wakeup or clone code with an entity identity check, as demonstrated in the following sections. Safely implementing \_\_wakeup ------------------------------ To safely implement ``__wakeup``, simply enclose your implementation code in an identity check as follows: .. code-block:: php id) { // ... Your code here as normal ... } // otherwise do nothing, do NOT throw an exception! } //... } Safely implementing \_\_clone ----------------------------- Safely implementing ``__clone`` is pretty much the same: .. code-block:: php id) { // ... Your code here as normal ... } // otherwise do nothing, do NOT throw an exception! } //... } Summary ------- As you have seen, it is quite easy to safely make use of ``__wakeup`` and ``__clone`` in your entities without adding any really Doctrine-specific or Doctrine-dependant code. These implementations are possible and safe because when Doctrine invokes these methods, the entities never have an identity (yet). Furthermore, it is possibly a good idea to check for the identity in your code anyway, since it's rarely the case that you want to unserialize or clone an entity with no identity. doctrine2-2.4.8/docs/en/cookbook/integrating-with-codeigniter.rst000066400000000000000000000102651257105210500250700ustar00rootroot00000000000000Integrating with CodeIgniter ============================ This is recipe for using Doctrine 2 in your `CodeIgniter `_ framework. .. note:: This might not work for all CodeIgniter versions and may require slight adjustments. Here is how to set it up: Make a CodeIgniter library that is both a wrapper and a bootstrap for Doctrine 2. Setting up the file structure ----------------------------- Here are the steps: - Add a php file to your system/application/libraries folder called Doctrine.php. This is going to be your wrapper/bootstrap for the D2 entity manager. - Put the Doctrine folder (the one that contains Common, DBAL, and ORM) inside that same libraries folder. - Your system/application/libraries folder now looks like this: system/applications/libraries -Doctrine -Doctrine.php -index.html - If you want, open your config/autoload.php file and autoload your Doctrine library. register(); $entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" )); $entitiesClassLoader->register(); $proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies'); $proxiesClassLoader->register(); // Set up caches $config = new Configuration; $cache = new ArrayCache; $config->setMetadataCacheImpl($cache); $driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities')); $config->setMetadataDriverImpl($driverImpl); $config->setQueryCacheImpl($cache); $config->setQueryCacheImpl($cache); // Proxy configuration $config->setProxyDir(APPPATH.'/models/proxies'); $config->setProxyNamespace('Proxies'); // Set up logger $logger = new EchoSQLLogger; $config->setSQLLogger($logger); $config->setAutoGenerateProxyClasses( TRUE ); // Database connection information $connectionOptions = array( 'driver' => 'pdo_mysql', 'user' => $db['default']['username'], 'password' => $db['default']['password'], 'host' => $db['default']['hostname'], 'dbname' => $db['default']['database'] ); // Create EntityManager $this->em = EntityManager::create($connectionOptions, $config); } } Please note that this is a development configuration; for a production system you'll want to use a real caching system like APC, get rid of EchoSqlLogger, and turn off autoGenerateProxyClasses. For more details, consult the `Doctrine 2 Configuration documentation `_. Now to use it ------------- Whenever you need a reference to the entity manager inside one of your controllers, views, or models you can do this: .. code-block:: php doctrine->em; That's all there is to it. Once you get the reference to your EntityManager do your Doctrine 2.0 voodoo as normal. Note: If you do not choose to autoload the Doctrine library, you will need to put this line before you get a reference to it: .. code-block:: php load->library('doctrine'); Good luck! doctrine2-2.4.8/docs/en/cookbook/mysql-enums.rst000066400000000000000000000131771257105210500216110ustar00rootroot00000000000000Mysql Enums =========== The type system of Doctrine 2 consists of flyweights, which means there is only one instance of any given type. Additionally types do not contain state. Both assumptions make it rather complicated to work with the Enum Type of MySQL that is used quite a lot by developers. When using Enums with a non-tweaked Doctrine 2 application you will get errors from the Schema-Tool commands due to the unknown database type "enum". By default Doctrine does not map the MySQL enum type to a Doctrine type. This is because Enums contain state (their allowed values) and Doctrine types don't. This cookbook entry shows two possible solutions to work with MySQL enums. But first a word of warning. The MySQL Enum type has considerable downsides: - Adding new values requires to rebuild the whole table, which can take hours depending on the size. - Enums are ordered in the way the values are specified, not in their "natural" order. - Enums validation mechanism for allowed values is not necessarily good, specifying invalid values leads to an empty enum for the default MySQL error settings. You can easily replicate the "allow only some values" requirement in your Doctrine entities. Solution 1: Mapping to Varchars ------------------------------- You can map ENUMs to varchars. You can register MySQL ENUMs to map to Doctrine varchars. This way Doctrine always resolves ENUMs to Doctrine varchars. It will even detect this match correctly when using SchemaTool update commands. .. code-block:: php getConnection(); $conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string'); In this case you have to ensure that each varchar field that is an enum in the database only gets passed the allowed values. You can easily enforce this in your entities: .. code-block:: php status = $status; } } If you want to actively create enums through the Doctrine Schema-Tool by using the **columnDefinition** attribute. .. code-block:: php values); return "ENUM(".implode(", ", $values).") COMMENT '(DC2Type:".$this->name.")'"; } public function convertToPHPValue($value, AbstractPlatform $platform) { return $value; } public function convertToDatabaseValue($value, AbstractPlatform $platform) { if (!in_array($value, $this->values)) { throw new \InvalidArgumentException("Invalid '".$this->name."' value."); } return $value; } public function getName() { return $this->name; } } With this base class you can define an enum as easily as: .. code-block:: php addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface', 'Acme\\CustomerModule\\Entity\\Customer', array()); // Add the ResolveTargetEntityListener $evm->addEventSubscriber($rtel); $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm); Final Thoughts -------------- With the ``ResolveTargetEntityListener``, we are able to decouple our bundles, keeping them usable by themselves, but still being able to define relationships between different objects. By using this method, I've found my bundles end up being easier to maintain independently. doctrine2-2.4.8/docs/en/cookbook/sql-table-prefixes.rst000066400000000000000000000051401257105210500230150ustar00rootroot00000000000000SQL-Table Prefixes ================== This recipe is intended as an example of implementing a loadClassMetadata listener to provide a Table Prefix option for your application. The method used below is not a hack, but fully integrates into the Doctrine system, all SQL generated will include the appropriate table prefix. In most circumstances it is desirable to separate different applications into individual databases, but in certain cases, it may be beneficial to have a table prefix for your Entities to separate them from other vendor products in the same database. Implementing the listener ------------------------- The listener in this example has been set up with the DoctrineExtensions namespace. You create this file in your library/DoctrineExtensions directory, but will need to set up appropriate autoloaders. .. code-block:: php prefix = (string) $prefix; } public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) { $classMetadata = $eventArgs->getClassMetadata(); $classMetadata->setTableName($this->prefix . $classMetadata->getTableName()); foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) { if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY) { $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name']; $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName; } } } } Telling the EntityManager about our listener -------------------------------------------- A listener of this type must be set up before the EntityManager has been initialised, otherwise an Entity might be created or cached before the prefix has been set. .. note:: If you set this listener up, be aware that you will need to clear your caches and drop then recreate your database schema. .. code-block:: php addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix); $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm); doctrine2-2.4.8/docs/en/cookbook/strategy-cookbook-introduction.rst000066400000000000000000000210531257105210500254740ustar00rootroot00000000000000Strategy-Pattern ================ This recipe will give you a short introduction on how to design similar entities without using expensive (i.e. slow) inheritance but with not more than \* the well-known strategy pattern \* event listeners Scenario / Problem ------------------ Given a Content-Management-System, we probably want to add / edit some so-called "blocks" and "panels". What are they for? - A block might be a registration form, some text content, a table with information. A good example might also be a small calendar. - A panel is by definition a block that can itself contain blocks. A good example for a panel might be a sidebar box: You could easily add a small calendar into it. So, in this scenario, when building your CMS, you will surely add lots of blocks and panels to your pages and you will find yourself highly uncomfortable because of the following: - Every existing page needs to know about the panels it contains - therefore, you'll have an association to your panels. But if you've got several types of panels - what do you do? Add an association to every panel-type? This wouldn't be flexible. You might be tempted to add an AbstractPanelEntity and an AbstractBlockEntity that use class inheritance. Your page could then only confer to the AbstractPanelType and Doctrine 2 would do the rest for you, i.e. load the right entities. But - you'll for sure have lots of panels and blocks, and even worse, you'd have to edit the discriminator map *manually* every time you or another developer implements a new block / entity. This would tear down any effort of modular programming. Therefore, we need something that's far more flexible. Solution -------- The solution itself is pretty easy. We will have one base class that will be loaded via the page and that has specific behaviour - a Block class might render the front-end and even the backend, for example. Now, every block that you'll write might look different or need different data - therefore, we'll offer an API to these methods but internally, we use a strategy that exactly knows what to do. First of all, we need to make sure that we have an interface that contains every needed action. Such actions would be rendering the front-end or the backend, solving dependencies (blocks that are supposed to be placed in the sidebar could refuse to be placed in the middle of your page, for example). Such an interface could look like this: .. code-block:: php blockStrategy. Will not be persisted by Doctrine 2. * * @var BlockStrategyInterface */ protected $strategyInstance; /** * Returns the strategy that is used for this blockitem. * * The strategy itself defines how this block can be rendered etc. * * @return string */ public function getStrategyClassName() { return $this->strategyClassName; } /** * Returns the instantiated strategy * * @return BlockStrategyInterface */ public function getStrategyInstance() { return $this->strategyInstance; } /** * Sets the strategy this block / panel should work as. Make sure that you've used * this method before persisting the block! * * @param BlockStrategyInterface $strategy */ public function setStrategy(BlockStrategyInterface $strategy) { $this->strategyInstance = $strategy; $this->strategyClassName = get_class($strategy); $strategy->setBlockEntity($this); } Now, the important point is that $strategyClassName is a Doctrine 2 field, i.e. Doctrine will persist this value. This is only the class name of your strategy and not an instance! Finishing your strategy pattern, we hook into the Doctrine postLoad event and check whether a block has been loaded. If so, you will initialize it - i.e. get the strategies classname, create an instance of it and set it via setStrategyBlock(). This might look like this: .. code-block:: php view = $view; } public function getSubscribedEvents() { return array(ORM\Events::postLoad); } public function postLoad(ORM\Event\LifecycleEventArgs $args) { $blockItem = $args->getEntity(); // Both blocks and panels are instances of Block\AbstractBlock if ($blockItem instanceof Block\AbstractBlock) { $strategy = $blockItem->getStrategyClassName(); $strategyInstance = new $strategy(); if (null !== $blockItem->getConfig()) { $strategyInstance->setConfig($blockItem->getConfig()); } $strategyInstance->setView($this->view); $blockItem->setStrategy($strategyInstance); } } } In this example, even some variables are set - like a view object or a specific configuration object. doctrine2-2.4.8/docs/en/cookbook/validation-of-entities.rst000066400000000000000000000104741257105210500236720ustar00rootroot00000000000000Validation of Entities ====================== .. sectionauthor:: Benjamin Eberlei Doctrine 2 does not ship with any internal validators, the reason being that we think all the frameworks out there already ship with quite decent ones that can be integrated into your Domain easily. What we offer are hooks to execute any kind of validation. .. note:: You don't need to validate your entities in the lifecycle events. Its only one of many options. Of course you can also perform validations in value setters or any other method of your entities that are used in your code. Entities can register lifecycle event methods with Doctrine that are called on different occasions. For validation we would need to hook into the events called before persisting and updating. Even though we don't support validation out of the box, the implementation is even simpler than in Doctrine 1 and you will get the additional benefit of being able to re-use your validation in any other part of your domain. Say we have an ``Order`` with several ``OrderLine`` instances. We never want to allow any customer to order for a larger sum than he is allowed to: .. code-block:: php customer->getOrderLimit(); $amount = 0; foreach ($this->orderLines AS $line) { $amount += $line->getAmount(); } if ($amount > $orderLimit) { throw new CustomerOrderLimitExceededException(); } } } Now this is some pretty important piece of business logic in your code, enforcing it at any time is important so that customers with a unknown reputation don't owe your business too much money. We can enforce this constraint in any of the metadata drivers. First Annotations: .. code-block:: php YAML needs some little change yet, to allow multiple lifecycle events for one method, this will happen before Beta 1 though. Now validation is performed whenever you call ``EntityManager#persist($order)`` or when you call ``EntityManager#flush()`` and an order is about to be updated. Any Exception that happens in the lifecycle callbacks will be cached by the EntityManager and the current transaction is rolled back. Of course you can do any type of primitive checks, not null, email-validation, string size, integer and date ranges in your validation callbacks. .. code-block:: php plannedShipDate instanceof DateTime)) { throw new ValidateException(); } if ($this->plannedShipDate->format('U') < time()) { throw new ValidateException(); } if ($this->customer == null) { throw new OrderRequiresCustomerException(); } } } What is nice about lifecycle events is, you can also re-use the methods at other places in your domain, for example in combination with your form library. Additionally there is no limitation in the number of methods you register on one particular event, i.e. you can register multiple methods for validation in "PrePersist" or "PreUpdate" or mix and share them in any combinations between those two events. There is no limit to what you can and can't validate in "PrePersist" and "PreUpdate" as long as you don't create new entity instances. This was already discussed in the previous blog post on the Versionable extension, which requires another type of event called "onFlush". Further readings: :doc:`Lifecycle Events <../reference/events>` doctrine2-2.4.8/docs/en/cookbook/working-with-datetime.rst000066400000000000000000000136041257105210500235350ustar00rootroot00000000000000Working with DateTime Instances =============================== There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner workings pretty well not to make mistakes with date handling. This cookbook entry holds several interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2. DateTime changes are detected by Reference ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When calling ``EntityManager#flush()`` Doctrine computes the changesets of all the currently managed entities and saves the differences to the database. In case of object properties (@Column(type="datetime") or @Column(type="object")) these comparisons are always made **BY REFERENCE**. That means the following change will **NOT** be saved into the database: .. code-block:: php updated->modify("now"); } } The way to go would be: .. code-block:: php updated = new \DateTime("now"); } } Default Timezone Gotcha ~~~~~~~~~~~~~~~~~~~~~~~ By default Doctrine assumes that you are working with a default timezone. Each DateTime instance that is created by Doctrine will be assigned the timezone that is currently the default, either through the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``. This is very important to handle correctly if your application runs on different serves or is moved from one to another server (with different timezone settings). You have to make sure that the timezone is the correct one on all this systems. Handling different Timezones with the DateTime Type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you first come across the requirement to save different you are still optimistic to manage this mess, however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2) that supports timezones correctly. Correctly here means that you can cover all the use-cases that can come up with timezones. If you don't believe me you should read up on `Storing DateTime in Databases `_. The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC. However with frequent daylight saving and political timezone changes you can have a UTC offset that moves in different offset directions depending on the real location. The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround that even allows correct date-time handling with timezones: 1. Always convert any DateTime instance to UTC. 2. Only set Timezones for displaying purposes 3. Save the Timezone in the Entity for persistence. Say we have an application for an international postal company and employees insert events regarding postal-package around the world, in their current timezones. To determine the exact time an event occurred means to save both the UTC time at the time of the booking and the timezone the event happened in. .. code-block:: php format($platform->getDateTimeFormatString(), (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC')) ); } public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $val = \DateTime::createFromFormat( $platform->getDateTimeFormatString(), $value, (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC')) ); if (!$val) { throw ConversionException::conversionFailed($value, $this->getName()); } return $val; } } This database type makes sure that every DateTime instance is always saved in UTC, relative to the current timezone that the passed DateTime instance has. To be able to transform these values back into their real timezone you have to save the timezone in a separate field of the entity requiring timezoned datetimes: .. code-block:: php localized = true; $this->created = $createDate; $this->timezone = $createDate->getTimeZone()->getName(); } public function getCreated() { if (!$this->localized) { $this->created->setTimeZone(new \DateTimeZone($this->timezone)); } return $this->created; } } This snippet makes use of the previously discussed "changeset by reference only" property of objects. That means a new DateTime will only be used during updating if the reference changes between retrieval and flush operation. This means we can easily go and modify the instance by setting the previous local timezone. doctrine2-2.4.8/docs/en/index.rst000066400000000000000000000112241257105210500166070ustar00rootroot00000000000000Welcome to Doctrine 2 ORM's documentation! ========================================== The Doctrine documentation is comprised of tutorials, a reference section and cookbook articles that explain different parts of the Object Relational mapper. Doctrine DBAL and Doctrine Common both have their own documentation. Getting Help ------------ If this documentation is not helping to answer questions you have about Doctrine ORM don't panic. You can get help from different sources: - There is a :doc:`FAQ ` with answers to frequent questions. - The `Doctrine Mailing List `_ - Internet Relay Chat (IRC) in #doctrine on Freenode - Report a bug on `JIRA `_. - On `Twitter `_ with ``#doctrine2`` - On `StackOverflow `_ If you need more structure over the different topics you can browse the :doc:`table of contents `. Getting Started --------------- * **Tutorial**: :doc:`Getting Started with Doctrine ` * **Setup**: :doc:`Installation & Configuration ` Mapping Objects onto a Database ------------------------------- * **Mapping**: :doc:`Objects ` | :doc:`Associations ` | :doc:`Inheritance ` * **Drivers**: :doc:`Docblock Annotations ` | :doc:`XML ` | :doc:`YAML ` | :doc:`PHP ` Working with Objects -------------------- * **Basic Reference**: :doc:`Entities ` | :doc:`Associations ` | :doc:`Events ` * **Query Reference**: :doc:`DQL ` | :doc:`QueryBuilder ` | :doc:`Native SQL ` * **Internals**: :doc:`Internals explained ` | :doc:`Associations ` Advanced Topics --------------- * :doc:`Architecture ` * :doc:`Advanced Configuration ` * :doc:`Limitations and knowns issues ` * :doc:`Commandline Tools ` * :doc:`Transactions and Concurrency ` * :doc:`Filters ` * :doc:`NamingStrategy ` * :doc:`Improving Performance ` * :doc:`Caching ` * :doc:`Partial Objects ` * :doc:`Change Tracking Policies ` * :doc:`Best Practices ` * :doc:`Metadata Drivers ` Tutorials --------- * :doc:`Indexed associations ` * :doc:`Extra Lazy Associations ` * :doc:`Composite Primary Keys ` * :doc:`Ordered associations ` * :doc:`Pagination ` * :doc:`Override Field/Association Mappings In Subclasses ` Cookbook -------- * **Patterns**: :doc:`Aggregate Fields ` | :doc:`Decorator Pattern ` | :doc:`Strategy Pattern ` * **DQL Extension Points**: :doc:`DQL Custom Walkers ` | :doc:`DQL User-Defined-Functions ` * **Implementation**: :doc:`Array Access ` | :doc:`Notify ChangeTracking Example ` | :doc:`Using Wakeup Or Clone ` | :doc:`Working with DateTime ` | :doc:`Validation ` | :doc:`Entities in the Session ` | :doc:`Keeping your Modules independent ` * **Integration into Frameworks/Libraries** :doc:`CodeIgniter ` * **Hidden Gems** :doc:`Prefixing Table Name ` * **Custom Datatypes** :doc:`MySQL Enums ` :doc:`Advanced Field Value Conversion ` .. include:: toc.rst doctrine2-2.4.8/docs/en/make.bat000066400000000000000000000060131257105210500163530ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation set SPHINXBUILD=sphinx-build set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end doctrine2-2.4.8/docs/en/reference/000077500000000000000000000000001257105210500167045ustar00rootroot00000000000000doctrine2-2.4.8/docs/en/reference/advanced-configuration.rst000066400000000000000000000355131257105210500240570ustar00rootroot00000000000000Advanced Configuration ====================== The configuration of the EntityManager requires a ``Doctrine\ORM\Configuration`` instance as well as some database connection parameters. This example shows all the potential steps of configuration. .. code-block:: php setMetadataCacheImpl($cache); $driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); $config->setMetadataDriverImpl($driverImpl); $config->setQueryCacheImpl($cache); $config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); $config->setProxyNamespace('MyProject\Proxies'); if ($applicationMode == "development") { $config->setAutoGenerateProxyClasses(true); } else { $config->setAutoGenerateProxyClasses(false); } $connectionOptions = array( 'driver' => 'pdo_sqlite', 'path' => 'database.sqlite' ); $em = EntityManager::create($connectionOptions, $config); .. note:: Do not use Doctrine without a metadata and query cache! Doctrine is optimized for working with caches. The main parts in Doctrine that are optimized for caching are the metadata mapping information with the metadata cache and the DQL to SQL conversions with the query cache. These 2 caches require only an absolute minimum of memory yet they heavily improve the runtime performance of Doctrine. The recommended cache driver to use with Doctrine is `APC `_. APC provides you with an opcode-cache (which is highly recommended anyway) and a very fast in-memory cache storage that you can use for the metadata and query caches as seen in the previous code snippet. Configuration Options --------------------- The following sections describe all the configuration options available on a ``Doctrine\ORM\Configuration`` instance. Proxy Directory (***REQUIRED***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php setProxyDir($dir); $config->getProxyDir(); Gets or sets the directory where Doctrine generates any proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. Proxy Namespace (***REQUIRED***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php setProxyNamespace($namespace); $config->getProxyNamespace(); Gets or sets the namespace to use for generated proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down. Metadata Driver (***REQUIRED***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php setMetadataDriverImpl($driver); $config->getMetadataDriverImpl(); Gets or sets the metadata driver implementation that is used by Doctrine to acquire the object-relational metadata for your classes. There are currently 4 available implementations: - ``Doctrine\ORM\Mapping\Driver\AnnotationDriver`` - ``Doctrine\ORM\Mapping\Driver\XmlDriver`` - ``Doctrine\ORM\Mapping\Driver\YamlDriver`` - ``Doctrine\ORM\Mapping\Driver\DriverChain`` Throughout the most part of this manual the AnnotationDriver is used in the examples. For information on the usage of the XmlDriver or YamlDriver please refer to the dedicated chapters ``XML Mapping`` and ``YAML Mapping``. The annotation driver can be configured with a factory method on the ``Doctrine\ORM\Configuration``: .. code-block:: php newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); $config->setMetadataDriverImpl($driverImpl); The path information to the entities is required for the annotation driver, because otherwise mass-operations on all entities through the console could not work correctly. All of metadata drivers accept either a single directory as a string or an array of directories. With this feature a single driver can support multiple directories of Entities. Metadata Cache (***RECOMMENDED***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php setMetadataCacheImpl($cache); $config->getMetadataCacheImpl(); Gets or sets the cache implementation to use for caching metadata information, that is, all the information you supply via annotations, xml or yaml, so that they do not need to be parsed and loaded from scratch on every single request which is a waste of resources. The cache implementation must implement the ``Doctrine\Common\Cache\Cache`` interface. Usage of a metadata cache is highly recommended. The recommended implementations for production are: - ``Doctrine\Common\Cache\ApcCache`` - ``Doctrine\Common\Cache\MemcacheCache`` - ``Doctrine\Common\Cache\XcacheCache`` - ``Doctrine\Common\Cache\RedisCache`` For development you should use the ``Doctrine\Common\Cache\ArrayCache`` which only caches data on a per-request basis. Query Cache (***RECOMMENDED***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php setQueryCacheImpl($cache); $config->getQueryCacheImpl(); Gets or sets the cache implementation to use for caching DQL queries, that is, the result of a DQL parsing process that includes the final SQL as well as meta information about how to process the SQL result set of a query. Note that the query cache does not affect query results. You do not get stale data. This is a pure optimization cache without any negative side-effects (except some minimal memory usage in your cache). Usage of a query cache is highly recommended. The recommended implementations for production are: - ``Doctrine\Common\Cache\ApcCache`` - ``Doctrine\Common\Cache\MemcacheCache`` - ``Doctrine\Common\Cache\XcacheCache`` - ``Doctrine\Common\Cache\RedisCache`` For development you should use the ``Doctrine\Common\Cache\ArrayCache`` which only caches data on a per-request basis. SQL Logger (***Optional***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php setSQLLogger($logger); $config->getSQLLogger(); Gets or sets the logger to use for logging all SQL statements executed by Doctrine. The logger class must implement the ``Doctrine\DBAL\Logging\SQLLogger`` interface. A simple default implementation that logs to the standard output using ``echo`` and ``var_dump`` can be found at ``Doctrine\DBAL\Logging\EchoSQLLogger``. Auto-generating Proxy Classes (***OPTIONAL***) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php setAutoGenerateProxyClasses($bool); $config->getAutoGenerateProxyClasses(); Gets or sets whether proxy classes should be generated automatically at runtime by Doctrine. If set to ``FALSE``, proxy classes must be generated manually through the doctrine command line task ``generate-proxies``. The strongly recommended value for a production environment is ``FALSE``. Development vs Production Configuration --------------------------------------- You should code your Doctrine2 bootstrapping with two different runtime models in mind. There are some serious benefits of using APC or Memcache in production. In development however this will frequently give you fatal errors, when you change your entities and the cache still keeps the outdated metadata. That is why we recommend the ``ArrayCache`` for development. Furthermore you should have the Auto-generating Proxy Classes option to true in development and to false in production. If this option is set to ``TRUE`` it can seriously hurt your script performance if several proxy classes are re-generated during script execution. Filesystem calls of that magnitude can even slower than all the database queries Doctrine issues. Additionally writing a proxy sets an exclusive file lock which can cause serious performance bottlenecks in systems with regular concurrent requests. Connection Options ------------------ The ``$connectionOptions`` passed as the first argument to ``EntityManager::create()`` has to be either an array or an instance of ``Doctrine\DBAL\Connection``. If an array is passed it is directly passed along to the DBAL Factory ``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL configuration is explained in the `DBAL section <./../../../../../dbal/2.0/docs/reference/configuration/en>`_. Proxy Objects ------------- A proxy object is an object that is put in place or used instead of the "real" object. A proxy object can add behavior to the object being proxied without that object being aware of it. In Doctrine 2, proxy objects are used to realize several features but mainly for transparent lazy-loading. Proxy objects with their lazy-loading facilities help to keep the subset of objects that are already in memory connected to the rest of the objects. This is an essential property as without it there would always be fragile partial objects at the outer edges of your object graph. Doctrine 2 implements a variant of the proxy pattern where it generates classes that extend your entity classes and adds lazy-loading capabilities to them. Doctrine can then give you an instance of such a proxy class whenever you request an object of the class being proxied. This happens in two situations: Reference Proxies ~~~~~~~~~~~~~~~~~ The method ``EntityManager#getReference($entityName, $identifier)`` lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier. You could simply do this: .. code-block:: php getReference('MyProject\Model\Item', $itemId); $cart->addItem($item); Here, we added an Item to a Cart without loading the Item from the database. If you invoke any method on the Item instance, it would fully initialize its state transparently from the database. Here $item is actually an instance of the proxy class that was generated for the Item class but your code does not need to care. In fact it **should not care**. Proxy objects should be transparent to your code. Association proxies ~~~~~~~~~~~~~~~~~~~ The second most important situation where Doctrine uses proxy objects is when querying for objects. Whenever you query for an object that has a single-valued association to another object that is configured LAZY, without joining that association in the same query, Doctrine puts proxy objects in place where normally the associated object would be. Just like other proxies it will transparently initialize itself on first access. .. note:: Joining an association in a DQL or native query essentially means eager loading of that association in that query. This will override the 'fetch' option specified in the mapping for that association, but only for that query. Generating Proxy classes ~~~~~~~~~~~~~~~~~~~~~~~~ Proxy classes can either be generated manually through the Doctrine Console or automatically by Doctrine. The configuration option that controls this behavior is: .. code-block:: php setAutoGenerateProxyClasses($bool); $config->getAutoGenerateProxyClasses(); The default value is ``TRUE`` for convenient development. However, this setting is not optimal for performance and therefore not recommended for a production environment. To eliminate the overhead of proxy class generation during runtime, set this configuration option to ``FALSE``. When you do this in a development environment, note that you may get class/file not found errors if certain proxy classes are not available or failing lazy-loads if new methods were added to the entity class that are not yet in the proxy class. In such a case, simply use the Doctrine Console to (re)generate the proxy classes like so: .. code-block:: php $ ./doctrine orm:generate-proxies Autoloading Proxies ------------------- When you deserialize proxy objects from the session or any other storage it is necessary to have an autoloading mechanism in place for these classes. For implementation reasons Proxy class names are not PSR-0 compliant. This means that you have to register a special autoloader for these classes: .. code-block:: php addDriver($xmlDriver, 'Doctrine\Tests\Models\Company'); $chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping'); Based on the namespace of the entity the loading of entities is delegated to the appropriate driver. The chain semantics come from the fact that the driver loops through all namespaces and matches the entity class name against the namespace using a ``strpos() === 0`` call. This means you need to order the drivers correctly if sub-namespaces use different metadata driver implementations. Default Repository (***OPTIONAL***) ----------------------------------- Specifies the FQCN of a subclass of the EntityRepository. That will be available for all entities without a custom repository class. .. code-block:: php setDefaultRepositoryClassName($fqcn); $config->getDefaultRepositoryClassName(); The default value is ``Doctrine\ORM\EntityRepository``. Any repository class must be a subclass of EntityRepository otherwise you got an ORMException Setting up the Console ---------------------- Doctrine uses the Symfony Console component for generating the command line interface. You can take a look at the ``vendor/bin/doctrine.php`` script and the ``Doctrine\ORM\Tools\Console\ConsoleRunner`` command for inspiration how to setup the cli. In general the required code looks like this: .. code-block:: php setCatchExceptions(true); $cli->setHelperSet($helperSet); Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli); $cli->run(); doctrine2-2.4.8/docs/en/reference/annotations-reference.rst000066400000000000000000000756311257105210500237430ustar00rootroot00000000000000Annotations Reference ===================== In this chapter a reference of every Doctrine 2 Annotation is given with short explanations on their context and usage. Index ----- - :ref:`@Column ` - :ref:`@ColumnResult ` - :ref:`@ChangeTrackingPolicy ` - :ref:`@DiscriminatorColumn ` - :ref:`@DiscriminatorMap ` - :ref:`@Entity ` - :ref:`@EntityResult ` - :ref:`@FieldResult ` - :ref:`@GeneratedValue ` - :ref:`@HasLifecycleCallbacks ` - :ref:`@Index ` - :ref:`@Id ` - :ref:`@InheritanceType ` - :ref:`@JoinColumn ` - :ref:`@JoinColumns ` - :ref:`@JoinTable ` - :ref:`@ManyToOne ` - :ref:`@ManyToMany ` - :ref:`@MappedSuperclass ` - :ref:`@NamedNativeQuery ` - :ref:`@OneToOne ` - :ref:`@OneToMany ` - :ref:`@OrderBy ` - :ref:`@PostLoad ` - :ref:`@PostPersist ` - :ref:`@PostRemove ` - :ref:`@PostUpdate ` - :ref:`@PrePersist ` - :ref:`@PreRemove ` - :ref:`@PreUpdate ` - :ref:`@SequenceGenerator ` - :ref:`@SqlResultSetMapping ` - :ref:`@Table ` - :ref:`@UniqueConstraint ` - :ref:`@Version ` Reference --------- .. _annref_column: @Column ~~~~~~~ Marks an annotated instance variable as "persistent". It has to be inside the instance variables PHP DocBlock comment. Any value hold inside this variable will be saved to and loaded from the database as part of the lifecycle of the instance variables entity-class. Required attributes: - **type**: Name of the Doctrine Type which is converted between PHP and Database representation. Optional attributes: - **name**: By default the property name is used for the database column name also, however the 'name' attribute allows you to determine the column name. - **length**: Used by the "string" type to determine its maximum length in the database. Doctrine does not validate the length of a string values for you. - **precision**: The precision for a decimal (exact numeric) column (Applies only for decimal column) - **scale**: The scale for a decimal (exact numeric) column (Applies only for decimal column) - **unique**: Boolean value to determine if the value of the column should be unique across all rows of the underlying entities table. - **nullable**: Determines if NULL values allowed for this column. - **columnDefinition**: DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. However you should make careful use of this feature and the consequences. SchemaTool will not detect changes on the column correctly anymore if you use "columnDefinition". Additionally you should remember that the "type" attribute still handles the conversion between PHP and Database values. If you use this attribute on a column that is used for joins between tables you should also take a look at :ref:`@JoinColumn `. Examples: .. code-block:: php ` can be found in the configuration section. Example: .. code-block:: php = 2.1) Specifies that this entity is marked as read only and not considered for change-tracking. Entities of this type can be persisted and removed though. Example: .. code-block:: php `. This annotation is optional and only has meaning when used in conjunction with @Id. If this annotation is not specified with @Id the NONE strategy is used as default. Required attributes: - **strategy**: Set the name of the identifier generation strategy. Valid values are AUTO, SEQUENCE, TABLE, IDENTITY, UUID, CUSTOM and NONE. Example: .. code-block:: php ` annotation on the entity-class level. It allows to hint the SchemaTool to generate a database index on the specified table columns. It only has meaning in the SchemaTool schema generation context. Required attributes: - **name**: Name of the Index - **columns**: Array of columns. Example: .. code-block:: php ` and :ref:`@DiscriminatorColumn ` annotations. Examples: .. code-block:: php `, :ref:`@OneToOne ` fields and in the Context of :ref:`@JoinTable ` nested inside a @ManyToMany. This annotation is not required. If its not specified the attributes *name* and *referencedColumnName* are inferred from the table and primary key names. Required attributes: - **name**: Column name that holds the foreign key identifier for this relation. In the context of @JoinTable it specifies the column name in the join table. - **referencedColumnName**: Name of the primary key identifier that is used for joining of this relation. Optional attributes: - **unique**: Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false. - **nullable**: Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true. - **onDelete**: Cascade Action (Database-level) - **columnDefinition**: DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on @JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a "columnDefinition" attribute on :ref:`@Column ` also sets the related @JoinColumn's columnDefinition. This is necessary to make foreign keys work. Example: .. code-block:: php ` or :ref:`@OneToOne ` relation with an entity that has multiple identifiers. .. _annref_jointable: @JoinTable ~~~~~~~~~~~~~~ Using :ref:`@OneToMany ` or :ref:`@ManyToMany ` on the owning side of the relation requires to specify the @JoinTable annotation which describes the details of the database join table. If you do not specify @JoinTable on these relations reasonable mapping defaults apply using the affected table and the column names. Required attributes: - **name**: Database name of the join-table - **joinColumns**: An array of @JoinColumn annotations describing the join-relation between the owning entities table and the join table. - **inverseJoinColumns**: An array of @JoinColumn annotations describing the join-relation between the inverse entities table and the join table. Example: .. code-block:: php ` is an additional, optional annotation that has reasonable default configuration values using the table and names of the two related entities. Required attributes: - **targetEntity**: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! Optional attributes: - **mappedBy**: This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship. - **inversedBy**: The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. - **cascade**: Cascade Option - **fetch**: One of LAZY, EXTRA_LAZY or EAGER - **indexBy**: Index the collection by a field on the target entity. .. note:: For ManyToMany bidirectional relationships either side may be the owning side (the side that defines the @JoinTable and/or does not make use of the mappedBy attribute, thus using a default join table). Example: .. code-block:: php `. Optional attributes: - **repositoryClass**: (>= 2.2) Specifies the FQCN of a subclass of the EntityRepository. That will be inherited for all subclasses of that Mapped Superclass. Example: .. code-block:: php ` with one additional option that can be specified. The configuration defaults for :ref:`@JoinColumn ` using the target entity table and primary key column names apply here too. Required attributes: - **targetEntity**: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! Optional attributes: - **cascade**: Cascade Option - **fetch**: One of LAZY or EAGER - **orphanRemoval**: Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any owning instance, should be removed by Doctrine. Defaults to false. - **inversedBy**: The inversedBy attribute designates the field in the entity that is the inverse side of the relationship. Example: .. code-block:: php ` or :ref:`@OneToMany ` annotation to specify by which criteria the collection should be retrieved from the database by using an ORDER BY clause. This annotation requires a single non-attributed value with an DQL snippet: Example: .. code-block:: php ` annotation on the entity-class level. It allows to hint the SchemaTool to generate a database unique constraint on the specified table columns. It only has meaning in the SchemaTool schema generation context. Required attributes: - **name**: Name of the Index - **columns**: Array of columns. Example: .. code-block:: php ` annotations that have the type integer or datetime. Combining @Version with :ref:`@Id ` is not supported. Example: .. code-block:: php `. - An entity class must not implement ``__wakeup`` or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`. Also consider implementing `Serializable `_ instead. - Any two entity classes in a class hierarchy that inherit directly or indirectly from one another must not have a mapped property with the same name. That is, if B inherits from A then B must not have a mapped field with the same name as an already mapped field that is inherited from A. - An entity cannot make use of func_get_args() to implement variable parameters. Generated proxies do not support this for performance reasons and your code might actually fail to work when violating this restriction. Entities support inheritance, polymorphic associations, and polymorphic queries. Both abstract and concrete classes can be entities. Entities may extend non-entity classes as well as entity classes, and non-entity classes may extend entity classes. .. note:: The constructor of an entity is only ever invoked when *you* construct a new instance with the *new* keyword. Doctrine never calls entity constructors, thus you are free to use them as you wish and even have it require arguments of any type. Entity states ~~~~~~~~~~~~~ An entity instance can be characterized as being NEW, MANAGED, DETACHED or REMOVED. - A NEW entity instance has no persistent identity, and is not yet associated with an EntityManager and a UnitOfWork (i.e. those just created with the "new" operator). - A MANAGED entity instance is an instance with a persistent identity that is associated with an EntityManager and whose persistence is thus managed. - A DETACHED entity instance is an instance with a persistent identity that is not (or no longer) associated with an EntityManager and a UnitOfWork. - A REMOVED entity instance is an instance with a persistent identity, associated with an EntityManager, that will be removed from the database upon transaction commit. .. _architecture_persistent_fields: Persistent fields ~~~~~~~~~~~~~~~~~ The persistent state of an entity is represented by instance variables. An instance variable must be directly accessed only from within the methods of the entity by the entity instance itself. Instance variables must not be accessed by clients of the entity. The state of the entity is available to clients only through the entity’s methods, i.e. accessor methods (getter/setter methods) or other business methods. Collection-valued persistent fields and properties must be defined in terms of the ``Doctrine\Common\Collections\Collection`` interface. The collection implementation type may be used by the application to initialize fields or properties before the entity is made persistent. Once the entity becomes managed (or detached), subsequent access must be through the interface type. Serializing entities ~~~~~~~~~~~~~~~~~~~~ Serializing entities can be problematic and is not really recommended, at least not as long as an entity instance still holds references to proxy objects or is still managed by an EntityManager. If you intend to serialize (and unserialize) entity instances that still hold references to proxy objects you may run into problems with private properties because of technical limitations. Proxy objects implement ``__sleep`` and it is not possible for ``__sleep`` to return names of private properties in parent classes. On the other hand it is not a solution for proxy objects to implement ``Serializable`` because Serializable does not work well with any potential cyclic object references (at least we did not find a way yet, if you did, please contact us). The EntityManager ~~~~~~~~~~~~~~~~~ The ``EntityManager`` class is a central access point to the ORM functionality provided by Doctrine 2. The ``EntityManager`` API is used to manage the persistence of your objects and to query for persistent objects. Transactional write-behind ~~~~~~~~~~~~~~~~~~~~~~~~~~ An ``EntityManager`` and the underlying ``UnitOfWork`` employ a strategy called "transactional write-behind" that delays the execution of SQL statements in order to execute them in the most efficient way and to execute them at the end of a transaction so that all write locks are quickly released. You should see Doctrine as a tool to synchronize your in-memory objects with the database in well defined units of work. Work with your objects and modify them as usual and when you're done call ``EntityManager#flush()`` to make your changes persistent. The Unit of Work ~~~~~~~~~~~~~~~~ Internally an ``EntityManager`` uses a ``UnitOfWork``, which is a typical implementation of the `Unit of Work pattern `_, to keep track of all the things that need to be done the next time ``flush`` is invoked. You usually do not directly interact with a ``UnitOfWork`` but with the ``EntityManager`` instead. doctrine2-2.4.8/docs/en/reference/association-mapping.rst000066400000000000000000000764241257105210500234200ustar00rootroot00000000000000Association Mapping =================== This chapter introduces association mappings which are used to explain references between objects and are mapped to a relational database using foreign keys. Instead of working with the foreign keys directly you will always work with references to objects: - A reference to a single object is represented by a foreign key. - A collection of objects is represented by many foreign keys pointing to the object holding the collection This chapter is split into three different sections. - A list of all the possible association mapping use-cases is given. - :ref:`association_mapping_defaults` are explained that simplify the use-case examples. - :ref:`collections` are introduced that contain entities in associations. To master associations you should also learn about :doc:`owning and inverse sides of associations ` One-To-One, Unidirectional -------------------------- A unidirectional one-to-one association is very common. Here is an example of a ``Product`` that has one ``Shipping`` object associated to it. The ``Shipping`` side does not reference back to the ``Product`` so it is unidirectional. .. configuration-block:: .. code-block:: php .. code-block:: yaml Product: type: entity oneToOne: shipping: targetEntity: Shipping joinColumn: name: shipping_id referencedColumnName: id Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. Generated MySQL Schema: .. code-block:: sql CREATE TABLE Product ( id INT AUTO_INCREMENT NOT NULL, shipping_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipping_id), PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE Shipping ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id); One-To-One, Bidirectional ------------------------- Here is a one-to-one relationship between a ``Customer`` and a ``Cart``. The ``Cart`` has a reference back to the ``Customer`` so it is bidirectional. .. configuration-block:: .. code-block:: php .. code-block:: yaml Customer: oneToOne: cart: targetEntity: Cart mappedBy: customer Cart: oneToOne: customer: targetEntity: Customer inversedBy: cart joinColumn: name: customer_id referencedColumnName: id Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. Generated MySQL Schema: .. code-block:: sql CREATE TABLE Cart ( id INT AUTO_INCREMENT NOT NULL, customer_id INT DEFAULT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE Customer ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id); See how the foreign key is defined on the owning side of the relation, the table ``Cart``. One-To-One, Self-referencing ---------------------------- You can easily have self referencing one-to-one relationships like below. .. code-block:: php phonenumbers = new \Doctrine\Common\Collections\ArrayCollection(); } // ... } /** @Entity **/ class Phonenumber { // ... } .. code-block:: xml .. code-block:: yaml User: type: entity manyToMany: phonenumbers: targetEntity: Phonenumber joinTable: name: users_phonenumbers joinColumns: user_id: referencedColumnName: id inverseJoinColumns: phonenumber_id: referencedColumnName: id unique: true Generates the following MySQL Schema: .. code-block:: sql CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE users_phonenumbers ( user_id INT NOT NULL, phonenumber_id INT NOT NULL, UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id), PRIMARY KEY(user_id, phonenumber_id) ) ENGINE = InnoDB; CREATE TABLE Phonenumber ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id); ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id); Many-To-One, Unidirectional --------------------------- You can easily implement a many-to-one unidirectional association with the following: .. configuration-block:: .. code-block:: php .. code-block:: yaml User: type: entity manyToOne: address: targetEntity: Address joinColumn: name: address_id referencedColumnName: id .. note:: The above ``@JoinColumn`` is optional as it would default to ``address_id`` and ``id`` anyways. You can omit it and let it use the defaults. Generated MySQL Schema: .. code-block:: sql CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, address_id INT DEFAULT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE Address ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id); One-To-Many, Bidirectional -------------------------- Bidirectional one-to-many associations are very common. The following code shows an example with a Product and a Feature class: .. configuration-block:: .. code-block:: php features = new \Doctrine\Common\Collections\ArrayCollection(); } } /** @Entity **/ class Feature { // ... /** * @ManyToOne(targetEntity="Product", inversedBy="features") * @JoinColumn(name="product_id", referencedColumnName="id") **/ private $product; // ... } .. code-block:: xml .. code-block:: yaml Product: type: entity oneToMany: features: targetEntity: Feature mappedBy: product Feature: type: entity manyToOne: product: targetEntity: Product inversedBy: features joinColumn: name: product_id referencedColumnName: id Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. Generated MySQL Schema: .. code-block:: sql CREATE TABLE Product ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE Feature ( id INT AUTO_INCREMENT NOT NULL, product_id INT DEFAULT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id); One-To-Many, Self-referencing ----------------------------- You can also setup a one-to-many association that is self-referencing. In this example we setup a hierarchy of ``Category`` objects by creating a self referencing relationship. This effectively models a hierarchy of categories and from the database perspective is known as an adjacency list approach. .. configuration-block:: .. code-block:: php children = new \Doctrine\Common\Collections\ArrayCollection(); } } .. code-block:: xml .. code-block:: yaml Category: type: entity oneToMany: children: targetEntity: Category mappedBy: parent manyToOne: parent: targetEntity: Category inversedBy: children Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same. Generated MySQL Schema: .. code-block:: sql CREATE TABLE Category ( id INT AUTO_INCREMENT NOT NULL, parent_id INT DEFAULT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE Category ADD FOREIGN KEY (parent_id) REFERENCES Category(id); Many-To-Many, Unidirectional ---------------------------- Real many-to-many associations are less common. The following example shows a unidirectional association between User and Group entities: .. configuration-block:: .. code-block:: php groups = new \Doctrine\Common\Collections\ArrayCollection(); } } /** @Entity **/ class Group { // ... } .. code-block:: xml .. code-block:: yaml User: type: entity manyToMany: groups: targetEntity: Group joinTable: name: users_groups joinColumns: user_id: referencedColumnName: id inverseJoinColumns: group_id: referencedColumnName: id Generated MySQL Schema: .. code-block:: sql CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE users_groups ( user_id INT NOT NULL, group_id INT NOT NULL, PRIMARY KEY(user_id, group_id) ) ENGINE = InnoDB; CREATE TABLE Group ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE users_groups ADD FOREIGN KEY (user_id) REFERENCES User(id); ALTER TABLE users_groups ADD FOREIGN KEY (group_id) REFERENCES Group(id); .. note:: Why are many-to-many associations less common? Because frequently you want to associate additional attributes with an association, in which case you introduce an association class. Consequently, the direct many-to-many association disappears and is replaced by one-to-many/many-to-one associations between the 3 participating classes. Many-To-Many, Bidirectional --------------------------- Here is a similar many-to-many relationship as above except this one is bidirectional. .. configuration-block:: .. code-block:: php groups = new \Doctrine\Common\Collections\ArrayCollection(); } // ... } /** @Entity **/ class Group { // ... /** * @ManyToMany(targetEntity="User", mappedBy="groups") **/ private $users; public function __construct() { $this->users = new \Doctrine\Common\Collections\ArrayCollection(); } // ... } .. code-block:: xml .. code-block:: yaml User: type: entity manyToMany: groups: targetEntity: Group inversedBy: users joinTable: name: users_groups joinColumns: user_id: referencedColumnName: id inverseJoinColumns: group_id: referencedColumnName: id Group: type: entity manyToMany: users: targetEntity: User mappedBy: groups The MySQL schema is exactly the same as for the Many-To-Many uni-directional case above. Picking Owning and Inverse Side ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For Many-To-Many associations you can chose which entity is the owning and which the inverse side. There is a very simple semantic rule to decide which side is more suitable to be the owning side from a developers perspective. You only have to ask yourself, which entity is responsible for the connection management and pick that as the owning side. Take an example of two entities ``Article`` and ``Tag``. Whenever you want to connect an Article to a Tag and vice-versa, it is mostly the Article that is responsible for this relation. Whenever you add a new article, you want to connect it with existing or new tags. Your create Article form will probably support this notion and allow to specify the tags directly. This is why you should pick the Article as owning side, as it makes the code more understandable: .. code-block:: php addArticle($this); // synchronously updating inverse side $this->tags[] = $tag; } } class Tag { private $articles; public function addArticle(Article $article) { $this->articles[] = $article; } } This allows to group the tag adding on the ``Article`` side of the association: .. code-block:: php addTag($tagA); $article->addTag($tagB); Many-To-Many, Self-referencing ------------------------------ You can even have a self-referencing many-to-many association. A common scenario is where a ``User`` has friends and the target entity of that relationship is a ``User`` so it is self referencing. In this example it is bidirectional so ``User`` has a field named ``$friendsWithMe`` and ``$myFriends``. .. code-block:: php friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection(); $this->myFriends = new \Doctrine\Common\Collections\ArrayCollection(); } // ... } Generated MySQL Schema: .. code-block:: sql CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE friends ( user_id INT NOT NULL, friend_user_id INT NOT NULL, PRIMARY KEY(user_id, friend_user_id) ) ENGINE = InnoDB; ALTER TABLE friends ADD FOREIGN KEY (user_id) REFERENCES User(id); ALTER TABLE friends ADD FOREIGN KEY (friend_user_id) REFERENCES User(id); .. _association_mapping_defaults: Mapping Defaults ---------------- Before we introduce all the association mappings in detail, you should note that the @JoinColumn and @JoinTable definitions are usually optional and have sensible default values. The defaults for a join column in a one-to-one/many-to-one association is as follows: :: name: "_id" referencedColumnName: "id" As an example, consider this mapping: .. configuration-block:: .. code-block:: php .. code-block:: yaml Product: type: entity oneToOne: shipping: targetEntity: Shipping This is essentially the same as the following, more verbose, mapping: .. configuration-block:: .. code-block:: php .. code-block:: yaml Product: type: entity oneToOne: shipping: targetEntity: Shipping joinColumn: name: shipping_id referencedColumnName: id The @JoinTable definition used for many-to-many mappings has similar defaults. As an example, consider this mapping: .. configuration-block:: .. code-block:: php .. code-block:: yaml User: type: entity manyToMany: groups: targetEntity: Group This is essentially the same as the following, more verbose, mapping: .. configuration-block:: .. code-block:: php .. code-block:: yaml User: type: entity manyToMany: groups: targetEntity: Group joinTable: name: User_Group joinColumns: User_id: referencedColumnName: id inverseJoinColumns: Group_id: referencedColumnName: id In that case, the name of the join table defaults to a combination of the simple, unqualified class names of the participating classes, separated by an underscore character. The names of the join columns default to the simple, unqualified class name of the targeted class followed by "\_id". The referencedColumnName always defaults to "id", just as in one-to-one or many-to-one mappings. If you accept these defaults, you can reduce the mapping code to a minimum. .. _collections: Collections ----------- In all the examples of many-valued associations in this manual we will make use of a ``Collection`` interface and a corresponding default implementation ``ArrayCollection`` that are defined in the ``Doctrine\Common\Collections`` namespace. Why do we need that? Doesn't that couple my domain model to Doctrine? Unfortunately, PHP arrays, while being great for many things, do not make up for good collections of business objects, especially not in the context of an ORM. The reason is that plain PHP arrays can not be transparently extended / instrumented in PHP code, which is necessary for a lot of advanced ORM features. The classes / interfaces that come closest to an OO collection are ArrayAccess and ArrayObject but until instances of these types can be used in all places where a plain array can be used (something that may happen in PHP6) their usability is fairly limited. You "can" type-hint on ``ArrayAccess`` instead of ``Collection``, since the Collection interface extends ``ArrayAccess``, but this will severely limit you in the way you can work with the collection, because the ``ArrayAccess`` API is (intentionally) very primitive and more importantly because you can not pass this collection to all the useful PHP array functions, which makes it very hard to work with. .. warning:: The Collection interface and ArrayCollection class, like everything else in the Doctrine namespace, are neither part of the ORM, nor the DBAL, it is a plain PHP class that has no outside dependencies apart from dependencies on PHP itself (and the SPL). Therefore using this class in your domain classes and elsewhere does not introduce a coupling to the persistence layer. The Collection class, like everything else in the Common namespace, is not part of the persistence layer. You could even copy that class over to your project if you want to remove Doctrine from your project and all your domain classes will work the same as before. Initializing Collections ------------------------ You have to be careful when using entity fields that contain a collection of related entities. Say we have a User entity that contains a collection of groups: .. code-block:: php groups; } } With this code alone the ``$groups`` field only contains an instance of ``Doctrine\Common\Collections\Collection`` if the user is retrieved from Doctrine, however not after you instantiated a fresh instance of the User. When your user entity is still new ``$groups`` will obviously be null. This is why we recommend to initialize all collection fields to an empty ``ArrayCollection`` in your entities constructor: .. code-block:: php groups = new ArrayCollection(); } public function getGroups() { return $this->groups; } } Now the following code will work even if the Entity hasn't been associated with an EntityManager yet: .. code-block:: php find('Group', $groupId); $user = new User(); $user->getGroups()->add($group); doctrine2-2.4.8/docs/en/reference/basic-mapping.rst000066400000000000000000000541021257105210500221520ustar00rootroot00000000000000Basic Mapping ============= This chapter explains the basic mapping of objects and properties. Mapping of associations will be covered in the next chapter "Association Mapping". Mapping Drivers --------------- Doctrine provides several different ways for specifying object-relational mapping metadata: - Docblock Annotations - XML - YAML This manual usually mentions docblock annotations in all the examples that are spread throughout all chapters, however for many examples alternative YAML and XML examples are given as well. There are dedicated reference chapters for XML and YAML mapping, respectively that explain them in more detail. There is also an Annotation reference chapter. .. note:: If you're wondering which mapping driver gives the best performance, the answer is: They all give exactly the same performance. Once the metadata of a class has been read from the source (annotations, xml or yaml) it is stored in an instance of the ``Doctrine\ORM\Mapping\ClassMetadata`` class and these instances are stored in the metadata cache. Therefore at the end of the day all drivers perform equally well. If you're not using a metadata cache (not recommended!) then the XML driver might have a slight edge in performance due to the powerful native XML support in PHP. Introduction to Docblock Annotations ------------------------------------ You've probably used docblock annotations in some form already, most likely to provide documentation metadata for a tool like ``PHPDocumentor`` (@author, @link, ...). Docblock annotations are a tool to embed metadata inside the documentation section which can then be processed by some tool. Doctrine 2 generalizes the concept of docblock annotations so that they can be used for any kind of metadata and so that it is easy to define new docblock annotations. In order to allow more involved annotation values and to reduce the chances of clashes with other docblock annotations, the Doctrine 2 docblock annotations feature an alternative syntax that is heavily inspired by the Annotation syntax introduced in Java 5. The implementation of these enhanced docblock annotations is located in the ``Doctrine\Common\Annotations`` namespace and therefore part of the Common package. Doctrine 2 docblock annotations support namespaces and nested annotations among other things. The Doctrine 2 ORM defines its own set of docblock annotations for supplying object-relational mapping metadata. .. note:: If you're not comfortable with the concept of docblock annotations, don't worry, as mentioned earlier Doctrine 2 provides XML and YAML alternatives and you could easily implement your own favourite mechanism for defining ORM metadata. Persistent classes ------------------ In order to mark a class for object-relational persistence it needs to be designated as an entity. This can be done through the ``@Entity`` marker annotation. .. configuration-block:: .. code-block:: php .. code-block:: yaml MyPersistentClass: type: entity # ... By default, the entity will be persisted to a table with the same name as the class name. In order to change that, you can use the ``@Table`` annotation as follows: .. configuration-block:: .. code-block:: php .. code-block:: yaml MyPersistentClass: type: entity table: my_persistent_class # ... Now instances of MyPersistentClass will be persisted into a table named ``my_persistent_class``. Doctrine Mapping Types ---------------------- A Doctrine Mapping Type defines the mapping between a PHP type and a SQL type. All Doctrine Mapping Types that ship with Doctrine are fully portable between different RDBMS. You can even write your own custom mapping types that might or might not be portable, which is explained later in this chapter. For example, the Doctrine Mapping Type ``string`` defines the mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc. depending on the RDBMS brand). Here is a quick overview of the built-in mapping types: - ``string``: Type that maps a SQL VARCHAR to a PHP string. - ``integer``: Type that maps a SQL INT to a PHP integer. - ``smallint``: Type that maps a database SMALLINT to a PHP integer. - ``bigint``: Type that maps a database BIGINT to a PHP string. - ``boolean``: Type that maps a SQL boolean to a PHP boolean. - ``decimal``: Type that maps a SQL DECIMAL to a PHP string. - ``date``: Type that maps a SQL DATETIME to a PHP DateTime object. - ``time``: Type that maps a SQL TIME to a PHP DateTime object. - ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP DateTime object. - ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP DateTime object with timezone. - ``text``: Type that maps a SQL CLOB to a PHP string. - ``object``: Type that maps a SQL CLOB to a PHP object using ``serialize()`` and ``unserialize()`` - ``array``: Type that maps a SQL CLOB to a PHP array using ``serialize()`` and ``unserialize()`` - ``simple_array``: Type that maps a SQL CLOB to a PHP array using ``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT* Only use this type if you are sure that your values cannot contain a ",". - ``json_array``: Type that maps a SQL CLOB to a PHP array using ``json_encode()`` and ``json_decode()`` - ``float``: Type that maps a SQL Float (Double Precision) to a PHP double. *IMPORTANT*: Works only with locale settings that use decimal points as separator. - ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to varchar but uses a specific type if the platform supports it. - ``blob``: Type that maps a SQL BLOB to a PHP resource stream .. note:: Doctrine Mapping Types are NOT SQL types and NOT PHP types! They are mapping types between 2 types. Additionally Mapping types are *case-sensitive*. For example, using a DateTime column will NOT match the datetime type that ships with Doctrine 2. .. note:: DateTime and Object types are compared by reference, not by value. Doctrine updates this values if the reference changes and therefore behaves as if these objects are immutable value objects. .. warning:: All Date types assume that you are exclusively using the default timezone set by `date_default_timezone_set() `_ or by the php.ini configuration ``date.timezone``. Working with different timezones will cause troubles and unexpected behavior. If you need specific timezone handling you have to handle this in your domain, converting all the values back and forth from UTC. There is also a :doc:`cookbook entry <../cookbook/working-with-datetime>` on working with datetimes that gives hints for implementing multi timezone applications. Property Mapping ---------------- After a class has been marked as an entity it can specify mappings for its instance fields. Here we will only look at simple fields that hold scalar values like strings, numbers, etc. Associations to other objects are covered in the chapter "Association Mapping". To mark a property for relational persistence the ``@Column`` docblock annotation is used. This annotation usually requires at least 1 attribute to be set, the ``type``. The ``type`` attribute specifies the Doctrine Mapping Type to use for the field. If the type is not specified, 'string' is used as the default mapping type since it is the most flexible. Example: .. configuration-block:: .. code-block:: php .. code-block:: yaml MyPersistentClass: type: entity fields: id: type: integer name: length: 50 In that example we mapped the field ``id`` to the column ``id`` using the mapping type ``integer`` and the field ``name`` is mapped to the column ``name`` with the default mapping type ``string``. As you can see, by default the column names are assumed to be the same as the field names. To specify a different name for the column, you can use the ``name`` attribute of the Column annotation as follows: .. configuration-block:: .. code-block:: php .. code-block:: yaml MyPersistentClass: type: entity fields: name: length: 50 column: db_name The Column annotation has some more attributes. Here is a complete list: - ``type``: (optional, defaults to 'string') The mapping type to use for the column. - ``column``: (optional, defaults to field name) The name of the column in the database. - ``length``: (optional, default 255) The length of the column in the database. (Applies only if a string-valued column is used). - ``unique``: (optional, default FALSE) Whether the column is a unique key. - ``nullable``: (optional, default FALSE) Whether the database column is nullable. - ``precision``: (optional, default 0) The precision for a decimal (exact numeric) column. (Applies only if a decimal column is used.) - ``scale``: (optional, default 0) The scale for a decimal (exact numeric) column. (Applies only if a decimal column is used.) .. _reference-basic-mapping-custom-mapping-types: Custom Mapping Types -------------------- Doctrine allows you to create new mapping types. This can come in handy when you're missing a specific mapping type or when you want to replace the existing implementation of a mapping type. In order to create a new mapping type you need to subclass ``Doctrine\DBAL\Types\Type`` and implement/override the methods as you wish. Here is an example skeleton of such a custom type class: .. code-block:: php getConnection(); $conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype'); Now using Schema-Tool, whenever it detects a column having the ``db_mytype`` it will convert it into a ``mytype`` Doctrine Type instance for Schema representation. Keep in mind that you can easily produce clashes this way, each database type can only map to exactly one Doctrine mapping type. Custom ColumnDefinition ----------------------- You can define a custom definition for each column using the "columnDefinition" attribute of ``@Column``. You have to define all the definitions that follow the name of a column here. .. note:: Using columnDefinition will break change-detection in SchemaTool. Identifiers / Primary Keys -------------------------- Every entity class needs an identifier/primary key. You designate the field that serves as the identifier with the ``@Id`` marker annotation. Here is an example: .. configuration-block:: .. code-block:: php .. code-block:: yaml MyPersistentClass: type: entity id: id: type: integer fields: name: length: 50 Without doing anything else, the identifier is assumed to be manually assigned. That means your code would need to properly set the identifier property before passing a new entity to ``EntityManager#persist($entity)``. A common alternative strategy is to use a generated value as the identifier. To do this, you use the ``@GeneratedValue`` annotation like this: .. configuration-block:: .. code-block:: php .. code-block:: yaml MyPersistentClass: type: entity id: id: type: integer generator: strategy: AUTO fields: name: length: 50 This tells Doctrine to automatically generate a value for the identifier. How this value is generated is specified by the ``strategy`` attribute, which is optional and defaults to 'AUTO'. A value of ``AUTO`` tells Doctrine to use the generation strategy that is preferred by the currently used database platform. See below for details. Identifier Generation Strategies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The previous example showed how to use the default identifier generation strategy without knowing the underlying database with the AUTO-detection strategy. It is also possible to specify the identifier generation strategy more explicitly, which allows to make use of some additional features. Here is the list of possible generation strategies: - ``AUTO`` (default): Tells Doctrine to pick the strategy that is preferred by the used database platform. The preferred strategies are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle and PostgreSQL. This strategy provides full portability. - ``SEQUENCE``: Tells Doctrine to use a database sequence for ID generation. This strategy does currently not provide full portability. Sequences are supported by Oracle and PostgreSql. - ``IDENTITY``: Tells Doctrine to use special identity columns in the database that generate a value on insertion of a row. This strategy does currently not provide full portability and is supported by the following platforms: MySQL/SQLite (AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL). - ``TABLE``: Tells Doctrine to use a separate table for ID generation. This strategy provides full portability. ***This strategy is not yet implemented!*** - ``NONE``: Tells Doctrine that the identifiers are assigned (and thus generated) by your code. The assignment must take place before a new entity is passed to ``EntityManager#persist``. NONE is the same as leaving off the @GeneratedValue entirely. Sequence Generator ^^^^^^^^^^^^^^^^^^ The Sequence Generator can currently be used in conjunction with Oracle or Postgres and allows some additional configuration options besides specifying the sequence's name: .. configuration-block:: .. code-block:: php .. code-block:: yaml MyPersistentClass: type: entity id: id: type: integer generator: strategy: SEQUENCE sequenceGenerator: sequenceName: tablename_seq allocationSize: 100 initialValue: 1 The initial value specifies at which value the sequence should start. The allocationSize is a powerful feature to optimize INSERT performance of Doctrine. The allocationSize specifies by how much values the sequence is incremented whenever the next value is retrieved. If this is larger than 1 (one) Doctrine can generate identifier values for the allocationSizes amount of entities. In the above example with ``allocationSize=100`` Doctrine 2 would only need to access the sequence once to generate the identifiers for 100 new entities. *The default allocationSize for a @SequenceGenerator is currently 10.* .. caution:: The allocationSize is detected by SchemaTool and transformed into an "INCREMENT BY " clause in the CREATE SEQUENCE statement. For a database schema created manually (and not SchemaTool) you have to make sure that the allocationSize configuration option is never larger than the actual sequences INCREMENT BY value, otherwise you may get duplicate keys. .. note:: It is possible to use strategy="AUTO" and at the same time specifying a @SequenceGenerator. In such a case, your custom sequence settings are used in the case where the preferred strategy of the underlying platform is SEQUENCE, such as for Oracle and PostgreSQL. Composite Keys ~~~~~~~~~~~~~~ Doctrine 2 allows to use composite primary keys. There are however some restrictions opposed to using a single identifier. The use of the ``@GeneratedValue`` annotation is only supported for simple (not composite) primary keys, which means you can only use composite keys if you generate the primary key values yourself before calling ``EntityManager#persist()`` on the entity. To designate a composite primary key / identifier, simply put the @Id marker annotation on all fields that make up the primary key. Quoting Reserved Words ---------------------- It may sometimes be necessary to quote a column or table name because it conflicts with a reserved word of the particular RDBMS in use. This is often referred to as "Identifier Quoting". To let Doctrine know that you would like a table or column name to be quoted in all SQL statements, enclose the table or column name in backticks. Here is an example: .. code-block:: php setStatus('user'); $user->setUsername('user' . $i); $user->setName('Mr.Smith-' . $i); $em->persist($user); if (($i % $batchSize) === 0) { $em->flush(); $em->clear(); // Detaches all objects from Doctrine! } } Bulk Updates ------------ There are 2 possibilities for bulk updates with Doctrine. DQL UPDATE ~~~~~~~~~~ The by far most efficient way for bulk updates is to use a DQL UPDATE query. Example: .. code-block:: php createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9'); $numUpdated = $q->execute(); Iterating results ~~~~~~~~~~~~~~~~~ An alternative solution for bulk updates is to use the ``Query#iterate()`` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this, combining the iteration with the batching strategy that was already used for bulk inserts: .. code-block:: php createQuery('select u from MyProject\Model\User u'); $iterableResult = $q->iterate(); foreach($iterableResult AS $row) { $user = $row[0]; $user->increaseCredit(); $user->calculateNewBonuses(); if (($i % $batchSize) === 0) { $em->flush(); // Executes all updates. $em->clear(); // Detaches all objects from Doctrine! } ++$i; } $em->flush(); .. note:: Iterating results is not possible with queries that fetch-join a collection-valued association. The nature of such SQL result sets is not suitable for incremental hydration. Bulk Deletes ------------ There are two possibilities for bulk deletes with Doctrine. You can either issue a single DQL DELETE query or you can iterate over results removing them one at a time. DQL DELETE ~~~~~~~~~~ The by far most efficient way for bulk deletes is to use a DQL DELETE query. Example: .. code-block:: php createQuery('delete from MyProject\Model\Manager m where m.salary > 100000'); $numDeleted = $q->execute(); Iterating results ~~~~~~~~~~~~~~~~~ An alternative solution for bulk deletes is to use the ``Query#iterate()`` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this: .. code-block:: php createQuery('select u from MyProject\Model\User u'); $iterableResult = $q->iterate(); while (($row = $iterableResult->next()) !== false) { $em->remove($row[0]); if (($i % $batchSize) === 0) { $em->flush(); // Executes all deletions. $em->clear(); // Detaches all objects from Doctrine! } ++$i; } $em->flush(); .. note:: Iterating results is not possible with queries that fetch-join a collection-valued association. The nature of such SQL result sets is not suitable for incremental hydration. Iterating Large Results for Data-Processing ------------------------------------------- You can use the ``iterate()`` method just to iterate over a large result and no UPDATE or DELETE intention. The ``IterableResult`` instance returned from ``$query->iterate()`` implements the Iterator interface so you can process a large result without memory problems using the following approach: .. code-block:: php _em->createQuery('select u from MyProject\Model\User u'); $iterableResult = $q->iterate(); foreach ($iterableResult AS $row) { // do stuff with the data in the row, $row[0] is always the object // detach from Doctrine, so that it can be Garbage-Collected immediately $this->_em->detach($row[0]); } .. note:: Iterating results is not possible with queries that fetch-join a collection-valued association. The nature of such SQL result sets is not suitable for incremental hydration. doctrine2-2.4.8/docs/en/reference/best-practices.rst000066400000000000000000000103641257105210500223520ustar00rootroot00000000000000Best Practices ============== The best practices mentioned here that affect database design generally refer to best practices when working with Doctrine and do not necessarily reflect best practices for database design in general. Don't use public properties on entities --------------------------------------- It is very important that you don't map public properties on entities, but only protected or private ones. The reason for this is simple, whenever you access a public property of a proxy object that hasn't been initialized yet the return value will be null. Doctrine cannot hook into this process and magically make the entity lazy load. This can create situations where it is very hard to debug the current failure. We therefore urge you to map only private and protected properties on entities and use getter methods or magic \_\_get() to access them. Constrain relationships as much as possible ------------------------------------------- It is important to constrain relationships as much as possible. This means: - Impose a traversal direction (avoid bidirectional associations if possible) - Eliminate nonessential associations This has several benefits: - Reduced coupling in your domain model - Simpler code in your domain model (no need to maintain bidirectionality properly) - Less work for Doctrine Avoid composite keys -------------------- Even though Doctrine fully supports composite keys it is best not to use them if possible. Composite keys require additional work by Doctrine and thus have a higher probability of errors. Use events judiciously ---------------------- The event system of Doctrine is great and fast. Even though making heavy use of events, especially lifecycle events, can have a negative impact on the performance of your application. Thus you should use events judiciously. Use cascades judiciously ------------------------ Automatic cascades of the persist/remove/merge/etc. operations are very handy but should be used wisely. Do NOT simply add all cascades to all associations. Think about which cascades actually do make sense for you for a particular association, given the scenarios it is most likely used in. Don't use special characters ---------------------------- Avoid using any non-ASCII characters in class, field, table or column names. Doctrine itself is not unicode-safe in many places and will not be until PHP itself is fully unicode-aware (PHP6). Don't use identifier quoting ---------------------------- Identifier quoting is a workaround for using reserved words that often causes problems in edge cases. Do not use identifier quoting and avoid using reserved words as table or column names. Initialize collections in the constructor ----------------------------------------- It is recommended best practice to initialize any business collections in entities in the constructor. Example: .. code-block:: php addresses = new ArrayCollection; $this->articles = new ArrayCollection; } } Don't map foreign keys to fields in an entity --------------------------------------------- Foreign keys have no meaning whatsoever in an object model. Foreign keys are how a relational database establishes relationships. Your object model establishes relationships through object references. Thus mapping foreign keys to object fields heavily leaks details of the relational model into the object model, something you really should not do. Use explicit transaction demarcation ------------------------------------ While Doctrine will automatically wrap all DML operations in a transaction on flush(), it is considered best practice to explicitly set the transaction boundaries yourself. Otherwise every single query is wrapped in a small transaction (Yes, SELECT queries, too) since you can not talk to your database outside of a transaction. While such short transactions for read-only (SELECT) queries generally don't have any noticeable performance impact, it is still preferable to use fewer, well-defined transactions that are established through explicit transaction boundaries. doctrine2-2.4.8/docs/en/reference/caching.rst000066400000000000000000000272351257105210500210430ustar00rootroot00000000000000Caching ======= Doctrine provides cache drivers in the ``Common`` package for some of the most popular caching implementations such as APC, Memcache and Xcache. We also provide an ``ArrayCache`` driver which stores the data in a PHP array. Obviously, the cache does not live between requests but this is useful for testing in a development environment. Cache Drivers ------------- The cache drivers follow a simple interface that is defined in ``Doctrine\Common\Cache\Cache``. All the cache drivers extend a base class ``Doctrine\Common\Cache\AbstractCache`` which implements the before mentioned interface. The interface defines the following methods for you to publicly use. - fetch($id) - Fetches an entry from the cache. - contains($id) - Test if an entry exists in the cache. - save($id, $data, $lifeTime = false) - Puts data into the cache. - delete($id) - Deletes a cache entry. Each driver extends the ``AbstractCache`` class which defines a few abstract protected methods that each of the drivers must implement. - \_doFetch($id) - \_doContains($id) - \_doSave($id, $data, $lifeTime = false) - \_doDelete($id) The public methods ``fetch()``, ``contains()``, etc. utilize the above protected methods that are implemented by the drivers. The code is organized this way so that the protected methods in the drivers do the raw interaction with the cache implementation and the ``AbstractCache`` can build custom functionality on top of these methods. APC ~~~ In order to use the APC cache driver you must have it compiled and enabled in your php.ini. You can read about APC `in the PHP Documentation `_. It will give you a little background information about what it is and how you can use it as well as how to install it. Below is a simple example of how you could use the APC cache driver by itself. .. code-block:: php save('cache_id', 'my_data'); Memcache ~~~~~~~~ In order to use the Memcache cache driver you must have it compiled and enabled in your php.ini. You can read about Memcache ` on the PHP website `_. It will give you a little background information about what it is and how you can use it as well as how to install it. Below is a simple example of how you could use the Memcache cache driver by itself. .. code-block:: php connect('memcache_host', 11211); $cacheDriver = new \Doctrine\Common\Cache\MemcacheCache(); $cacheDriver->setMemcache($memcache); $cacheDriver->save('cache_id', 'my_data'); Xcache ~~~~~~ In order to use the Xcache cache driver you must have it compiled and enabled in your php.ini. You can read about Xcache `here `_. It will give you a little background information about what it is and how you can use it as well as how to install it. Below is a simple example of how you could use the Xcache cache driver by itself. .. code-block:: php save('cache_id', 'my_data'); Redis ~~~~~ In order to use the Redis cache driver you must have it compiled and enabled in your php.ini. You can read about what is Redis `from here `_. Also check `A PHP extension for Redis `_ for how you can use and install Redis PHP extension. Below is a simple example of how you could use the Redis cache driver by itself. .. code-block:: php connect('redis_host', 6379); $cacheDriver = new \Doctrine\Common\Cache\RedisCache(); $cacheDriver->setRedis($redis); $cacheDriver->save('cache_id', 'my_data'); Using Cache Drivers ------------------- In this section we'll describe how you can fully utilize the API of the cache drivers to save cache, check if some cache exists, fetch the cached data and delete the cached data. We'll use the ``ArrayCache`` implementation as our example here. .. code-block:: php save('cache_id', 'my_data'); The ``save()`` method accepts three arguments which are described below. - ``$id`` - The cache id - ``$data`` - The cache entry/data. - ``$lifeTime`` - The lifetime. If != false, sets a specific lifetime for this cache entry (null => infinite lifeTime). You can save any type of data whether it be a string, array, object, etc. .. code-block:: php 'value1', 'key2' => 'value2' ); $cacheDriver->save('my_array', $array); Checking ~~~~~~~~ Checking whether some cache exists is very simple, just use the ``contains()`` method. It accepts a single argument which is the ID of the cache entry. .. code-block:: php contains('cache_id')) { echo 'cache exists'; } else { echo 'cache does not exist'; } Fetching ~~~~~~~~ Now if you want to retrieve some cache entry you can use the ``fetch()`` method. It also accepts a single argument just like ``contains()`` which is the ID of the cache entry. .. code-block:: php fetch('my_array'); Deleting ~~~~~~~~ As you might guess, deleting is just as easy as saving, checking and fetching. We have a few ways to delete cache entries. You can delete by an individual ID, regular expression, prefix, suffix or you can delete all entries. By Cache ID ^^^^^^^^^^^ .. code-block:: php delete('my_array'); All ^^^ If you simply want to delete all cache entries you can do so with the ``deleteAll()`` method. .. code-block:: php deleteAll(); Namespaces ~~~~~~~~~~ If you heavily use caching in your application and utilize it in multiple parts of your application, or use it in different applications on the same server you may have issues with cache naming collisions. This can be worked around by using namespaces. You can set the namespace a cache driver should use by using the ``setNamespace()`` method. .. code-block:: php setNamespace('my_namespace_'); Integrating with the ORM ------------------------ The Doctrine ORM package is tightly integrated with the cache drivers to allow you to improve performance of various aspects of Doctrine by just simply making some additional configurations and method calls. Query Cache ~~~~~~~~~~~ It is highly recommended that in a production environment you cache the transformation of a DQL query to its SQL counterpart. It doesn't make sense to do this parsing multiple times as it doesn't change unless you alter the DQL query. This can be done by configuring the query cache implementation to use on your ORM configuration. .. code-block:: php setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache()); Result Cache ~~~~~~~~~~~~ The result cache can be used to cache the results of your queries so that we don't have to query the database or hydrate the data again after the first time. You just need to configure the result cache implementation. .. code-block:: php setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache()); Now when you're executing DQL queries you can configure them to use the result cache. .. code-block:: php createQuery('select u from \Entities\User u'); $query->useResultCache(true); You can also configure an individual query to use a different result cache driver. .. code-block:: php setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache()); .. note:: Setting the result cache driver on the query will automatically enable the result cache for the query. If you want to disable it pass false to ``useResultCache()``. :: useResultCache(false); If you want to set the time the cache has to live you can use the ``setResultCacheLifetime()`` method. .. code-block:: php setResultCacheLifetime(3600); The ID used to store the result set cache is a hash which is automatically generated for you if you don't set a custom ID yourself with the ``setResultCacheId()`` method. .. code-block:: php setResultCacheId('my_custom_id'); You can also set the lifetime and cache ID by passing the values as the second and third argument to ``useResultCache()``. .. code-block:: php useResultCache(true, 3600, 'my_custom_id'); Metadata Cache ~~~~~~~~~~~~~~ Your class metadata can be parsed from a few different sources like YAML, XML, Annotations, etc. Instead of parsing this information on each request we should cache it using one of the cache drivers. Just like the query and result cache we need to configure it first. .. code-block:: php setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); Now the metadata information will only be parsed once and stored in the cache driver. Clearing the Cache ------------------ We've already shown you previously how you can use the API of the cache drivers to manually delete cache entries. For your convenience we offer a command line task for you to help you with clearing the query, result and metadata cache. From the Doctrine command line you can run the following command. .. code-block:: php $ ./doctrine clear-cache Running this task with no arguments will clear all the cache for all the configured drivers. If you want to be more specific about what you clear you can use the following options. To clear the query cache use the ``--query`` option. .. code-block:: php $ ./doctrine clear-cache --query To clear the metadata cache use the ``--metadata`` option. .. code-block:: php $ ./doctrine clear-cache --metadata To clear the result cache use the ``--result`` option. .. code-block:: php $ ./doctrine clear-cache --result When you use the ``--result`` option you can use some other options to be more specific about what queries result sets you want to clear. Just like the API of the cache drivers you can clear based on an ID, regular expression, prefix or suffix. .. code-block:: php $ ./doctrine clear-cache --result --id=cache_id Or if you want to clear based on a regular expressions. .. code-block:: php $ ./doctrine clear-cache --result --regex=users_.* Or with a prefix. .. code-block:: php $ ./doctrine clear-cache --result --prefix=users_ And finally with a suffix. .. code-block:: php $ ./doctrine clear-cache --result --suffix=_my_account .. note:: Using the ``--id``, ``--regex``, etc. options with the ``--query`` and ``--metadata`` are not allowed as it is not necessary to be specific about what you clear. You only ever need to completely clear the cache to remove stale entries. Cache Slams ----------- Something to be careful of when utilizing the cache drivers is cache slams. If you have a heavily trafficked website with some code that checks for the existence of a cache record and if it does not exist it generates the information and saves it to the cache. Now if 100 requests were issued all at the same time and each one sees the cache does not exist and they all try and insert the same cache entry it could lock up APC, Xcache, etc. and cause problems. Ways exist to work around this, like pre-populating your cache and not letting your users requests populate the cache. You can read more about cache slams `in this blog post `_. doctrine2-2.4.8/docs/en/reference/change-tracking-policies.rst000066400000000000000000000121761257105210500242770ustar00rootroot00000000000000Change Tracking Policies ======================== Change tracking is the process of determining what has changed in managed entities since the last time they were synchronized with the database. Doctrine provides 3 different change tracking policies, each having its particular advantages and disadvantages. The change tracking policy can be defined on a per-class basis (or more precisely, per-hierarchy). Deferred Implicit ~~~~~~~~~~~~~~~~~ The deferred implicit policy is the default change tracking policy and the most convenient one. With this policy, Doctrine detects the changes by a property-by-property comparison at commit time and also detects changes to entities or new entities that are referenced by other managed entities ("persistence by reachability"). Although the most convenient policy, it can have negative effects on performance if you are dealing with large units of work (see "Understanding the Unit of Work"). Since Doctrine can't know what has changed, it needs to check all managed entities for changes every time you invoke EntityManager#flush(), making this operation rather costly. Deferred Explicit ~~~~~~~~~~~~~~~~~ The deferred explicit policy is similar to the deferred implicit policy in that it detects changes through a property-by-property comparison at commit time. The difference is that Doctrine 2 only considers entities that have been explicitly marked for change detection through a call to EntityManager#persist(entity) or through a save cascade. All other entities are skipped. This policy therefore gives improved performance for larger units of work while sacrificing the behavior of "automatic dirty checking". Therefore, flush() operations are potentially cheaper with this policy. The negative aspect this has is that if you have a rather large application and you pass your objects through several layers for processing purposes and business tasks you may need to track yourself which entities have changed on the way so you can pass them to EntityManager#persist(). This policy can be configured as follows: .. code-block:: php _listeners[] = $listener; } } Then, in each property setter of this class or derived classes, you need to notify all the ``PropertyChangedListener`` instances. As an example we add a convenience method on ``MyEntity`` that shows this behaviour: .. code-block:: php _listeners) { foreach ($this->_listeners as $listener) { $listener->propertyChanged($this, $propName, $oldValue, $newValue); } } } public function setData($data) { if ($data != $this->data) { $this->_onPropertyChanged('data', $this->data, $data); $this->data = $data; } } } You have to invoke ``_onPropertyChanged`` inside every method that changes the persistent state of ``MyEntity``. The check whether the new value is different from the old one is not mandatory but recommended. That way you also have full control over when you consider a property changed. The negative point of this policy is obvious: You need implement an interface and write some plumbing code. But also note that we tried hard to keep this notification functionality abstract. Strictly speaking, it has nothing to do with the persistence layer and the Doctrine ORM or DBAL. You may find that property notification events come in handy in many other scenarios as well. As mentioned earlier, the ``Doctrine\Common`` namespace is not that evil and consists solely of very small classes and interfaces that have almost no external dependencies (none to the DBAL and none to the ORM) and that you can easily take with you should you want to swap out the persistence layer. This change tracking policy does not introduce a dependency on the Doctrine DBAL/ORM or the persistence layer. The positive point and main advantage of this policy is its effectiveness. It has the best performance characteristics of the 3 policies with larger units of work and a flush() operation is very cheap when nothing has changed. doctrine2-2.4.8/docs/en/reference/configuration.rst000066400000000000000000000102131257105210500223020ustar00rootroot00000000000000Installation and Configuration ============================== Doctrine can be installed with `Composer `_. For older versions we still have `PEAR packages `_. Define the following requirement in your ``composer.json`` file: :: { "require": { "doctrine/orm": "*" } } Then call ``composer install`` from your command line. If you don't know how Composer works, check out their `Getting Started `_ to set up. Class loading ------------- Autoloading is taken care of by Composer. You just have to include the composer autoload file in your project: .. code-block:: php 'pdo_mysql', 'user' => 'root', 'password' => '', 'dbname' => 'foo', ); $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode); $entityManager = EntityManager::create($dbParams, $config); Or if you prefer XML: .. code-block:: php ` section. .. note:: You can learn more about the database connection configuration in the `Doctrine DBAL connection configuration reference `_. Setting up the Commandline Tool ------------------------------- Doctrine ships with a number of command line tools that are very helpful during development. You can call this command from the Composer binary directory: .. code-block:: sh $ php vendor/bin/doctrine You need to register your applications EntityManager to the console tool to make use of the tasks by creating a ``cli-config.php`` file with the following content: On Doctrine 2.4 and above: .. code-block:: php new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); doctrine2-2.4.8/docs/en/reference/dql-doctrine-query-language.rst000066400000000000000000001614721257105210500247620ustar00rootroot00000000000000Doctrine Query Language =========================== DQL stands for Doctrine Query Language and is an Object Query Language derivate that is very similar to the Hibernate Query Language (HQL) or the Java Persistence Query Language (JPQL). In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects. .. note:: A common mistake for beginners is to mistake DQL for being just some form of SQL and therefore trying to use table names and column names or join arbitrary tables together in a query. You need to think about DQL as a query language for your object model, not for your relational schema. DQL is case in-sensitive, except for namespace, class and field names, which are case sensitive. Types of DQL queries -------------------- DQL as a query language has SELECT, UPDATE and DELETE constructs that map to their corresponding SQL statement types. INSERT statements are not allowed in DQL, because entities and their relations have to be introduced into the persistence context through ``EntityManager#persist()`` to ensure consistency of your object model. DQL SELECT statements are a very powerful way of retrieving parts of your domain model that are not accessible via associations. Additionally they allow to retrieve entities and their associations in one single SQL select statement which can make a huge difference in performance in contrast to using several queries. DQL UPDATE and DELETE statements offer a way to execute bulk changes on the entities of your domain model. This is often necessary when you cannot load all the affected entities of a bulk update into memory. SELECT queries -------------- DQL SELECT clause ~~~~~~~~~~~~~~~~~ The select clause of a DQL query specifies what appears in the query result. The composition of all the expressions in the select clause also influences the nature of the query result. Here is an example that selects all users with an age > 20: .. code-block:: php createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20'); $users = $query->getResult(); Lets examine the query: - ``u`` is a so called identification variable or alias that refers to the ``MyProject\Model\User`` class. By placing this alias in the SELECT clause we specify that we want all instances of the User class that are matched by this query to appear in the query result. - The FROM keyword is always followed by a fully-qualified class name which in turn is followed by an identification variable or alias for that class name. This class designates a root of our query from which we can navigate further via joins (explained later) and path expressions. - The expression ``u.age`` in the WHERE clause is a path expression. Path expressions in DQL are easily identified by the use of the '.' operator that is used for constructing paths. The path expression ``u.age`` refers to the ``age`` field on the User class. The result of this query would be a list of User objects where all users are older than 20. The SELECT clause allows to specify both class identification variables that signal the hydration of a complete entity class or just fields of the entity using the syntax ``u.name``. Combinations of both are also allowed and it is possible to wrap both fields and identification values into aggregation and DQL functions. Numerical fields can be part of computations using mathematical operations. See the sub-section on `Functions, Operators, Aggregates`_ for more information. Joins ~~~~~ A SELECT query can contain joins. There are 2 types of JOINs: "Regular" Joins and "Fetch" Joins. **Regular Joins**: Used to limit the results and/or compute aggregate values. **Fetch Joins**: In addition to the uses of regular joins: Used to fetch related entities and include them in the hydrated result of a query. There is no special DQL keyword that distinguishes a regular join from a fetch join. A join (be it an inner or outer join) becomes a "fetch join" as soon as fields of the joined entity appear in the SELECT part of the DQL query outside of an aggregate function. Otherwise its a "regular join". Example: Regular join of the address: .. code-block:: php createQuery("SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); $users = $query->getResult(); Fetch join of the address: .. code-block:: php createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); $users = $query->getResult(); When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the root level of the result array. In the previous example an array of User instances is returned and the address of each user is fetched and hydrated into the ``User#address`` variable. If you access the address Doctrine does not need to lazy load the association with another query. .. note:: Doctrine allows you to walk all the associations between all the objects in your domain model. Objects that were not already loaded from the database are replaced with lazy load proxy instances. Non-loaded Collections are also replaced by lazy-load instances that fetch all the contained objects upon first access. However relying on the lazy-load mechanism leads to many small queries executed against the database, which can significantly affect the performance of your application. **Fetch Joins** are the solution to hydrate most or all of the entities that you need in a single SELECT query. Named and Positional Parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DQL supports both named and positional parameters, however in contrast to many SQL dialects positional parameters are specified with numbers, for example "?1", "?2" and so on. Named parameters are specified with ":name1", ":name2" and so on. When referencing the parameters in ``Query#setParameter($param, $value)`` both named and positional parameters are used **without** their prefixes. DQL SELECT Examples ~~~~~~~~~~~~~~~~~~~ This section contains a large set of DQL queries and some explanations of what is happening. The actual result also depends on the hydration mode. Hydrate all User entities: .. code-block:: php createQuery('SELECT u FROM MyProject\Model\User u'); $users = $query->getResult(); // array of User objects Retrieve the IDs of all CmsUsers: .. code-block:: php createQuery('SELECT u.id FROM CmsUser u'); $ids = $query->getResult(); // array of CmsUser ids Retrieve the IDs of all users that have written an article: .. code-block:: php createQuery('SELECT DISTINCT u.id FROM CmsArticle a JOIN a.user u'); $ids = $query->getResult(); // array of CmsUser ids Retrieve all articles and sort them by the name of the articles users instance: .. code-block:: php createQuery('SELECT a FROM CmsArticle a JOIN a.user u ORDER BY u.name ASC'); $articles = $query->getResult(); // array of CmsArticle objects Retrieve the Username and Name of a CmsUser: .. code-block:: php createQuery('SELECT u.username, u.name FROM CmsUser u'); $users = $query->getResult(); // array of CmsUser username and name values echo $users[0]['username']; Retrieve a ForumUser and his single associated entity: .. code-block:: php createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a'); $users = $query->getResult(); // array of ForumUser objects with the avatar association loaded echo get_class($users[0]->getAvatar()); Retrieve a CmsUser and fetch join all the phonenumbers he has: .. code-block:: php createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p'); $users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded $phonenumbers = $users[0]->getPhonenumbers(); Hydrate a result in Ascending: .. code-block:: php createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC'); $users = $query->getResult(); // array of ForumUser objects Or in Descending Order: .. code-block:: php createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC'); $users = $query->getResult(); // array of ForumUser objects Using Aggregate Functions: .. code-block:: php createQuery('SELECT COUNT(u.id) FROM Entities\User u'); $count = $query->getSingleScalarResult(); $query = $em->createQuery('SELECT u, count(g.id) FROM Entities\User u JOIN u.groups g GROUP BY u.id'); $result = $query->getResult(); With WHERE Clause and Positional Parameter: .. code-block:: php createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1'); $query->setParameter(1, 321); $users = $query->getResult(); // array of ForumUser objects With WHERE Clause and Named Parameter: .. code-block:: php createQuery('SELECT u FROM ForumUser u WHERE u.username = :name'); $query->setParameter('name', 'Bob'); $users = $query->getResult(); // array of ForumUser objects With Nested Conditions in WHERE Clause: .. code-block:: php createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id'); $query->setParameters(array( 'name' => 'Bob', 'name2' => 'Alice', 'id' => 321, )); $users = $query->getResult(); // array of ForumUser objects With COUNT DISTINCT: .. code-block:: php createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser'); $users = $query->getResult(); // array of ForumUser objects With Arithmetic Expression in WHERE clause: .. code-block:: php createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); $users = $query->getResult(); // array of ForumUser objects Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids: .. code-block:: php createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a'); $results = $query->getResult(); // array of user ids and every article_id for each user Restricting a JOIN clause by additional conditions: .. code-block:: php createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE :foo"); $query->setParameter('foo', '%foo%'); $users = $query->getResult(); Using several Fetch JOINs: .. code-block:: php createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c'); $users = $query->getResult(); BETWEEN in WHERE clause: .. code-block:: php createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2'); $query->setParameter(1, 123); $query->setParameter(2, 321); $usernames = $query->getResult(); DQL Functions in WHERE clause: .. code-block:: php createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'"); $usernames = $query->getResult(); IN() Expression: .. code-block:: php createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)'); $usernames = $query->getResult(); $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id IN (1, 2)'); $users = $query->getResult(); $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id NOT IN (1)'); $users = $query->getResult(); CONCAT() DQL Function: .. code-block:: php createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); $query->setParameter(1, 'Jess'); $ids = $query->getResult(); $query = $em->createQuery('SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1'); $query->setParameter(1, 321); $idUsernames = $query->getResult(); EXISTS in WHERE clause with correlated Subquery .. code-block:: php createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.user = u.id)'); $ids = $query->getResult(); Get all users who are members of $group. .. code-block:: php createQuery('SELECT u.id FROM CmsUser u WHERE :groupId MEMBER OF u.groups'); $query->setParameter('groupId', $group); $ids = $query->getResult(); Get all users that have more than 1 phonenumber .. code-block:: php createQuery('SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1'); $users = $query->getResult(); Get all users that have no phonenumber .. code-block:: php createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); $users = $query->getResult(); Get all instances of a specific type, for use with inheritance hierarchies: .. versionadded:: 2.1 .. code-block:: php createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee'); $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1'); $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1'); Get all users visible on a given website that have chosen certain gender: .. versionadded:: 2.2 .. code-block:: php createQuery('SELECT u FROM User u WHERE u.gender IN (SELECT IDENTITY(agl.gender) FROM Site s JOIN s.activeGenderList agl WHERE s.id = ?1)'); Starting with 2.4, the IDENTITY() DQL function also works for composite primary keys: .. code-block:: php createQuery('SELECT IDENTITY(c.location, 'latitude') AS latitude, IDENTITY(c.location, 'longitude') AS longitude FROM Checkpoint c WHERE c.user = ?1'); Partial Object Syntax ^^^^^^^^^^^^^^^^^^^^^ By default when you run a DQL query in Doctrine and select only a subset of the fields for a given entity, you do not receive objects back. Instead, you receive only arrays as a flat rectangular result set, similar to how you would if you were just using SQL directly and joining some data. If you want to select partial objects you can use the ``partial`` DQL keyword: .. code-block:: php createQuery('SELECT partial u.{id, username} FROM CmsUser u'); $users = $query->getResult(); // array of partially loaded CmsUser objects You use the partial syntax when joining as well: .. code-block:: php createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a'); $users = $query->getResult(); // array of partially loaded CmsUser objects "NEW" Operator Syntax ^^^^^^^^^^^^^^^^^^^^^ .. versionadded:: 2.4 Using the ``NEW`` operator you can construct Data Transfer Objects (DTOs) directly from DQL queries. - When using ``SELECT NEW`` you don't need to specify a mapped entity. - You can specify any PHP class, it's only require that the constructor of this class matches the ``NEW`` statement. - This approach involves determining exactly which columns you really need, and instantiating data-transfer object that containing a constructor with those arguments. If you want to select data-transfer objects you should create a class: .. code-block:: php createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city) FROM Customer c JOIN c.email e JOIN c.address a'); $users = $query->getResult(); // array of CustomerDTO .. code-block:: php createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city, SUM(o.value)) FROM Customer c JOIN c.email e JOIN c.address a JOIN c.orders o GROUP BY c'); $users = $query->getResult(); // array of CustomerDTO Using INDEX BY ~~~~~~~~~~~~~~ The INDEX BY construct is nothing that directly translates into SQL but that affects object and array hydration. After each FROM and JOIN clause you specify by which field this class should be indexed in the result. By default a result is incremented by numerical keys starting with 0. However with INDEX BY you can specify any other column to be the key of your result, it really only makes sense with primary or unique fields though: .. code-block:: sql SELECT u.id, u.status, upper(u.name) nameUpper FROM User u INDEX BY u.id JOIN u.phonenumbers p INDEX BY p.phonenumber Returns an array of the following kind, indexed by both user-id then phonenumber-id: .. code-block:: php array 0 => array 1 => object(stdClass)[299] public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) public 'id' => int 1 .. 'nameUpper' => string 'ROMANB' (length=6) 1 => array 2 => object(stdClass)[298] public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) public 'id' => int 2 ... 'nameUpper' => string 'JWAGE' (length=5) UPDATE queries -------------- DQL not only allows to select your Entities using field names, you can also execute bulk updates on a set of entities using an DQL-UPDATE query. The Syntax of an UPDATE query works as expected, as the following example shows: .. code-block:: sql UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) References to related entities are only possible in the WHERE clause and using sub-selects. .. warning:: DQL UPDATE statements are ported directly into a Database UPDATE statement and therefore bypass any locking scheme, events and do not increment the version column. Entities that are already loaded into the persistence context will *NOT* be synced with the updated database state. It is recommended to call ``EntityManager#clear()`` and retrieve new instances of any affected entity. DELETE queries -------------- DELETE queries can also be specified using DQL and their syntax is as simple as the UPDATE syntax: .. code-block:: sql DELETE MyProject\Model\User u WHERE u.id = 4 The same restrictions apply for the reference of related entities. .. warning:: DQL DELETE statements are ported directly into a Database DELETE statement and therefore bypass any events and checks for the version column if they are not explicitly added to the WHERE clause of the query. Additionally Deletes of specifies entities are *NOT* cascaded to related entities even if specified in the metadata. Functions, Operators, Aggregates -------------------------------- DQL Functions ~~~~~~~~~~~~~ The following functions are supported in SELECT, WHERE and HAVING clauses: - IDENTITY(single\_association\_path\_expression [, fieldMapping]) - Retrieve the foreign key column of association of the owning side - ABS(arithmetic\_expression) - CONCAT(str1, str2) - CURRENT\_DATE() - Return the current date - CURRENT\_TIME() - Returns the current time - CURRENT\_TIMESTAMP() - Returns a timestamp of the current date and time. - LENGTH(str) - Returns the length of the given string - LOCATE(needle, haystack [, offset]) - Locate the first occurrence of the substring in the string. - LOWER(str) - returns the string lowercased. - MOD(a, b) - Return a MOD b. - SIZE(collection) - Return the number of elements in the specified collection - SQRT(q) - Return the square-root of q. - SUBSTRING(str, start [, length]) - Return substring of given string. - TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim the string by the given trim char, defaults to whitespaces. - UPPER(str) - Return the upper-case of the given string. - DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH) - DATE_SUB(date, days, unit) - Substract the number of days from a given date. (Supported units are DAY, MONTH) - DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2. Arithmetic operators ~~~~~~~~~~~~~~~~~~~~ You can do math in DQL using numeric values, for example: .. code-block:: sql SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 Aggregate Functions ~~~~~~~~~~~~~~~~~~~ The following aggregate functions are allowed in SELECT and GROUP BY clauses: AVG, COUNT, MIN, MAX, SUM Other Expressions ~~~~~~~~~~~~~~~~~ DQL offers a wide-range of additional expressions that are known from SQL, here is a list of all the supported constructs: - ``ALL/ANY/SOME`` - Used in a WHERE clause followed by a sub-select this works like the equivalent constructs in SQL. - ``BETWEEN a AND b`` and ``NOT BETWEEN a AND b`` can be used to match ranges of arithmetic values. - ``IN (x1, x2, ...)`` and ``NOT IN (x1, x2, ..)`` can be used to match a set of given values. - ``LIKE ..`` and ``NOT LIKE ..`` match parts of a string or text using % as a wildcard. - ``IS NULL`` and ``IS NOT NULL`` to check for null values - ``EXISTS`` and ``NOT EXISTS`` in combination with a sub-select Adding your own functions to the DQL language ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default DQL comes with functions that are part of a large basis of underlying databases. However you will most likely choose a database platform at the beginning of your project and most likely never change it. For this cases you can easily extend the DQL parser with own specialized platform functions. You can register custom DQL functions in your ORM Configuration: .. code-block:: php addCustomStringFunction($name, $class); $config->addCustomNumericFunction($name, $class); $config->addCustomDatetimeFunction($name, $class); $em = EntityManager::create($dbParams, $config); The functions have to return either a string, numeric or datetime value depending on the registered function type. As an example we will add a MySQL specific FLOOR() functionality. All the given classes have to implement the base class : .. code-block:: php walkSimpleArithmeticExpression( $this->simpleArithmeticExpression ) . ')'; } public function parse(\Doctrine\ORM\Query\Parser $parser) { $lexer = $parser->getLexer(); $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } We will register the function by calling and can then use it: .. code-block:: php getConfiguration(); $config->registerNumericFunction('FLOOR', 'MyProject\Query\MysqlFloor'); $dql = "SELECT FLOOR(person.salary * 1.75) FROM CompanyPerson person"; Querying Inherited Classes -------------------------- This section demonstrates how you can query inherited classes and what type of results to expect. Single Table ~~~~~~~~~~~~ `Single Table Inheritance `_ is an inheritance mapping strategy where all classes of a hierarchy are mapped to a single database table. In order to distinguish which row represents which type in the hierarchy a so-called discriminator column is used. First we need to setup an example set of entities to use. In this scenario it is a generic Person and Employee example: .. code-block:: php setName('test'); $employee->setDepartment('testing'); $em->persist($employee); $em->flush(); Now lets run a simple query to retrieve the ``Employee`` we just created: .. code-block:: sql SELECT e FROM Entities\Employee e WHERE e.name = 'test' If we check the generated SQL you will notice it has some special conditions added to ensure that we will only get back ``Employee`` entities: .. code-block:: sql SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, p0_.discr AS discr3 FROM Person p0_ WHERE (p0_.name = ?) AND p0_.discr IN ('employee') Class Table Inheritance ~~~~~~~~~~~~~~~~~~~~~~~ `Class Table Inheritance `_ is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hierarchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance. The example for class table inheritance is the same as single table, you just need to change the inheritance type from ``SINGLE_TABLE`` to ``JOINED``: .. code-block:: php createQuery('select u from MyProject\Model\User u'); // example2: using setDql $q = $em->createQuery(); $q->setDql('select u from MyProject\Model\User u'); Query Result Formats ~~~~~~~~~~~~~~~~~~~~ The format in which the result of a DQL SELECT query is returned can be influenced by a so-called ``hydration mode``. A hydration mode specifies a particular way in which a SQL result set is transformed. Each hydration mode has its own dedicated method on the Query class. Here they are: - ``Query#getResult()``: Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array where the objects are nested in the result rows (mixed). - ``Query#getSingleResult()``: Retrieves a single object. If the result contains more than one or no object, an exception is thrown. The pure/mixed distinction does not apply. - ``Query#getOneOrNullResult()``: Retrieve a single object. If no object is found null will be returned. - ``Query#getArrayResult()``: Retrieves an array graph (a nested array) that is largely interchangeable with the object graph generated by ``Query#getResult()`` for read-only purposes. .. note:: An array graph can differ from the corresponding object graph in certain scenarios due to the difference of the identity semantics between arrays and objects. - ``Query#getScalarResult()``: Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The pure/mixed distinction does not apply. - ``Query#getSingleScalarResult()``: Retrieves a single scalar value from the result returned by the dbms. If the result contains more than a single scalar value, an exception is thrown. The pure/mixed distinction does not apply. Instead of using these methods, you can alternatively use the general-purpose method ``Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)``. Using this method you can directly supply the hydration mode as the second parameter via one of the Query constants. In fact, the methods mentioned earlier are just convenient shortcuts for the execute method. For example, the method ``Query#getResult()`` internally invokes execute, passing in ``Query::HYDRATE_OBJECT`` as the hydration mode. The use of the methods mentioned earlier is generally preferred as it leads to more concise code. Pure and Mixed Results ~~~~~~~~~~~~~~~~~~~~~~ The nature of a result returned by a DQL SELECT query retrieved through ``Query#getResult()`` or ``Query#getArrayResult()`` can be of 2 forms: **pure** and **mixed**. In the previous simple examples, you already saw a "pure" query result, with only objects. By default, the result type is **pure** but **as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. A mixed result has a different structure than a pure result in order to accommodate for the scalar values. A pure result usually looks like this: .. code-block:: php $dql = "SELECT u FROM User u"; array [0] => Object [1] => Object [2] => Object ... A mixed result on the other hand has the following general structure: .. code-block:: php $dql = "SELECT u, 'some scalar string', count(u.groups) AS num FROM User u JOIN u.groups g GROUP BY u.id"; array [0] [0] => Object [1] => "some scalar string" ['num'] => 42 // ... more scalar values, either indexed numerically or with a name [1] [0] => Object [1] => "some scalar string" ['num'] => 42 // ... more scalar values, either indexed numerically or with a name To better understand mixed results, consider the following DQL query: .. code-block:: sql SELECT u, UPPER(u.name) nameUpper FROM MyProject\Model\User u This query makes use of the ``UPPER`` DQL function that returns a scalar value and because there is now a scalar value in the SELECT clause, we get a mixed result. Conventions for mixed results are as follows: - The object fetched in the FROM clause is always positioned with the key '0'. - Every scalar without a name is numbered in the order given in the query, starting with 1. - Every aliased scalar is given with its alias-name as the key. The case of the name is kept. - If several objects are fetched from the FROM clause they alternate every row. Here is how the result could look like: .. code-block:: php array array [0] => User (Object) ['nameUpper'] => "ROMAN" array [0] => User (Object) ['nameUpper'] => "JONATHAN" ... And here is how you would access it in PHP code: .. code-block:: php getName(); echo "Name UPPER: " . $row['nameUpper']; } Fetching Multiple FROM Entities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you fetch multiple entities that are listed in the FROM clause then the hydration will return the rows iterating the different top-level entities. .. code-block:: php $dql = "SELECT u, g FROM User u, Group g"; array [0] => Object (User) [1] => Object (Group) [2] => Object (User) [3] => Object (Group) Hydration Modes ~~~~~~~~~~~~~~~ Each of the Hydration Modes makes assumptions about how the result is returned to user land. You should know about all the details to make best use of the different result formats: The constants for the different hydration modes are: - Query::HYDRATE\_OBJECT - Query::HYDRATE\_ARRAY - Query::HYDRATE\_SCALAR - Query::HYDRATE\_SINGLE\_SCALAR Object Hydration ^^^^^^^^^^^^^^^^ Object hydration hydrates the result set into the object graph: .. code-block:: php createQuery('SELECT u FROM CmsUser u'); $users = $query->getResult(Query::HYDRATE_OBJECT); Array Hydration ^^^^^^^^^^^^^^^ You can run the same query with array hydration and the result set is hydrated into an array that represents the object graph: .. code-block:: php createQuery('SELECT u FROM CmsUser u'); $users = $query->getResult(Query::HYDRATE_ARRAY); You can use the ``getArrayResult()`` shortcut as well: .. code-block:: php getArrayResult(); Scalar Hydration ^^^^^^^^^^^^^^^^ If you want to return a flat rectangular result set instead of an object graph you can use scalar hydration: .. code-block:: php createQuery('SELECT u FROM CmsUser u'); $users = $query->getResult(Query::HYDRATE_SCALAR); echo $users[0]['u_id']; The following assumptions are made about selected fields using Scalar Hydration: 1. Fields from classes are prefixed by the DQL alias in the result. A query of the kind 'SELECT u.name ..' returns a key 'u\_name' in the result rows. Single Scalar Hydration ^^^^^^^^^^^^^^^^^^^^^^^ If you a query which returns just a single scalar value you can use single scalar hydration: .. code-block:: php createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); $query->setParameter(1, 'jwage'); $numArticles = $query->getResult(Query::HYDRATE_SINGLE_SCALAR); You can use the ``getSingleScalarResult()`` shortcut as well: .. code-block:: php getSingleScalarResult(); Custom Hydration Modes ^^^^^^^^^^^^^^^^^^^^^^ You can easily add your own custom hydration modes by first creating a class which extends ``AbstractHydrator``: .. code-block:: php _stmt->fetchAll(PDO::FETCH_ASSOC); } } Next you just need to add the class to the ORM configuration: .. code-block:: php getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); Now the hydrator is ready to be used in your queries: .. code-block:: php createQuery('SELECT u FROM CmsUser u'); $results = $query->getResult('CustomHydrator'); Iterating Large Result Sets ~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are situations when a query you want to execute returns a very large result-set that needs to be processed. All the previously described hydration modes completely load a result-set into memory which might not be feasible with large result sets. See the `Batch Processing `_ section on details how to iterate large result sets. Functions ~~~~~~~~~ The following methods exist on the ``AbstractQuery`` which both ``Query`` and ``NativeQuery`` extend from. Parameters ^^^^^^^^^^ Prepared Statements that use numerical or named wildcards require additional parameters to be executable against the database. To pass parameters to the query the following methods can be used: - ``AbstractQuery::setParameter($param, $value)`` - Set the numerical or named wildcard to the given value. - ``AbstractQuery::setParameters(array $params)`` - Set an array of parameter key-value pairs. - ``AbstractQuery::getParameter($param)`` - ``AbstractQuery::getParameters()`` Both named and positional parameters are passed to these methods without their ? or : prefix. Cache related API ^^^^^^^^^^^^^^^^^ You can cache query results based either on all variables that define the result (SQL, Hydration Mode, Parameters and Hints) or on user-defined cache keys. However by default query results are not cached at all. You have to enable the result cache on a per query basis. The following example shows a complete workflow using the Result Cache API: .. code-block:: php createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); $query->setParameter(1, 12); $query->setResultCacheDriver(new ApcCache()); $query->useResultCache(true) ->setResultCacheLifeTime($seconds = 3600); $result = $query->getResult(); // cache miss $query->expireResultCache(true); $result = $query->getResult(); // forced expire, cache miss $query->setResultCacheId('my_query_result'); $result = $query->getResult(); // saved in given result cache id. // or call useResultCache() with all parameters: $query->useResultCache(true, $seconds = 3600, 'my_query_result'); $result = $query->getResult(); // cache hit! // Introspection $queryCacheProfile = $query->getQueryCacheProfile(); $cacheDriver = $query->getResultCacheDriver(); $lifetime = $query->getLifetime(); $key = $query->getCacheKey(); .. note:: You can set the Result Cache Driver globally on the ``Doctrine\ORM\Configuration`` instance so that it is passed to every ``Query`` and ``NativeQuery`` instance. Query Hints ^^^^^^^^^^^ You can pass hints to the query parser and hydrators by using the ``AbstractQuery::setHint($name, $value)`` method. Currently there exist mostly internal query hints that are not be consumed in userland. However the following few hints are to be used in userland: - Query::HINT\_FORCE\_PARTIAL\_LOAD - Allows to hydrate objects although not all their columns are fetched. This query hint can be used to handle memory consumption problems with large result-sets that contain char or binary data. Doctrine has no way of implicitly reloading this data. Partially loaded objects have to be passed to ``EntityManager::refresh()`` if they are to be reloaded fully from the database. - Query::HINT\_REFRESH - This query is used internally by ``EntityManager::refresh()`` and can be used in userland as well. If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the fields of the existing entity will be refreshed. In normal operation a result-set that loads data of an already existing entity is discarded in favor of the already existing entity. - Query::HINT\_CUSTOM\_TREE\_WALKERS - An array of additional ``Doctrine\ORM\Query\TreeWalker`` instances that are attached to the DQL query parsing process. Query Cache (DQL Query Only) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Parsing a DQL query and converting it into a SQL query against the underlying database platform obviously has some overhead in contrast to directly executing Native SQL queries. That is why there is a dedicated Query Cache for caching the DQL parser results. In combination with the use of wildcards you can reduce the number of parsed queries in production to zero. The Query Cache Driver is passed from the ``Doctrine\ORM\Configuration`` instance to each ``Doctrine\ORM\Query`` instance by default and is also enabled by default. This also means you don't regularly need to fiddle with the parameters of the Query Cache, however if you do there are several methods to interact with it: - ``Query::setQueryCacheDriver($driver)`` - Allows to set a Cache instance - ``Query::setQueryCacheLifeTime($seconds = 3600)`` - Set lifetime of the query caching. - ``Query::expireQueryCache($bool)`` - Enforce the expiring of the query cache if set to true. - ``Query::getExpireQueryCache()`` - ``Query::getQueryCacheDriver()`` - ``Query::getQueryCacheLifeTime()`` First and Max Result Items (DQL Query Only) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can limit the number of results returned from a DQL query as well as specify the starting offset, Doctrine then uses a strategy of manipulating the select query to return only the requested number of results: - ``Query::setMaxResults($maxResults)`` - ``Query::setFirstResult($offset)`` .. note:: If your query contains a fetch-joined collection specifying the result limit methods are not working as you would expect. Set Max Results restricts the number of database result rows, however in the case of fetch-joined collections one root entity might appear in many rows, effectively hydrating less than the specified number of results. .. _dql-temporarily-change-fetch-mode: Temporarily change fetch mode in DQL ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ While normally all your associations are marked as lazy or extra lazy you will have cases where you are using DQL and don't want to fetch join a second, third or fourth level of entities into your result, because of the increased cost of the SQL JOIN. You can mark a many-to-one or one-to-one association as fetched temporarily to batch fetch these entities using a WHERE .. IN query. .. code-block:: php createQuery("SELECT u FROM MyProject\User u"); $query->setFetchMode("MyProject\User", "address", "EAGER"); $query->execute(); Given that there are 10 users and corresponding addresses in the database the executed queries will look something like: .. code-block:: sql SELECT * FROM users; SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); EBNF ---- The following context-free grammar, written in an EBNF variant, describes the Doctrine Query Language. You can consult this grammar whenever you are unsure about what is possible with DQL or what the correct syntax for a particular query should be. Document syntax: ~~~~~~~~~~~~~~~~ - non-terminals begin with an upper case character - terminals begin with a lower case character - parentheses (...) are used for grouping - square brackets [...] are used for defining an optional part, e.g. zero or one time - curly brackets {...} are used for repetition, e.g. zero or more times - double quotation marks "..." define a terminal string a vertical bar \| represents an alternative Terminals ~~~~~~~~~ - identifier (name, email, ...) - string ('foo', 'bar''s house', '%ninja%', ...) - char ('/', '\\', ' ', ...) - integer (-1, 0, 1, 34, ...) - float (-0.23, 0.007, 1.245342E+8, ...) - boolean (false, true) Query Language ~~~~~~~~~~~~~~ .. code-block:: php QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement Statements ~~~~~~~~~~ .. code-block:: php SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] UpdateStatement ::= UpdateClause [WhereClause] DeleteStatement ::= DeleteClause [WhereClause] Identifiers ~~~~~~~~~~~ .. code-block:: php /* Alias Identification usage (the "u" of "u.name") */ IdentificationVariable ::= identifier /* Alias Identification declaration (the "u" of "FROM User u") */ AliasIdentificationVariable :: = identifier /* identifier that must be a class name (the "User" of "FROM User u") */ AbstractSchemaName ::= identifier /* identifier that must be a field (the "name" of "u.name") */ /* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */ FieldIdentificationVariable ::= identifier /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ CollectionValuedAssociationField ::= FieldIdentificationVariable /* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */ SingleValuedAssociationField ::= FieldIdentificationVariable /* identifier that must be an embedded class state field (for the future) */ EmbeddedClassStateField ::= FieldIdentificationVariable /* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ SimpleStateField ::= FieldIdentificationVariable /* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */ AliasResultVariable = identifier /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ ResultVariable = identifier Path Expressions ~~~~~~~~~~~~~~~~ .. code-block:: php /* "u.Group" or "u.Phonenumbers" declarations */ JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) /* "u.Group" or "u.Phonenumbers" usages */ AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression /* "u.name" or "u.Group" */ SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression /* "u.name" or "u.Group.name" */ StateFieldPathExpression ::= IdentificationVariable "." StateField /* "u.Group" */ SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField /* "u.Group.Permissions" */ CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField /* "name" */ StateField ::= {EmbeddedClassStateField "."}* SimpleStateField Clauses ~~~~~~~ .. code-block:: php SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* WhereClause ::= "WHERE" ConditionalExpression HavingClause ::= "HAVING" ConditionalExpression GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] Items ~~~~~ .. code-block:: php UpdateItem ::= SingleValuedPathExpression "=" NewValue OrderByItem ::= (SimpleArithmeticExpression | SingleValuedPathExpression | ScalarExpression | ResultVariable) ["ASC" | "DESC"] GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression NewValue ::= SimpleArithmeticExpression | "NULL" From, Join and Index by ~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) JoinVariableDeclaration ::= Join [IndexBy] RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] IndexBy ::= "INDEX" "BY" StateFieldPathExpression Select Expressions ~~~~~~~~~~~~~~~~~~ .. code-block:: php SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression) [["AS"] ["HIDDEN"] AliasResultVariable] SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable] PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" Conditional Expressions ~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* ConditionalFactor ::= ["NOT"] ConditionalPrimary ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression | InExpression | NullComparisonExpression | ExistsExpression | EmptyCollectionComparisonExpression | CollectionMemberExpression | InstanceOfExpression Collection Expressions ~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression Literal Values ~~~~~~~~~~~~~~ .. code-block:: php Literal ::= string | char | integer | float | boolean InParameter ::= Literal | InputParameter Input Parameter ~~~~~~~~~~~~~~~ .. code-block:: php InputParameter ::= PositionalParameter | NamedParameter PositionalParameter ::= "?" integer NamedParameter ::= ":" string Arithmetic Expressions ~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings | FunctionsReturningDatetime | IdentificationVariable | ResultVariable | InputParameter | CaseExpression Scalar and Type Expressions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression | BooleanPrimary | CaseExpression | InstanceOfExpression StringExpression ::= StringPrimary | "(" Subselect ")" StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression BooleanExpression ::= BooleanPrimary | "(" Subselect ")" BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression SimpleEntityExpression ::= IdentificationVariable | InputParameter DatetimeExpression ::= DatetimePrimary | "(" Subselect ")" DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression .. note:: Parts of CASE expressions are not yet implemented. Aggregate Expressions ~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" Case Expressions ~~~~~~~~~~~~~~~~ .. code-block:: php CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" CaseOperand ::= StateFieldPathExpression | TypeDiscriminator SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" Other Expressions ~~~~~~~~~~~~~~~~~ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS .. code-block:: php QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") InstanceOfParameter ::= AbstractSchemaName | InputParameter LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char] NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" Functions ~~~~~~~~~ .. code-block:: php FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime FunctionsReturningNumerics ::= "LENGTH" "(" StringPrimary ")" | "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | "ABS" "(" SimpleArithmeticExpression ")" | "SQRT" "(" SimpleArithmeticExpression ")" | "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | "SIZE" "(" CollectionValuedPathExpression ")" | "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" | "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" | "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" FunctionsReturningStrings ::= "CONCAT" "(" StringPrimary "," StringPrimary ")" | "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | "LOWER" "(" StringPrimary ")" | "UPPER" "(" StringPrimary ")" | "IDENTITY" "(" SingleValuedAssociationPathExpression ")" doctrine2-2.4.8/docs/en/reference/events.rst000066400000000000000000000731361257105210500207540ustar00rootroot00000000000000Events ====== Doctrine 2 features a lightweight event system that is part of the Common package. Doctrine uses it to dispatch system events, mainly :ref:`lifecycle events `. You can also use it for your own custom events. The Event System ---------------- The event system is controlled by the ``EventManager``. It is the central point of Doctrine's event listener system. Listeners are registered on the manager and events are dispatched through the manager. .. code-block:: php addEventListener(array(self::preFoo, self::postFoo), $this); } public function preFoo(EventArgs $e) { $this->preFooInvoked = true; } public function postFoo(EventArgs $e) { $this->postFooInvoked = true; } } // Create a new instance $test = new EventTest($evm); Events can be dispatched by using the ``dispatchEvent()`` method. .. code-block:: php dispatchEvent(EventTest::preFoo); $evm->dispatchEvent(EventTest::postFoo); You can easily remove a listener with the ``removeEventListener()`` method. .. code-block:: php removeEventListener(array(self::preFoo, self::postFoo), $this); The Doctrine 2 event system also has a simple concept of event subscribers. We can define a simple ``TestEventSubscriber`` class which implements the ``\Doctrine\Common\EventSubscriber`` interface and implements a ``getSubscribedEvents()`` method which returns an array of events it should be subscribed to. .. code-block:: php preFooInvoked = true; } public function getSubscribedEvents() { return array(TestEvent::preFoo); } } $eventSubscriber = new TestEventSubscriber(); $evm->addEventSubscriber($eventSubscriber); .. note:: The array to return in the ``getSubscribedEvents`` method is a simple array with the values being the event names. The subscriber must have a method that is named exactly like the event. Now when you dispatch an event, any event subscribers will be notified for that event. .. code-block:: php dispatchEvent(TestEvent::preFoo); Now you can test the ``$eventSubscriber`` instance to see if the ``preFoo()`` method was invoked. .. code-block:: php preFooInvoked) { echo 'pre foo invoked!'; } Naming convention ~~~~~~~~~~~~~~~~~ Events being used with the Doctrine 2 EventManager are best named with camelcase and the value of the corresponding constant should be the name of the constant itself, even with spelling. This has several reasons: - It is easy to read. - Simplicity. - Each method within an EventSubscriber is named after the corresponding constant. If constant name and constant value differ, you MUST use the new value and thus, your code might be subject to codechanges when the value changes. This contradicts the intention of a constant. An example for a correct notation can be found in the example ``EventTest`` above. .. _reference-events-lifecycle-events: Lifecycle Events ---------------- The EntityManager and UnitOfWork trigger a bunch of events during the life-time of their registered entities. - preRemove - The preRemove event occurs for a given entity before the respective EntityManager remove operation for that entity is executed. It is not called for a DQL DELETE statement. - postRemove - The postRemove event occurs for an entity after the entity has been deleted. It will be invoked after the database delete operations. It is not called for a DQL DELETE statement. - prePersist - The prePersist event occurs for a given entity before the respective EntityManager persist operation for that entity is executed. It should be noted that this event is only triggered on *initial* persist of an entity - postPersist - The postPersist event occurs for an entity after the entity has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event. - preUpdate - The preUpdate event occurs before the database update operations to entity data. It is not called for a DQL UPDATE statement. - postUpdate - The postUpdate event occurs after the database update operations to entity data. It is not called for a DQL UPDATE statement. - postLoad - The postLoad event occurs for an entity after the entity has been loaded into the current EntityManager from the database or after the refresh operation has been applied to it. - loadClassMetadata - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (annotations/xml/yaml). - preFlush - The preFlush event occurs at the very beginning of a flush operation. This event is not a lifecycle callback. - onFlush - The onFlush event occurs after the change-sets of all managed entities are computed. This event is not a lifecycle callback. - postFlush - The postFlush event occurs at the end of a flush operation. This event is not a lifecycle callback. - onClear - The onClear event occurs when the EntityManager#clear() operation is invoked, after all references to entities have been removed from the unit of work. .. warning:: Note that the postLoad event occurs for an entity before any associations have been initialized. Therefore it is not safe to access associations in a postLoad callback or event handler. You can access the Event constants from the ``Events`` class in the ORM package. .. code-block:: php createdAt = date('Y-m-d H:i:s'); } /** @PrePersist */ public function doOtherStuffOnPrePersist() { $this->value = 'changed from prePersist callback!'; } /** @PostPersist */ public function doStuffOnPostPersist() { $this->value = 'changed from postPersist callback!'; } /** @PostLoad */ public function doStuffOnPostLoad() { $this->value = 'changed from postLoad callback!'; } /** @PreUpdate */ public function doStuffOnPreUpdate() { $this->value = 'changed from preUpdate callback!'; } } Note that the methods set as lifecycle callbacks need to be public and, when using these annotations, you have to apply the ``@HasLifecycleCallbacks`` marker annotation on the entity class. If you want to register lifecycle callbacks from YAML or XML you can do it with the following. .. code-block:: yaml User: type: entity fields: # ... name: type: string(50) lifecycleCallbacks: prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] postPersist: [ doStuffOnPostPersist ] In YAML the ``key`` of the lifecycleCallbacks entry is the event that you are triggering on and the value is the method (or methods) to call. The allowed event types are the ones listed in the previous Lifecycle Events section. XML would look something like this: .. code-block:: xml In XML the ``type`` of the lifecycle-callback entry is the event that you are triggering on and the ``method`` is the method to call. The allowed event types are the ones listed in the previous Lifecycle Events section. When using YAML or XML you need to remember to create public methods to match the callback names you defined. E.g. in these examples ``doStuffOnPrePersist()``, ``doOtherStuffOnPrePersist()`` and ``doStuffOnPostPersist()`` methods need to be defined on your ``User`` model. .. code-block:: php hasChangedField('username')) { // Do something when the username is changed. } } } Listening and subscribing to Lifecycle Events --------------------------------------------- Lifecycle event listeners are much more powerful than the simple lifecycle callbacks that are defined on the entity classes. They sit at a level above the entities and allow you to implement re-usable behaviors across different entity classes. Note that they require much more detailed knowledge about the inner workings of the EntityManager and UnitOfWork. Please read the *Implementing Event Listeners* section carefully if you are trying to write your own listener. For event subscribers, there are no surprises. They declare the lifecycle events in their ``getSubscribedEvents`` method and provide public methods that expect the relevant arguments. A lifecycle event listener looks like the following: .. code-block:: php getObject(); $entityManager = $args->getObjectManager(); // perhaps you only want to act on some "Product" entity if ($entity instanceof Product) { // do something with the Product } } } A lifecycle event subscriber may looks like this: .. code-block:: php getObject(); $entityManager = $args->getObjectManager(); // perhaps you only want to act on some "Product" entity if ($entity instanceof Product) { // do something with the Product } } .. note:: Lifecycle events are triggered for all entities. It is the responsibility of the listeners and subscribers to check if the entity is of a type it wants to handle. To register an event listener or subscriber, you have to hook it into the EventManager that is passed to the EntityManager factory: .. code-block:: php addEventListener(array(Events::preUpdate), new MyEventListener()); $eventManager->addEventSubscriber(new MyEventSubscriber()); $entityManager = EntityManager::create($dbOpts, $config, $eventManager); You can also retrieve the event manager instance after the EntityManager was created: .. code-block:: php getEventManager()->addEventListener(array(Events::preUpdate), new MyEventListener()); $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); .. _reference-events-implementing-listeners: Implementing Event Listeners ---------------------------- This section explains what is and what is not allowed during specific lifecycle events of the UnitOfWork. Although you get passed the EntityManager in all of these events, you have to follow these restrictions very carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and lost updates/persists/removes. For the described events that are also lifecycle callback events the restrictions apply as well, with the additional restriction that (prior to version 2.4) you do not have access to the EntityManager or UnitOfWork APIs inside these events. prePersist ~~~~~~~~~~ There are two ways for the ``prePersist`` event to be triggered. One is obviously when you call ``EntityManager#persist()``. The event is also called for all cascaded associations. There is another way for ``prePersist`` to be called, inside the ``flush()`` method when changes to associations are computed and this association is marked as cascade persist. Any new entity found during this operation is also persisted and ``prePersist`` called on it. This is called "persistence by reachability". In both cases you get passed a ``LifecycleEventArgs`` instance which has access to the entity and the entity manager. The following restrictions apply to ``prePersist``: - If you are using a PrePersist Identity Generator such as sequences the ID value will *NOT* be available within any PrePersist events. - Doctrine will not recognize changes made to relations in a prePersist event. This includes modifications to collections such as additions, removals or replacement. preRemove ~~~~~~~~~ The ``preRemove`` event is called on every entity when its passed to the ``EntityManager#remove()`` method. It is cascaded for all associations that are marked as cascade delete. There are no restrictions to what methods can be called inside the ``preRemove`` event, except when the remove method itself was called during a flush operation. preFlush ~~~~~~~~ ``preFlush`` is called at ``EntityManager#flush()`` before anything else. ``EntityManager#flush()`` can be called safely inside its listeners. .. code-block:: php getEntityManager(); $uow = $em->getUnitOfWork(); foreach ($uow->getScheduledEntityInsertions() AS $entity) { } foreach ($uow->getScheduledEntityUpdates() AS $entity) { } foreach ($uow->getScheduledEntityDeletions() AS $entity) { } foreach ($uow->getScheduledCollectionDeletions() AS $col) { } foreach ($uow->getScheduledCollectionUpdates() AS $col) { } } } The following restrictions apply to the onFlush event: - If you create and persist a new entity in "onFlush", then calling ``EntityManager#persist()`` is not enough. You have to execute an additional call to ``$unitOfWork->computeChangeSet($classMetadata, $entity)``. - Changing primitive fields or associations requires you to explicitly trigger a re-computation of the changeset of the affected entity. This can be done by either calling ``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``. postFlush ~~~~~~~~~ ``postFlush`` is called at the end of ``EntityManager#flush()``. ``EntityManager#flush()`` can **NOT** be called safely inside its listeners. .. code-block:: php getEntity() instanceof User) { if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') { $eventArgs->setNewValue('name', 'Bob'); } } } } You could also use this listener to implement validation of all the fields that have changed. This is more efficient than using a lifecycle callback when there are expensive validations to call: .. code-block:: php getEntity() instanceof Account) { if ($eventArgs->hasChangedField('creditCard')) { $this->validateCreditCard($eventArgs->getNewValue('creditCard')); } } } private function validateCreditCard($no) { // throw an exception to interrupt flush event. Transaction will be rolled back. } } Restrictions for this event: - Changes to associations of the passed entities are not recognized by the flush operation anymore. - Changes to fields of the passed entities are not recognized by the flush operation anymore, use the computed change-set passed to the event to modify primitive field values, e.g. use ``$eventArgs->setNewValue($field, $value);`` as in the Alice to Bob example above. - Any calls to ``EntityManager#persist()`` or ``EntityManager#remove()``, even in combination with the UnitOfWork API are strongly discouraged and don't work as expected outside the flush operation. postUpdate, postRemove, postPersist ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The three post events are called inside ``EntityManager#flush()``. Changes in here are not relevant to the persistence in the database, but you can use these events to alter non-persistable items, like non-mapped fields, logging or even associated classes that are directly mapped by Doctrine. postLoad ~~~~~~~~ This event is called after an entity is constructed by the EntityManager. Entity listeners ---------------- .. versionadded:: 2.4 An entity listeners is a lifecycle listener classes used for an entity. - The entity listeners mapping may be applied to an entity class or mapped superclass. - An entity listener is defined by mapping the entity class with the corresponding mapping. .. configuration-block:: .. code-block:: php .. code-block:: yaml MyProject\Entity\User: type: entity entityListeners: UserListener: # .... .. _reference-entity-listeners: Entity listeners class ~~~~~~~~~~~~~~~~~~~~~~ An ``Entity Listener`` could be any class, by default it should be a class with a no-arg constructor. - Different from :ref:`reference-events-implementing-listeners` an ``Entity Listener`` is invoked just to the specified entity - An entity listener method receives two arguments, the entity instance and the lifecycle event. - The callback method can be defined by naming convention or specifying a method mapping. - When a listener mapping is not given the parser will use the naming convention to look for a matching method, e.g. it will look for a public ``preUpdate()`` method if you are listening to the ``preUpdate`` event. - When a listener mapping is given the parser will not look for any methods using the naming convention. .. code-block:: php .. code-block:: yaml MyProject\Entity\User: type: entity entityListeners: UserListener: preFlush: [preFlushHandler] postLoad: [postLoadHandler] postPersist: [postPersistHandler] prePersist: [prePersistHandler] postUpdate: [postUpdateHandler] preUpdate: [preUpdateHandler] postRemove: [postRemoveHandler] preRemove: [preRemoveHandler] # .... Entity listeners resolver ~~~~~~~~~~~~~~~~~~~~~~~~~~ Doctrine invoke the listener resolver to get the listener instance. - An resolver allows you register a specific ``Entity Listener`` instance. - You can also implement your own resolver by extending ``Doctrine\ORM\Mapping\DefaultEntityListenerResolver`` or implementing ``Doctrine\ORM\Mapping\EntityListenerResolver`` Specifying an entity listener instance : .. code-block:: php service = $service; } public function preUpdate(User $user, PreUpdateEventArgs $event) { $this->service->doSomething($user); } } // register a entity listener. $listener = $container->get('user_listener'); $em->getConfiguration()->getEntityListenerResolver()->register($listener); Implementing your own resolver : .. code-block:: php container = $container; } public function resolve($className) { // resolve the service id by the given class name; $id = 'user_listener'; return $this->container->get($id); } } // configure the listener resolver. $em->getConfiguration()->setEntityListenerResolver($container->get('my_resolver')); Load ClassMetadata Event ------------------------ When the mapping information for an entity is read, it is populated in to a ``ClassMetadataInfo`` instance. You can hook in to this process and manipulate the instance. .. code-block:: php getMetadataFactory(); $evm = $em->getEventManager(); $evm->addEventListener(Events::loadClassMetadata, $test); class EventTest { public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) { $classMetadata = $eventArgs->getClassMetadata(); $fieldMapping = array( 'fieldName' => 'about', 'type' => 'string', 'length' => 255 ); $classMetadata->mapField($fieldMapping); } } doctrine2-2.4.8/docs/en/reference/faq.rst000066400000000000000000000226731257105210500202170ustar00rootroot00000000000000Frequently Asked Questions ========================== .. note:: This FAQ is a work in progress. We will add lots of questions and not answer them right away just to remember what is often asked. If you stumble across an unanswered question please write a mail to the mailing-list or join the #doctrine channel on Freenode IRC. Database Schema --------------- How do I set the charset and collation for MySQL tables? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can't set these values inside the annotations, yml or xml mapping files. To make a database work with the default charset and collation you should configure MySQL to use it as default charset, or create the database with charset and collation details. This way they get inherited to all newly created database tables and columns. Entity Classes -------------- I access a variable and its null, what is wrong? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If this variable is a public variable then you are violating one of the criteria for entities. All properties have to be protected or private for the proxy object pattern to work. How can I add default values to a column? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Doctrine does not support to set the default values in columns through the "DEFAULT" keyword in SQL. This is not necessary however, you can just use your class properties as default values. These are then used upon insert: .. code-block:: php class User { const STATUS_DISABLED = 0; const STATUS_ENABLED = 1; private $algorithm = "sha1"; private $status = self:STATUS_DISABLED; } . Mapping ------- Why do I get exceptions about unique constraint failures during ``$em->flush()``? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Doctrine does not check if you are re-adding entities with a primary key that already exists or adding entities to a collection twice. You have to check for both conditions yourself in the code before calling ``$em->flush()`` if you know that unique constraint failures can occur. In `Symfony2 `_ for example there is a Unique Entity Validator to achieve this task. For collections you can check with ``$collection->contains($entity)`` if an entity is already part of this collection. For a FETCH=LAZY collection this will initialize the collection, however for FETCH=EXTRA_LAZY this method will use SQL to determine if this entity is already part of the collection. Associations ------------ What is wrong when I get an InvalidArgumentException "A new entity was found through the relationship.."? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This exception is thrown during ``EntityManager#flush()`` when there exists an object in the identity map that contains a reference to an object that Doctrine does not know about. Say for example you grab a "User"-entity from the database with a specific id and set a completely new object into one of the associations of the User object. If you then call ``EntityManager#flush()`` without letting Doctrine know about this new object using ``EntityManager#persist($newObject)`` you will see this exception. You can solve this exception by: * Calling ``EntityManager#persist($newObject)`` on the new object * Using cascade=persist on the association that contains the new object How can I filter an association? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Natively you can't filter associations in 2.0 and 2.1. You should use DQL queries to query for the filtered set of entities. I call clear() on a One-To-Many collection but the entities are not deleted ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is an expected behavior that has to do with the inverse/owning side handling of Doctrine. By definition a One-To-Many association is on the inverse side, that means changes to it will not be recognized by Doctrine. If you want to perform the equivalent of the clear operation you have to iterate the collection and set the owning side many-to-one reference to NULL as well to detach all entities from the collection. This will trigger the appropriate UPDATE statements on the database. How can I add columns to a many-to-many table? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The many-to-many association is only supporting foreign keys in the table definition To work with many-to-many tables containing extra columns you have to use the foreign keys as primary keys feature of Doctrine introduced in version 2.1. See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`. How can i paginate fetch-joined collections? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you are issuing a DQL statement that fetches a collection as well you cannot easily iterate over this collection using a LIMIT statement (or vendor equivalent). Doctrine does not offer a solution for this out of the box but there are several extensions that do: * `DoctrineExtensions `_ * `Pagerfanta `_ Why does pagination not work correctly with fetch joins? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Pagination in Doctrine uses a LIMIT clause (or vendor equivalent) to restrict the results. However when fetch-joining this is not returning the correct number of results since joining with a one-to-many or many-to-many association multiplies the number of rows by the number of associated entities. See the previous question for a solution to this task. Inheritance ----------- Can I use Inheritance with Doctrine 2? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2. See the documentation chapter on :doc:`inheritance mapping ` for the details. Why does Doctrine not create proxy objects for my inheritance hierarchy? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you set a many-to-one or one-to-one association target-entity to any parent class of an inheritance hierarchy Doctrine does not know what PHP class the foreign is actually of. To find this out it has to execute a SQL query to look this information up in the database. EntityGenerator --------------- Why does the EntityGenerator not do X? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The EntityGenerator is not a full fledged code-generator that solves all tasks. Code-Generation is not a first-class priority in Doctrine 2 anymore (compared to Doctrine 1). The EntityGenerator is supposed to kick-start you, but not towards 100%. Why does the EntityGenerator not generate inheritance correctly? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Just from the details of the discriminator map the EntityGenerator cannot guess the inheritance hierarchy. This is why the generation of inherited entities does not fully work. You have to adjust some additional code to get this one working correctly. Performance ----------- Why is an extra SQL query executed every time I fetch an entity with a one-to-one relation? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If Doctrine detects that you are fetching an inverse side one-to-one association it has to execute an additional query to load this object, because it cannot know if there is no such object (setting null) or if it should set a proxy and which id this proxy has. To solve this problem currently a query has to be executed to find out this information. Doctrine Query Language ----------------------- What is DQL? ~~~~~~~~~~~~ DQL stands for Doctrine Query Language, a query language that very much looks like SQL but has some important benefits when using Doctrine: - It uses class names and fields instead of tables and columns, separating concerns between backend and your object model. - It utilizes the metadata defined to offer a range of shortcuts when writing. For example you do not have to specify the ON clause of joins, since Doctrine already knows about them. - It adds some functionality that is related to object management and transforms them into SQL. It also has some drawbacks of course: - The syntax is slightly different to SQL so you have to learn and remember the differences. - To be vendor independent it can only implement a subset of all the existing SQL dialects. Vendor specific functionality and optimizations cannot be used through DQL unless implemented by you explicitly. - For some DQL constructs subselects are used which are known to be slow in MySQL. Can I sort by a function (for example ORDER BY RAND()) in DQL? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ No, it is not supported to sort by function in DQL. If you need this functionality you should either use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow starting with 1000 rows. A Query fails, how can I debug it? ---------------------------------- First, if you are using the QueryBuilder you can use ``$queryBuilder->getDQL()`` to get the DQL string of this query. The corresponding SQL you can get from the Query instance by calling ``$query->getSQL()``. .. code-block:: php createQuery($dql); var_dump($query->getSQL()); $qb = $entityManager->createQueryBuilder(); $qb->select('u')->from('User', 'u'); var_dump($qb->getDQL()); doctrine2-2.4.8/docs/en/reference/filters.rst000066400000000000000000000066021257105210500211120ustar00rootroot00000000000000Filters ======= .. versionadded:: 2.2 Doctrine 2.2 features a filter system that allows the developer to add SQL to the conditional clauses of queries, regardless the place where the SQL is generated (e.g. from a DQL query, or by loading associated entities). The filter functionality works on SQL level. Whether a SQL query is generated in a Persister, during lazy loading, in extra lazy collections or from DQL. Each time the system iterates over all the enabled filters, adding a new SQL part as a filter returns. By adding SQL to the conditional clauses of queries, the filter system filters out rows belonging to the entities at the level of the SQL result set. This means that the filtered entities are never hydrated (which can be expensive). Example filter class -------------------- Throughout this document the example ``MyLocaleFilter`` class will be used to illustrate how the filter feature works. A filter class must extend the base ``Doctrine\ORM\Query\Filter\SQLFilter`` class and implement the ``addFilterConstraint`` method. The method receives the ``ClassMetadata`` of the filtered entity and the table alias of the SQL table of the entity. .. note:: In the case of joined or single table inheritance, you always get passed the ClassMetadata of the inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()``. Only parameters set via this function can be used in filters. The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters. .. code-block:: php reflClass->implementsInterface('LocaleAware')) { return ""; } return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); // getParameter applies quoting automatically } } Configuration ------------- Filter classes are added to the configuration as following: .. code-block:: php addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter"); The ``Configuration#addFilter()`` method takes a name for the filter and the name of the class responsible for the actual filtering. Disabling/Enabling Filters and Setting Parameters --------------------------------------------------- Filters can be disabled and enabled via the ``FilterCollection`` which is stored in the ``EntityManager``. The ``FilterCollection#enable($name)`` method will retrieve the filter object. You can set the filter parameters on that object. .. code-block:: php getFilters()->enable("locale"); $filter->setParameter('locale', 'en'); // Disable it $filter = $em->getFilters()->disable("locale"); .. warning:: Disabling and enabling filters has no effect on managed entities. If you want to refresh or reload an object after having modified a filter or the FilterCollection, then you should clear the EntityManager and re-fetch your entities, having the new rules for filtering applied. doctrine2-2.4.8/docs/en/reference/improving-performance.rst000066400000000000000000000044361257105210500237560ustar00rootroot00000000000000Improving Performance ===================== Bytecode Cache -------------- It is highly recommended to make use of a bytecode cache like APC. A bytecode cache removes the need for parsing PHP code on every request and can greatly improve performance. "If you care about performance and don't use a bytecode cache then you don't really care about performance. Please get one and start using it." *Stas Malyshev, Core Contributor to PHP and Zend Employee* Metadata and Query caches ------------------------- As already mentioned earlier in the chapter about configuring Doctrine, it is strongly discouraged to use Doctrine without a Metadata and Query cache (preferably with APC or Memcache as the cache driver). Operating Doctrine without these caches means Doctrine will need to load your mapping information on every single request and has to parse each DQL query on every single request. This is a waste of resources. Alternative Query Result Formats -------------------------------- Make effective use of the available alternative query result formats like nested array graphs or pure scalar results, especially in scenarios where data is loaded for read-only purposes. Read-Only Entities ------------------ Starting with Doctrine 2.1 you can mark entities as read only (See metadata mapping references for details). This means that the entity marked as read only is never considered for updates, which means when you call flush on the EntityManager these entities are skipped even if properties changed. Read-Only allows to persist new entities of a kind and remove existing ones, they are just not considered for updates. Extra-Lazy Collections ---------------------- If entities hold references to large collections you will get performance and memory problems initializing them. To solve this issue you can use the EXTRA_LAZY fetch-mode feature for collections. See the :doc:`tutorial <../tutorials/extra-lazy-associations>` for more information on how this fetch mode works. Temporarily change fetch mode in DQL ------------------------------------ See :ref:`Doctrine Query Language chapter ` Apply Best Practices -------------------- A lot of the points mentioned in the Best Practices chapter will also positively affect the performance of Doctrine. doctrine2-2.4.8/docs/en/reference/inheritance-mapping.rst000066400000000000000000000452211257105210500233640ustar00rootroot00000000000000Inheritance Mapping =================== Mapped Superclasses ------------------- A mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information for its subclasses, but which is not itself an entity. Typically, the purpose of such a mapped superclass is to define state and mapping information that is common to multiple entity classes. Mapped superclasses, just as regular, non-mapped classes, can appear in the middle of an otherwise mapped inheritance hierarchy (through Single Table Inheritance or Class Table Inheritance). .. note:: A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all. Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment. For further support of inheritance, the single or joined table inheritance features have to be used. Example: .. code-block:: php `_ is an inheritance mapping strategy where all classes of a hierarchy are mapped to a single database table. In order to distinguish which row represents which type in the hierarchy a so-called discriminator column is used. Example: .. code-block:: php `_ is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hierarchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance. Example: .. code-block:: php .. code-block:: yaml # user mapping MyProject\Model\User: type: mappedSuperclass # other fields mapping manyToOne: address: targetEntity: Address joinColumn: name: address_id referencedColumnName: id cascade: [ persist, merge ] manyToMany: groups: targetEntity: Group joinTable: name: users_groups joinColumns: user_id: referencedColumnName: id inverseJoinColumns: group_id: referencedColumnName: id cascade: [ persist, merge, detach ] # admin mapping MyProject\Model\Admin: type: entity associationOverride: address: joinColumn: adminaddress_id: name: adminaddress_id referencedColumnName: id groups: joinTable: name: users_admingroups joinColumns: adminuser_id: referencedColumnName: id inverseJoinColumns: admingroup_id: referencedColumnName: id Things to note: - The "association override" specifies the overrides base on the property name. - This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany) - The association type *CANNOT* be changed. - The override could redefine the joinTables or joinColumns depending on the association type. Attribute Override ~~~~~~~~~~~~~~~~~~~~ Override the mapping of a field. Could be used by an entity that extends a mapped superclass to override a field mapping defined by the mapped superclass. .. configuration-block:: .. code-block:: php .. code-block:: yaml # user mapping MyProject\Model\User: type: mappedSuperclass id: id: type: integer column: user_id length: 150 generator: strategy: AUTO fields: name: type: string column: user_name length: 250 nullable: true unique: false #other fields mapping # guest mapping MyProject\Model\Guest: type: entity attributeOverride: id: column: guest_id type: integer length: 140 name: column: guest_name type: string length: 240 nullable: false unique: true Things to note: - The "attribute override" specifies the overrides base on the property name. - The column type *CANNOT* be changed. if the column type is not equals you got a ``MappingException`` - The override can redefine all the column except the type. doctrine2-2.4.8/docs/en/reference/installation.rst000066400000000000000000000001761257105210500221430ustar00rootroot00000000000000Installation ============ The installation chapter has moved to `Installation and Configuration `_. doctrine2-2.4.8/docs/en/reference/limitations-and-known-issues.rst000066400000000000000000000165031257105210500252020ustar00rootroot00000000000000Limitations and Known Issues ============================ We try to make using Doctrine2 a very pleasant experience. Therefore we think it is very important to be honest about the current limitations to our users. Much like every other piece of software Doctrine2 is not perfect and far from feature complete. This section should give you an overview of current limitations of Doctrine 2 as well as critical known issues that you should know about. Current Limitations ------------------- There is a set of limitations that exist currently which might be solved in the future. Any of this limitations now stated has at least one ticket in the Tracker and is discussed for future releases. Join-Columns with non-primary keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is not possible to use join columns pointing to non-primary keys. Doctrine will think these are the primary keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance reasons not validate the correctness of this settings at runtime but only through the Validate Schema command. Mapping Arrays to a Join Table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Related to the previous limitation with "Foreign Keys as Identifier" you might be interested in mapping the same table structure as given above to an array. However this is not yet possible either. See the following example: .. code-block:: sql CREATE TABLE product ( id INTEGER, name VARCHAR, PRIMARY KEY(id) ); CREATE TABLE product_attributes ( product_id INTEGER, attribute_name VARCHAR, attribute_value VARCHAR, PRIMARY KEY (product_id, attribute_name) ); This schema should be mapped to a Product Entity as follows: .. code-block:: php class Product { private $id; private $name; private $attributes = array(); } Where the ``attribute_name`` column contains the key and ``attribute_value`` contains the value of each array element in ``$attributes``. The feature request for persistence of primitive value arrays `is described in the DDC-298 ticket `_. Value Objects ~~~~~~~~~~~~~ There is currently no native support value objects in Doctrine other than for ``DateTime`` instances or if you serialize the objects using ``serialize()/deserialize()`` which the DBAL Type "object" supports. The feature request for full value-object support `is described in the DDC-93 ticket `_. Cascade Merge with Bi-directional Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are two bugs now that concern the use of cascade merge in combination with bi-directional associations. Make sure to study the behavior of cascade merge if you are using it: - `DDC-875 `_ Merge can sometimes add the same entity twice into a collection - `DDC-763 `_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability" Custom Persisters ~~~~~~~~~~~~~~~~~ A Persister in Doctrine is an object that is responsible for the hydration and write operations of an entity against the database. Currently there is no way to overwrite the persister implementation for a given entity, however there are several use-cases that can benefit from custom persister implementations: - `Add Upsert Support `_ - `Evaluate possible ways in which stored-procedures can be used `_ - The previous Filter Rules Feature Request Persist Keys of Collections ~~~~~~~~~~~~~~~~~~~~~~~~~~~ PHP Arrays are ordered hash-maps and so should be the ``Doctrine\Common\Collections\Collection`` interface. We plan to evaluate a feature that optionally persists and hydrates the keys of a Collection instance. `Ticket DDC-213 `_ Mapping many tables to one entity ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is not possible to map several equally looking tables onto one entity. For example if you have a production and an archive table of a certain business concept then you cannot have both tables map to the same entity. Behaviors ~~~~~~~~~ Doctrine 2 will **never** include a behavior system like Doctrine 1 in the core library. We don't think behaviors add more value than they cost pain and debugging hell. Please see the many different blog posts we have written on this topics: - `Doctrine2 "Behaviors" in a Nutshell `_ - `A re-usable Versionable behavior for Doctrine2 `_ - `Write your own ORM on top of Doctrine2 `_ - `Doctrine 2 Behavioral Extensions `_ - `Doctrator _ Doctrine 2 has enough hooks and extension points so that **you** can add whatever you want on top of it. None of this will ever become core functionality of Doctrine2 however, you will have to rely on third party extensions for magical behaviors. Nested Set ~~~~~~~~~~ NestedSet was offered as a behavior in Doctrine 1 and will not be included in the core of Doctrine 2. However there are already two extensions out there that offer support for Nested Set with Doctrine 2: - `Doctrine2 Hierarchical-Structural Behavior `_ - `Doctrine2 NestedSet `_ Known Issues ------------ The Known Issues section describes critical/blocker bugs and other issues that are either complicated to fix, not fixable due to backwards compatibility issues or where no simple fix exists (yet). We don't plan to add every bug in the tracker there, just those issues that can potentially cause nightmares or pain of any sort. See the Open Bugs on Jira for more details on `bugs, improvement and feature requests `_. Identifier Quoting and Legacy Databases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For compatibility reasons between all the supported vendors and edge case problems Doctrine 2 does **NOT** do automatic identifier quoting. This can lead to problems when trying to get legacy-databases to work with Doctrine 2. - You can quote column-names as described in the :doc:`Basic-Mapping ` section. - You cannot quote join column names. - You cannot use non [a-zA-Z0-9\_]+ characters, they will break several SQL statements. Having problems with these kind of column names? Many databases support all CRUD operations on views that semantically map to certain tables. You can create views for all your problematic tables and column names to avoid the legacy quoting nightmare. Microsoft SQL Server and Doctrine "datetime" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Doctrine assumes that you use ``DateTime2`` data-types. If your legacy database contains DateTime datatypes then you have to add your own data-type (see Basic Mapping for an example). doctrine2-2.4.8/docs/en/reference/metadata-drivers.rst000066400000000000000000000140231257105210500226720ustar00rootroot00000000000000Metadata Drivers ================ The heart of an object relational mapper is the mapping information that glues everything together. It instructs the EntityManager how it should behave when dealing with the different entities. Core Metadata Drivers --------------------- Doctrine provides a few different ways for you to specify your metadata: - **XML files** (XmlDriver) - **Class DocBlock Annotations** (AnnotationDriver) - **YAML files** (YamlDriver) - **PHP Code in files or static functions** (PhpDriver) Something important to note about the above drivers is they are all an intermediate step to the same end result. The mapping information is populated to ``Doctrine\ORM\Mapping\ClassMetadata`` instances. So in the end, Doctrine only ever has to work with the API of the ``ClassMetadata`` class to get mapping information for an entity. .. note:: The populated ``ClassMetadata`` instances are also cached so in a production environment the parsing and populating only ever happens once. You can configure the metadata cache implementation using the ``setMetadataCacheImpl()`` method on the ``Doctrine\ORM\Configuration`` class: .. code-block:: php getConfiguration()->setMetadataCacheImpl(new ApcCache()); If you want to use one of the included core metadata drivers you just need to configure it. All the drivers are in the ``Doctrine\ORM\Mapping\Driver`` namespace: .. code-block:: php getConfiguration()->setMetadataDriverImpl($driver); Implementing Metadata Drivers ----------------------------- In addition to the included metadata drivers you can very easily implement your own. All you need to do is define a class which implements the ``Driver`` interface: .. code-block:: php _loadMappingFile($file); // populate ClassMetadataInfo instance from $data } /** * {@inheritdoc} */ protected function _loadMappingFile($file) { // parse contents of $file and return php data structure } } .. note:: When using the ``AbstractFileDriver`` it requires that you only have one entity defined per file and the file named after the class described inside where namespace separators are replaced by periods. So if you have an entity named ``Entities\User`` and you wanted to write a mapping file for your driver above you would need to name the file ``Entities.User.dcm.ext`` for it to be recognized. Now you can use your ``MyMetadataDriver`` implementation by setting it with the ``setMetadataDriverImpl()`` method: .. code-block:: php getConfiguration()->setMetadataDriverImpl($driver); ClassMetadata ------------- The last piece you need to know and understand about metadata in Doctrine 2 is the API of the ``ClassMetadata`` classes. You need to be familiar with them in order to implement your own drivers but more importantly to retrieve mapping information for a certain entity when needed. You have all the methods you need to manually specify the mapping information instead of using some mapping file to populate it from. The base ``ClassMetadataInfo`` class is responsible for only data storage and is not meant for runtime use. It does not require that the class actually exists yet so it is useful for describing some entity before it exists and using that information to generate for example the entities themselves. The class ``ClassMetadata`` extends ``ClassMetadataInfo`` and adds some functionality required for runtime usage and requires that the PHP class is present and can be autoloaded. You can read more about the API of the ``ClassMetadata`` classes in the PHP Mapping chapter. Getting ClassMetadata Instances ------------------------------- If you want to get the ``ClassMetadata`` instance for an entity in your project to programmatically use some mapping information to generate some HTML or something similar you can retrieve it through the ``ClassMetadataFactory``: .. code-block:: php getMetadataFactory(); $class = $cmf->getMetadataFor('MyEntityName'); Now you can learn about the entity and use the data stored in the ``ClassMetadata`` instance to get all mapped fields for example and iterate over them: .. code-block:: php fieldMappings as $fieldMapping) { echo $fieldMapping['fieldName'] . "\n"; } doctrine2-2.4.8/docs/en/reference/namingstrategy.rst000066400000000000000000000111351257105210500224730ustar00rootroot00000000000000Implementing a NamingStrategy ============================== .. versionadded:: 2.3 Using a naming strategy you can provide rules for automatically generating database identifiers, columns and tables names when the table/column name is not given. This feature helps reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``). Configuring a naming strategy ----------------------------- The default strategy used by Doctrine is quite minimal. By default the ``Doctrine\ORM\Mapping\DefaultNamingStrategy`` uses the simple class name and the attributes names to generate tables and columns You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` : .. code-block:: php setNamingStrategy($namingStrategy); Underscore naming strategy --------------------------- ``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy that might be a useful if you want to use a underlying convention. .. code-block:: php setNamingStrategy($namingStrategy); Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER or some_entity_name using CASE_LOWER is given. Naming strategy interface ------------------------- The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify a "naming standard" for database tables and columns. .. code-block:: php referenceColumnName(); } public function joinTableName($sourceEntity, $targetEntity, $propertyName = null) { return strtolower($this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity)); } public function joinKeyColumnName($entityName, $referencedColumnName = null) { return strtolower($this->classToTableName($entityName) . '_' . ($referencedColumnName ?: $this->referenceColumnName())); } } Configuring the namingstrategy is easy if. Just set your naming strategy calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :. .. code-block:: php setNamingStrategy($namingStrategy); doctrine2-2.4.8/docs/en/reference/native-sql.rst000066400000000000000000001045041257105210500215250ustar00rootroot00000000000000Native SQL ========== With ``NativeQuery`` you can execute native SELECT SQL statements and map the results to Doctrine entities or any other result format supported by Doctrine. In order to make this mapping possible, you need to describe to Doctrine what columns in the result map to which entity property. This description is represented by a ``ResultSetMapping`` object. With this feature you can map arbitrary SQL code to objects, such as highly vendor-optimized SQL or stored-procedures. Writing ``ResultSetMapping`` from scratch is complex, but there is a convenience wrapper around it called a ``ResultSetMappingBuilder``. It can generate the mappings for you based on Entities and even generates the ``SELECT`` clause based on this information for you. .. note:: If you want to execute DELETE, UPDATE or INSERT statements the Native SQL API cannot be used and will probably throw errors. Use ``EntityManager#getConnection()`` to access the native database connection and call the ``executeUpdate()`` method for these queries. The NativeQuery class --------------------- To create a ``NativeQuery`` you use the method ``EntityManager#createNativeQuery($sql, $resultSetMapping)``. As you can see in the signature of this method, it expects 2 ingredients: The SQL you want to execute and the ``ResultSetMapping`` that describes how the results will be mapped. Once you obtained an instance of a ``NativeQuery``, you can bind parameters to it with the same API that ``Query`` has and execute it. .. code-block:: php createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm); $query->setParameter(1, 'romanb'); $users = $query->getResult(); ResultSetMappingBuilder ----------------------- An easy start into ResultSet mapping is the ``ResultSetMappingBuilder`` object. This has several benefits: - The builder takes care of automatically updating your ``ResultSetMapping`` when the fields or associations change on the metadata of an entity. - You can generate the required ``SELECT`` expression for a builder by converting it to a string. - The API is much simpler than the usual ``ResultSetMapping`` API. One downside is that the builder API does not yet support entities with inheritance hierachies. .. code-block:: php addRootEntityFromClassMetadata('MyProject\User', 'u'); $rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' => 'address_id')); The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well. ..versionadded:: 2.4 Starting with Doctrine ORM 2.4 you can generate the ``SELECT`` clause from a ``ResultSetMappingBuilder``. You can either cast the builder object to ``(string)`` and the DQL aliases are used as SQL table aliases or use the ``generateSelectClause($tableAliases)`` method and pass a mapping from DQL alias (key) to SQL alias (value) .. code-block:: php generateSelectClause(array( 'u' => 't1', 'g' => 't2' )); $sql = "SELECT " . $selectClause . " FROM users t1 JOIN groups t2 ON t1.group_id = t2.id"; The ResultSetMapping -------------------- Understanding the ``ResultSetMapping`` is the key to using a ``NativeQuery``. A Doctrine result can contain the following components: - Entity results. These represent root result elements. - Joined entity results. These represent joined entities in associations of root entity results. - Field results. These represent a column in the result set that maps to a field of an entity. A field result always belongs to an entity result or joined entity result. - Scalar results. These represent scalar values in the result set that will appear in each result row. Adding scalar results to a ResultSetMapping can also cause the overall result to become **mixed** (see DQL - Doctrine Query Language) if the same ResultSetMapping also contains entity results. - Meta results. These represent columns that contain meta-information, such as foreign keys and discriminator columns. When querying for objects (``getResult()``), all meta columns of root entities or joined entities must be present in the SQL query and mapped accordingly using ``ResultSetMapping#addMetaResult``. .. note:: It might not surprise you that Doctrine uses ``ResultSetMapping`` internally when you create DQL queries. As the query gets parsed and transformed to SQL, Doctrine fills a ``ResultSetMapping`` that describes how the results should be processed by the hydration routines. We will now look at each of the result types that can appear in a ResultSetMapping in detail. Entity results ~~~~~~~~~~~~~~ An entity result describes an entity type that appears as a root element in the transformed result. You add an entity result through ``ResultSetMapping#addEntityResult()``. Let's take a look at the method signature in detail: .. code-block:: php addEntityResult('User', 'u'); $rsm->addFieldResult('u', 'id', 'id'); $rsm->addFieldResult('u', 'name', 'name'); $query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm); $query->setParameter(1, 'romanb'); $users = $query->getResult(); The result would look like this: .. code-block:: php array( [0] => User (Object) ) Note that this would be a partial object if the entity has more fields than just id and name. In the example above the column and field names are identical but that is not necessary, of course. Also note that the query string passed to createNativeQuery is **real native SQL**. Doctrine does not touch this SQL in any way. In the previous basic example, a User had no relations and the table the class is mapped to owns no foreign keys. The next example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress, where the User is the owning side and thus owns the foreign key. .. code-block:: php addEntityResult('User', 'u'); $rsm->addFieldResult('u', 'id', 'id'); $rsm->addFieldResult('u', 'name', 'name'); $rsm->addMetaResult('u', 'address_id', 'address_id'); $query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm); $query->setParameter(1, 'romanb'); $users = $query->getResult(); Foreign keys are used by Doctrine for lazy-loading purposes when querying for objects. In the previous example, each user object in the result will have a proxy (a "ghost") in place of the address that contains the address\_id. When the ghost proxy is accessed, it loads itself based on this key. Consequently, associations that are *fetch-joined* do not require the foreign keys to be present in the SQL result set, only associations that are lazy. .. code-block:: php addEntityResult('User', 'u'); $rsm->addFieldResult('u', 'id', 'id'); $rsm->addFieldResult('u', 'name', 'name'); $rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address'); $rsm->addFieldResult('a', 'address_id', 'id'); $rsm->addFieldResult('a', 'street', 'street'); $rsm->addFieldResult('a', 'city', 'city'); $sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' . 'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?'; $query = $this->_em->createNativeQuery($sql, $rsm); $query->setParameter(1, 'romanb'); $users = $query->getResult(); In this case the nested entity ``Address`` is registered with the ``ResultSetMapping#addJoinedEntityResult`` method, which notifies Doctrine that this entity is not hydrated at the root level, but as a joined entity somewhere inside the object graph. In this case we specify the alias 'u' as third parameter and ``address`` as fourth parameter, which means the ``Address`` is hydrated into the ``User::$address`` property. If a fetched entity is part of a mapped hierarchy that requires a discriminator column, this column must be present in the result set as a meta column so that Doctrine can create the appropriate concrete type. This is shown in the following example where we assume that there are one or more subclasses that extend User and either Class Table Inheritance or Single Table Inheritance is used to map the hierarchy (both use a discriminator column). .. code-block:: php addEntityResult('User', 'u'); $rsm->addFieldResult('u', 'id', 'id'); $rsm->addFieldResult('u', 'name', 'name'); $rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column $rsm->setDiscriminatorColumn('u', 'discr'); $query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm); $query->setParameter(1, 'romanb'); $users = $query->getResult(); Note that in the case of Class Table Inheritance, an example as above would result in partial objects if any objects in the result are actually a subtype of User. When using DQL, Doctrine automatically includes the necessary joins for this mapping strategy but with native SQL it is your responsibility. Named Native Query ------------------ You can also map a native query using a named native query mapping. To achieve that, you must describe the SQL resultset structure using named native query (and sql resultset mappings if is a several resultset mappings). Like named query, a named native query can be defined at class level or in a XML or YAML file. A resultSetMapping parameter is defined in @NamedNativeQuery, it represents the name of a defined @SqlResultSetMapping. .. configuration-block:: .. code-block:: php SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username .. code-block:: yaml MyProject\Model\User: type: entity namedNativeQueries: fetchMultipleJoinsEntityResults: name: fetchMultipleJoinsEntityResults resultSetMapping: mappingMultipleJoinsEntityResults query: SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username sqlResultSetMappings: mappingMultipleJoinsEntityResults: name: mappingMultipleJoinsEntityResults columnResult: 0: name: numphones entityResult: 0: entityClass: __CLASS__ fieldResult: 0: name: id column: u_id 1: name: name column: u_name 2: name: status column: u_status 1: entityClass: Address fieldResult: 0: name: id column: a_id 1: name: zip column: a_zip 2: name: country column: a_country Things to note: - The resultset mapping declares the entities retrieved by this native query. - Each field of the entity is bound to a SQL alias (or column name). - All fields of the entity including the ones of subclasses and the foreign key columns of related entities have to be present in the SQL query. - Field definitions are optional provided that they map to the same column name as the one declared on the class property. - ``__CLASS__`` is an alias for the mapped class In the above example, the ``fetchJoinedAddress`` named query use the joinMapping result set mapping. This mapping returns 2 entities, User and Address, each property is declared and associated to a column name, actually the column name retrieved by the query. Let's now see an implicit declaration of the property / column. .. configuration-block:: .. code-block:: php SELECT * FROM addresses .. code-block:: yaml MyProject\Model\Address: type: entity namedNativeQueries: findAll: resultSetMapping: mappingFindAll query: SELECT * FROM addresses sqlResultSetMappings: mappingFindAll: name: mappingFindAll entityResult: address: entityClass: Address In this example, we only describe the entity member of the result set mapping. The property / column mappings is done using the entity mapping values. In this case the model property is bound to the model_txt column. If the association to a related entity involve a composite primary key, a @FieldResult element should be used for each foreign key column. The @FieldResult name is composed of the property name for the relationship, followed by a dot ("."), followed by the name or the field or property of the primary key. .. configuration-block:: .. code-block:: php SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ? .. code-block:: yaml MyProject\Model\User: type: entity namedNativeQueries: fetchJoinedAddress: name: fetchJoinedAddress resultSetMapping: mappingJoinedAddress query: SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ? sqlResultSetMappings: mappingJoinedAddress: entityResult: 0: entityClass: __CLASS__ fieldResult: 0: name: id 1: name: name 2: name: status 3: name: address.id column: a_id 4: name: address.zip column: a_zip 5: name: address.city column: a_city 6: name: address.country column: a_country If you retrieve a single entity and if you use the default mapping, you can use the resultClass attribute instead of resultSetMapping: .. configuration-block:: .. code-block:: php SELECT * FROM addresses WHERE id = ? .. code-block:: yaml MyProject\Model\Address: type: entity namedNativeQueries: findAll: name: findAll resultClass: Address query: SELECT * FROM addresses In some of your native queries, you'll have to return scalar values, for example when building report queries. You can map them in the @SqlResultsetMapping through @ColumnResult. You actually can even mix, entities and scalar returns in the same native query (this is probably not that common though). .. configuration-block:: .. code-block:: php SELECT COUNT(*) AS count FROM addresses .. code-block:: yaml MyProject\Model\Address: type: entity namedNativeQueries: count: name: count resultSetMapping: mappingCount query: SELECT COUNT(*) AS count FROM addresses sqlResultSetMappings: mappingCount: name: mappingCount columnResult: count: name: count doctrine2-2.4.8/docs/en/reference/partial-objects.rst000066400000000000000000000070231257105210500225230ustar00rootroot00000000000000Partial Objects =============== A partial object is an object whose state is not fully initialized after being reconstituted from the database and that is disconnected from the rest of its data. The following section will describe why partial objects are problematic and what the approach of Doctrine2 to this problem is. .. note:: The partial object problem in general does not apply to methods or queries where you do not retrieve the query result as objects. Examples are: ``Query#getArrayResult()``, ``Query#getScalarResult()``, ``Query#getSingleScalarResult()``, etc. .. warning:: Use of partial objects is tricky. Fields that are not retrieved from the database will not be updated by the UnitOfWork even if they get changed in your objects. You can only promote a partial object to a fully-loaded object by calling ``EntityManager#refresh()`` or a DQL query with the refresh flag. What is the problem? -------------------- In short, partial objects are problematic because they are usually objects with broken invariants. As such, code that uses these partial objects tends to be very fragile and either needs to "know" which fields or methods can be safely accessed or add checks around every field access or method invocation. The same holds true for the internals, i.e. the method implementations, of such objects. You usually simply assume the state you need in the method is available, after all you properly constructed this object before you pushed it into the database, right? These blind assumptions can quickly lead to null reference errors when working with such partial objects. It gets worse with the scenario of an optional association (0..1 to 1). When the associated field is NULL, you don't know whether this object does not have an associated object or whether it was simply not loaded when the owning object was loaded from the database. These are reasons why many ORMs do not allow partial objects at all and instead you always have to load an object with all its fields (associations being proxied). One secure way to allow partial objects is if the programming language/platform allows the ORM tool to hook deeply into the object and instrument it in such a way that individual fields (not only associations) can be loaded lazily on first access. This is possible in Java, for example, through bytecode instrumentation. In PHP though this is not possible, so there is no way to have "secure" partial objects in an ORM with transparent persistence. Doctrine, by default, does not allow partial objects. That means, any query that only selects partial object data and wants to retrieve the result as objects (i.e. ``Query#getResult()``) will raise an exception telling you that partial objects are dangerous. If you want to force a query to return you partial objects, possibly as a performance tweak, you can use the ``partial`` keyword as follows: .. code-block:: php createQuery("select partial u.{id,name} from MyApp\Domain\User u"); You can also get a partial reference instead of a proxy reference by calling: .. code-block:: php getPartialReference('MyApp\Domain\User', 1); Partial references are objects with only the identifiers set as they are passed to the second argument of the ``getPartialReference()`` method. All other fields are null. When should I force partial objects? ------------------------------------ Mainly for optimization purposes, but be careful of premature optimization as partial objects lead to potentially more fragile code. doctrine2-2.4.8/docs/en/reference/php-mapping.rst000066400000000000000000000207741257105210500216700ustar00rootroot00000000000000PHP Mapping =========== Doctrine 2 also allows you to provide the ORM metadata in the form of plain PHP code using the ``ClassMetadata`` API. You can write the code in PHP files or inside of a static function named ``loadMetadata($class)`` on the entity class itself. PHP Files --------- If you wish to write your mapping information inside PHP files that are named after the entity and included to populate the metadata for an entity you can do so by using the ``PHPDriver``: .. code-block:: php getConfiguration()->setMetadataDriverImpl($driver); Now imagine we had an entity named ``Entities\User`` and we wanted to write a mapping file for it using the above configured ``PHPDriver`` instance: .. code-block:: php mapField(array( 'id' => true, 'fieldName' => 'id', 'type' => 'integer' )); $metadata->mapField(array( 'fieldName' => 'username', 'type' => 'string' )); Now we can easily retrieve the populated ``ClassMetadata`` instance where the ``PHPDriver`` includes the file and the ``ClassMetadataFactory`` caches it for later retrieval: .. code-block:: php getClassMetadata('Entities\User'); // or $class = $em->getMetadataFactory()->getMetadataFor('Entities\User'); Static Function --------------- In addition to the PHP files you can also specify your mapping information inside of a static function defined on the entity class itself. This is useful for cases where you want to keep your entity and mapping information together but don't want to use annotations. For this you just need to use the ``StaticPHPDriver``: .. code-block:: php getConfiguration()->setMetadataDriverImpl($driver); Now you just need to define a static function named ``loadMetadata($metadata)`` on your entity: .. code-block:: php mapField(array( 'id' => true, 'fieldName' => 'id', 'type' => 'integer' )); $metadata->mapField(array( 'fieldName' => 'username', 'type' => 'string' )); } } ClassMetadataBuilder -------------------- To ease the use of the ClassMetadata API (which is very raw) there is a ``ClassMetadataBuilder`` that you can use. .. code-block:: php createField('id', 'integer')->isPrimaryKey()->generatedValue()->build(); $builder->addField('username', 'string'); } } The API of the ClassMetadataBuilder has the following methods with a fluent interface: - ``addField($name, $type, array $mapping)`` - ``setMappedSuperclass()`` - ``setReadOnly()`` - ``setCustomRepositoryClass($className)`` - ``setTable($name)`` - ``addIndex(array $columns, $indexName)`` - ``addUniqueConstraint(array $columns, $constraintName)`` - ``addNamedQuery($name, $dqlQuery)`` - ``setJoinedTableInheritance()`` - ``setSingleTableInheritance()`` - ``setDiscriminatorColumn($name, $type = 'string', $length = 255)`` - ``addDiscriminatorMapClass($name, $class)`` - ``setChangeTrackingPolicyDeferredExplicit()`` - ``setChangeTrackingPolicyNotify()`` - ``addLifecycleEvent($methodName, $event)`` - ``addManyToOne($name, $targetEntity, $inversedBy = null)`` - ``addInverseOneToOne($name, $targetEntity, $mappedBy)`` - ``addOwningOneToOne($name, $targetEntity, $inversedBy = null)`` - ``addOwningManyToMany($name, $targetEntity, $inversedBy = null)`` - ``addInverseManyToMany($name, $targetEntity, $mappedBy)`` - ``addOneToMany($name, $targetEntity, $mappedBy)`` It also has several methods that create builders (which are necessary for advanced mappings): - ``createField($name, $type)`` returns a ``FieldBuilder`` instance - ``createManyToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance - ``createOneToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance - ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssociationBuilder`` instance - ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance ClassMetadataInfo API --------------------- The ``ClassMetadataInfo`` class is the base data object for storing the mapping metadata for a single entity. It contains all the getters and setters you need populate and retrieve information for an entity. General Setters ~~~~~~~~~~~~~~~ - ``setTableName($tableName)`` - ``setPrimaryTable(array $primaryTableDefinition)`` - ``setCustomRepositoryClass($repositoryClassName)`` - ``setIdGeneratorType($generatorType)`` - ``setIdGenerator($generator)`` - ``setSequenceGeneratorDefinition(array $definition)`` - ``setChangeTrackingPolicy($policy)`` - ``setIdentifier(array $identifier)`` Inheritance Setters ~~~~~~~~~~~~~~~~~~~ - ``setInheritanceType($type)`` - ``setSubclasses(array $subclasses)`` - ``setParentClasses(array $classNames)`` - ``setDiscriminatorColumn($columnDef)`` - ``setDiscriminatorMap(array $map)`` Field Mapping Setters ~~~~~~~~~~~~~~~~~~~~~ - ``mapField(array $mapping)`` - ``mapOneToOne(array $mapping)`` - ``mapOneToMany(array $mapping)`` - ``mapManyToOne(array $mapping)`` - ``mapManyToMany(array $mapping)`` Lifecycle Callback Setters ~~~~~~~~~~~~~~~~~~~~~~~~~~ - ``addLifecycleCallback($callback, $event)`` - ``setLifecycleCallbacks(array $callbacks)`` Versioning Setters ~~~~~~~~~~~~~~~~~~ - ``setVersionMapping(array &$mapping)`` - ``setVersioned($bool)`` - ``setVersionField()`` General Getters ~~~~~~~~~~~~~~~ - ``getTableName()`` - ``getTemporaryIdTableName()`` Identifier Getters ~~~~~~~~~~~~~~~~~~ - ``getIdentifierColumnNames()`` - ``usesIdGenerator()`` - ``isIdentifier($fieldName)`` - ``isIdGeneratorIdentity()`` - ``isIdGeneratorSequence()`` - ``isIdGeneratorTable()`` - ``isIdentifierNatural()`` - ``getIdentifierFieldNames()`` - ``getSingleIdentifierFieldName()`` - ``getSingleIdentifierColumnName()`` Inheritance Getters ~~~~~~~~~~~~~~~~~~~ - ``isInheritanceTypeNone()`` - ``isInheritanceTypeJoined()`` - ``isInheritanceTypeSingleTable()`` - ``isInheritanceTypeTablePerClass()`` - ``isInheritedField($fieldName)`` - ``isInheritedAssociation($fieldName)`` Change Tracking Getters ~~~~~~~~~~~~~~~~~~~~~~~ - ``isChangeTrackingDeferredExplicit()`` - ``isChangeTrackingDeferredImplicit()`` - ``isChangeTrackingNotify()`` Field & Association Getters ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ``isUniqueField($fieldName)`` - ``isNullable($fieldName)`` - ``getColumnName($fieldName)`` - ``getFieldMapping($fieldName)`` - ``getAssociationMapping($fieldName)`` - ``getAssociationMappings()`` - ``getFieldName($columnName)`` - ``hasField($fieldName)`` - ``getColumnNames(array $fieldNames = null)`` - ``getTypeOfField($fieldName)`` - ``getTypeOfColumn($columnName)`` - ``hasAssociation($fieldName)`` - ``isSingleValuedAssociation($fieldName)`` - ``isCollectionValuedAssociation($fieldName)`` Lifecycle Callback Getters ~~~~~~~~~~~~~~~~~~~~~~~~~~ - ``hasLifecycleCallbacks($lifecycleEvent)`` - ``getLifecycleCallbacks($event)`` ClassMetadata API ----------------- The ``ClassMetadata`` class extends ``ClassMetadataInfo`` and adds the runtime functionality required by Doctrine. It adds a few extra methods related to runtime reflection for working with the entities themselves. - ``getReflectionClass()`` - ``getReflectionProperties()`` - ``getReflectionProperty($name)`` - ``getSingleIdReflectionProperty()`` - ``getIdentifierValues($entity)`` - ``setIdentifierValues($entity, $id)`` - ``setFieldValue($entity, $field, $value)`` - ``getFieldValue($entity, $field)`` doctrine2-2.4.8/docs/en/reference/query-builder.rst000066400000000000000000000452501257105210500222350ustar00rootroot00000000000000The QueryBuilder ================ A ``QueryBuilder`` provides an API that is designed for conditionally constructing a DQL query in several steps. It provides a set of classes and methods that is able to programmatically build queries, and also provides a fluent API. This means that you can change between one methodology to the other as you want, and also pick one if you prefer. Constructing a new QueryBuilder object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The same way you build a normal Query, you build a ``QueryBuilder`` object, just providing the correct method name. Here is an example how to build a ``QueryBuilder`` object: .. code-block:: php createQueryBuilder(); Once you have created an instance of QueryBuilder, it provides a set of useful informative functions that you can use. One good example is to inspect what type of object the ``QueryBuilder`` is. .. code-block:: php getType(); // Prints: 0 There're currently 3 possible return values for ``getType()``: - ``QueryBuilder::SELECT``, which returns value 0 - ``QueryBuilder::DELETE``, returning value 1 - ``QueryBuilder::UPDATE``, which returns value 2 It is possible to retrieve the associated ``EntityManager`` of the current ``QueryBuilder``, its DQL and also a ``Query`` object when you finish building your DQL. .. code-block:: php getEntityManager(); // example4: retrieve the DQL string of what was defined in QueryBuilder $dql = $qb->getDql(); // example5: retrieve the associated Query object with the processed DQL $q = $qb->getQuery(); Internally, ``QueryBuilder`` works with a DQL cache to increase performance. Any changes that may affect the generated DQL actually modifies the state of ``QueryBuilder`` to a stage we call STATE\_DIRTY. One ``QueryBuilder`` can be in two different states: - ``QueryBuilder::STATE_CLEAN``, which means DQL haven't been altered since last retrieval or nothing were added since its instantiation - ``QueryBuilder::STATE_DIRTY``, means DQL query must (and will) be processed on next retrieval Working with QueryBuilder ~~~~~~~~~~~~~~~~~~~~~~~~~ High level API methods ^^^^^^^^^^^^^^^^^^^^^^ To simplify even more the way you build a query in Doctrine, we can take advantage of what we call Helper methods. For all base code, there is a set of useful methods to simplify a programmer's life. To illustrate how to work with them, here is the same example 6 re-written using ``QueryBuilder`` helper methods: .. code-block:: php select('u') ->from('User', 'u') ->where('u.id = ?1') ->orderBy('u.name', 'ASC'); ``QueryBuilder`` helper methods are considered the standard way to build DQL queries. Although it is supported, it should be avoided to use string based queries and greatly encouraged to use ``$qb->expr()->*`` methods. Here is a converted example 8 to suggested standard way to build queries: .. code-block:: php select(array('u')) // string 'u' is converted to array internally ->from('User', 'u') ->where($qb->expr()->orX( $qb->expr()->eq('u.id', '?1'), $qb->expr()->like('u.nickname', '?2') )) ->orderBy('u.surname', 'ASC')); Here is a complete list of helper methods available in ``QueryBuilder``: .. code-block:: php select('u') // Example - $qb->select(array('u', 'p')) // Example - $qb->select($qb->expr()->select('u', 'p')) public function select($select = null); // Example - $qb->delete('User', 'u') public function delete($delete = null, $alias = null); // Example - $qb->update('Group', 'g') public function update($update = null, $alias = null); // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold')) // Example - $qb->set('u.numChilds', 'u.numChilds + ?1') // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1')) public function set($key, $value); // Example - $qb->from('Phonenumber', 'p') public function from($from, $alias = null); // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1')) // Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1') public function innerJoin($join, $alias = null, $conditionType = null, $condition = null); // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55)) // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55') public function leftJoin($join, $alias = null, $conditionType = null, $condition = null); // NOTE: ->where() overrides all previously set conditions // // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2')) // Example - $qb->where($qb->expr()->andX($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2'))) // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2') public function where($where); // Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0')) public function andWhere($where); // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10)); public function orWhere($where); // NOTE: -> groupBy() overrides all previously set grouping conditions // // Example - $qb->groupBy('u.id') public function groupBy($groupBy); // Example - $qb->addGroupBy('g.name') public function addGroupBy($groupBy); // NOTE: -> having() overrides all previously set having conditions // // Example - $qb->having('u.salary >= ?1') // Example - $qb->having($qb->expr()->gte('u.salary', '?1')) public function having($having); // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0)) public function andHaving($having); // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100')) public function orHaving($having); // NOTE: -> orderBy() overrides all previously set ordering conditions // // Example - $qb->orderBy('u.surname', 'DESC') public function orderBy($sort, $order = null); // Example - $qb->addOrderBy('u.firstName') public function addOrderBy($sort, $order = null); // Default $order = 'ASC' } Binding parameters to your query ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Doctrine supports dynamic binding of parameters to your query, similar to preparing queries. You can use both strings and numbers as placeholders, although both have a slightly different syntax. Additionally, you must make your choice: Mixing both styles is not allowed. Binding parameters can simply be achieved as follows: .. code-block:: php select('u') ->from('User u') ->where('u.id = ?1') ->orderBy('u.name', 'ASC'); ->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100 You are not forced to enumerate your placeholders as the alternative syntax is available: .. code-block:: php select('u') ->from('User u') ->where('u.id = :identifier') ->orderBy('u.name', 'ASC'); ->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100 Note that numeric placeholders start with a ? followed by a number while the named placeholders start with a : followed by a string. Calling ``setParameter()`` automatically infers which type you are setting as value. This works for integers, arrays of strings/integers, DateTime instances and for managed entities. If you want to set a type explicitly you can call the third argument to ``setParameter()`` explicitly. It accepts either a PDO type or a DBAL Type name for conversion. If you've got several parameters to bind to your query, you can also use setParameters() instead of setParameter() with the following syntax: .. code-block:: php setParameters(array(1 => 'value for ?1', 2 => 'value for ?2')); Getting already bound parameters is easy - simply use the above mentioned syntax with "getParameter()" or "getParameters()": .. code-block:: php getParameters(); // $params instanceof \Doctrine\Common\Collections\ArrayCollection // Equivalent to $param = $qb->getParameter(1); // $param instanceof \Doctrine\ORM\Query\Parameter Note: If you try to get a parameter that was not bound yet, getParameter() simply returns NULL. The API of a Query Parameter is: .. code-block:: php namespace Doctrine\ORM\Query; class Parameter { public function getName(); public function getValue(); public function getType(); public function setValue($value, $type = null); } Limiting the Result ^^^^^^^^^^^^^^^^^^^ To limit a result the query builder has some methods in common with the Query object which can be retrieved from ``EntityManager#createQuery()``. .. code-block:: php add('select', 'u') ->add('from', 'User u') ->add('orderBy', 'u.name ASC') ->setFirstResult( $offset ) ->setMaxResults( $limit ); Executing a Query ^^^^^^^^^^^^^^^^^ The QueryBuilder is a builder object only, it has no means of actually executing the Query. Additionally a set of parameters such as query hints cannot be set on the QueryBuilder itself. This is why you always have to convert a querybuilder instance into a Query object: .. code-block:: php getQuery(); // Set additional Query options $query->setQueryHint('foo', 'bar'); $query->useResultCache('my_cache_id'); // Execute Query $result = $query->getResult(); $single = $query->getSingleResult(); $array = $query->getArrayResult(); $scalar = $query->getScalarResult(); $singleScalar = $query->getSingleScalarResult(); The Expr class ^^^^^^^^^^^^^^ To workaround some of the issues that ``add()`` method may cause, Doctrine created a class that can be considered as a helper for building expressions. This class is called ``Expr``, which provides a set of useful methods to help build expressions: .. code-block:: php add('select', new Expr\Select(array('u'))) ->add('from', new Expr\From('User', 'u')) ->add('where', $qb->expr()->orX( $qb->expr()->eq('u.id', '?1'), $qb->expr()->like('u.nickname', '?2') )) ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); Although it still sounds complex, the ability to programmatically create conditions are the main feature of ``Expr``. Here it is a complete list of supported helper methods available: .. code-block:: php expr()->andX($cond1 [, $condN])->add(...)->... public function andX($x = null); // Returns Expr\AndX instance // Example - $qb->expr()->orX($cond1 [, $condN])->add(...)->... public function orX($x = null); // Returns Expr\OrX instance /** Comparison objects **/ // Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1 public function eq($x, $y); // Returns Expr\Comparison instance // Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1 public function neq($x, $y); // Returns Expr\Comparison instance // Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1 public function lt($x, $y); // Returns Expr\Comparison instance // Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1 public function lte($x, $y); // Returns Expr\Comparison instance // Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1 public function gt($x, $y); // Returns Expr\Comparison instance // Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1 public function gte($x, $y); // Returns Expr\Comparison instance // Example - $qb->expr()->isNull('u.id') => u.id IS NULL public function isNull($x); // Returns string // Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL public function isNotNull($x); // Returns string /** Arithmetic objects **/ // Example - $qb->expr()->prod('u.id', '2') => u.id * 2 public function prod($x, $y); // Returns Expr\Math instance // Example - $qb->expr()->diff('u.id', '2') => u.id - 2 public function diff($x, $y); // Returns Expr\Math instance // Example - $qb->expr()->sum('u.id', '2') => u.id + 2 public function sum($x, $y); // Returns Expr\Math instance // Example - $qb->expr()->quot('u.id', '2') => u.id / 2 public function quot($x, $y); // Returns Expr\Math instance /** Pseudo-function objects **/ // Example - $qb->expr()->exists($qb2->getDql()) public function exists($subquery); // Returns Expr\Func instance // Example - $qb->expr()->all($qb2->getDql()) public function all($subquery); // Returns Expr\Func instance // Example - $qb->expr()->some($qb2->getDql()) public function some($subquery); // Returns Expr\Func instance // Example - $qb->expr()->any($qb2->getDql()) public function any($subquery); // Returns Expr\Func instance // Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1')) public function not($restriction); // Returns Expr\Func instance // Example - $qb->expr()->in('u.id', array(1, 2, 3)) // Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception. // Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above) public function in($x, $y); // Returns Expr\Func instance // Example - $qb->expr()->notIn('u.id', '2') public function notIn($x, $y); // Returns Expr\Func instance // Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%')) public function like($x, $y); // Returns Expr\Comparison instance // Example - $qb->expr()->between('u.id', '1', '10') public function between($val, $x, $y); // Returns Expr\Func /** Function objects **/ // Example - $qb->expr()->trim('u.firstname') public function trim($x); // Returns Expr\Func // Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat($qb->expr()->literal(' '), 'u.lastname')) public function concat($x, $y); // Returns Expr\Func // Example - $qb->expr()->substr('u.firstname', 0, 1) public function substr($x, $from, $len); // Returns Expr\Func // Example - $qb->expr()->lower('u.firstname') public function lower($x); // Returns Expr\Func // Example - $qb->expr()->upper('u.firstname') public function upper($x); // Returns Expr\Func // Example - $qb->expr()->length('u.firstname') public function length($x); // Returns Expr\Func // Example - $qb->expr()->avg('u.age') public function avg($x); // Returns Expr\Func // Example - $qb->expr()->max('u.age') public function max($x); // Returns Expr\Func // Example - $qb->expr()->min('u.age') public function min($x); // Returns Expr\Func // Example - $qb->expr()->abs('u.currentBalance') public function abs($x); // Returns Expr\Func // Example - $qb->expr()->sqrt('u.currentBalance') public function sqrt($x); // Returns Expr\Func // Example - $qb->expr()->count('u.firstname') public function count($x); // Returns Expr\Func // Example - $qb->expr()->countDistinct('u.surname') public function countDistinct($x); // Returns Expr\Func } Low Level API ^^^^^^^^^^^^^ Now we have describe the low level (thought of as the hardcore method) of creating queries. It may be useful to work at this level for optimization purposes, but most of the time it is preferred to work at a higher level of abstraction. All helper methods in ``QueryBuilder`` actually rely on a single one: ``add()``. This method is responsible of building every piece of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and ``$append`` (default=false) - ``$dqlPartName``: Where the ``$dqlPart`` should be placed. Possible values: select, from, where, groupBy, having, orderBy - ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts a string or any instance of ``Doctrine\ORM\Query\Expr\*`` - ``$append``: Optional flag (default=false) if the ``$dqlPart`` should override all previously defined items in ``$dqlPartName`` or not (no effect on the ``where`` and ``having`` DQL query parts, which always override all previously defined items) - .. code-block:: php add('select', 'u') ->add('from', 'User u') ->add('where', 'u.id = ?1') ->add('orderBy', 'u.name ASC'); Expr\* classes ^^^^^^^^^^^^^^ When you call ``add()`` with string, it internally evaluates to an instance of ``Doctrine\ORM\Query\Expr\Expr\*`` class. Here is the same query of example 6 written using ``Doctrine\ORM\Query\Expr\Expr\*`` classes: .. code-block:: php add('select', new Expr\Select(array('u'))) ->add('from', new Expr\From('User', 'u')) ->add('where', new Expr\Comparison('u.id', '=', '?1')) ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); Of course this is the hardest way to build a DQL query in Doctrine. To simplify some of these efforts, we introduce what we call as ``Expr`` helper class. doctrine2-2.4.8/docs/en/reference/tools.rst000066400000000000000000000412401257105210500205770ustar00rootroot00000000000000Tools ===== Doctrine Console ---------------- The Doctrine Console is a Command Line Interface tool for simplifying common administration tasks during the development of a project that uses Doctrine 2. Take a look at the :doc:`Installation and Configuration ` chapter for more information how to setup the console command. Display Help Information ~~~~~~~~~~~~~~~~~~~~~~~~ Type ``php vendor/bin/doctrine`` on the command line and you should see an overview of the available commands or use the --help flag to get information on the available commands. If you want to know more about the use of generate entities for example, you can call: .. code-block:: php $> php vendor/bin/doctrine orm:generate-entities --help Configuration ~~~~~~~~~~~~~ Whenever the ``doctrine`` command line tool is invoked, it can access all Commands that were registered by developer. There is no auto-detection mechanism at work. The Doctrine binary already registers all the commands that currently ship with Doctrine DBAL and ORM. If you want to use additional commands you have to register them yourself. All the commands of the Doctrine Console require access to the EntityManager or DBAL Connection. You have to inject them into the console application using so called Helper-Sets. This requires either the ``db`` or the ``em`` helpers to be defined in order to work correctly. Whenever you invoke the Doctrine binary the current folder is searched for a ``cli-config.php`` file. This file contains the project specific configuration: .. code-block:: php new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn) )); $cli->setHelperSet($helperSet); When dealing with the ORM package, the EntityManagerHelper is required: .. code-block:: php new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); $cli->setHelperSet($helperSet); The HelperSet instance has to be generated in a separate file (i.e. ``cli-config.php``) that contains typical Doctrine bootstrap code and predefines the needed HelperSet attributes mentioned above. A sample ``cli-config.php`` file looks as follows: .. code-block:: php new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); It is important to define a correct HelperSet that Doctrine binary script will ultimately use. The Doctrine Binary will automatically find the first instance of HelperSet in the global variable namespace and use this. .. note:: You have to adjust this snippet for your specific application or framework and use their facilities to access the Doctrine EntityManager and Connection Resources. Command Overview ~~~~~~~~~~~~~~~~ The following Commands are currently available: - ``help`` Displays help for a command (?) - ``list`` Lists commands - ``dbal:import`` Import SQL file(s) directly to Database. - ``dbal:run-sql`` Executes arbitrary SQL directly from the command line. - ``orm:clear-cache:metadata`` Clear all metadata cache of the various cache drivers. - ``orm:clear-cache:query`` Clear all query cache of the various cache drivers. - ``orm:clear-cache:result`` Clear result cache of the various cache drivers. - ``orm:convert-d1-schema`` Converts Doctrine 1.X schema into a Doctrine 2.X schema. - ``orm:convert-mapping`` Convert mapping information between supported formats. - ``orm:ensure-production-settings`` Verify that Doctrine is properly configured for a production environment. - ``orm:generate-entities`` Generate entity classes and method stubs from your mapping information. - ``orm:generate-proxies`` Generates proxy classes for entity classes. - ``orm:generate-repositories`` Generate repository classes from your mapping information. - ``orm:run-dql`` Executes arbitrary DQL directly from the command line. - ``orm:schema-tool:create`` Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output. - ``orm:schema-tool:drop`` Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output. - ``orm:schema-tool:update`` Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output. For these commands are also available aliases: - ``orm:convert:d1-schema`` is alias for ``orm:convert-d1-schema``. - ``orm:convert:mapping`` is alias for ``orm:convert-mapping``. - ``orm:generate:entities`` is alias for ``orm:generate-entities``. - ``orm:generate:proxies`` is alias for ``orm:generate-proxies``. - ``orm:generate:repositories`` is alias for ``orm:generate-repositories``. .. note:: Console also supports auto completion, for example, instead of ``orm:clear-cache:query`` you can use just ``o:c:q``. Database Schema Generation -------------------------- .. note:: SchemaTool can do harm to your database. It will drop or alter tables, indexes, sequences and such. Please use this tool with caution in development and not on a production server. It is meant for helping you develop your Database Schema, but NOT with migrating schema from A to B in production. A safe approach would be generating the SQL on development server and saving it into SQL Migration files that are executed manually on the production server. SchemaTool assumes your Doctrine Project uses the given database on its own. Update and Drop commands will mess with other tables if they are not related to the current project that is using Doctrine. Please be careful! To generate your database schema from your Doctrine mapping files you can use the ``SchemaTool`` class or the ``schema-tool`` Console Command. When using the SchemaTool class directly, create your schema using the ``createSchema()`` method. First create an instance of the ``SchemaTool`` and pass it an instance of the ``EntityManager`` that you want to use to create the schema. This method receives an array of ``ClassMetadataInfo`` instances. .. code-block:: php getClassMetadata('Entities\User'), $em->getClassMetadata('Entities\Profile') ); $tool->createSchema($classes); To drop the schema you can use the ``dropSchema()`` method. .. code-block:: php dropSchema($classes); This drops all the tables that are currently used by your metadata model. When you are changing your metadata a lot during development you might want to drop the complete database instead of only the tables of the current model to clean up with orphaned tables. .. code-block:: php dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE); You can also use database introspection to update your schema easily with the ``updateSchema()`` method. It will compare your existing database schema to the passed array of ``ClassMetdataInfo`` instances. .. code-block:: php updateSchema($classes); If you want to use this functionality from the command line you can use the ``schema-tool`` command. To create the schema use the ``create`` command: .. code-block:: php $ php doctrine orm:schema-tool:create To drop the schema use the ``drop`` command: .. code-block:: php $ php doctrine orm:schema-tool:drop If you want to drop and then recreate the schema then use both options: .. code-block:: php $ php doctrine orm:schema-tool:drop $ php doctrine orm:schema-tool:create As you would think, if you want to update your schema use the ``update`` command: .. code-block:: php $ php doctrine orm:schema-tool:update All of the above commands also accept a ``--dump-sql`` option that will output the SQL for the ran operation. .. code-block:: php $ php doctrine orm:schema-tool:create --dump-sql Before using the orm:schema-tool commands, remember to configure your cli-config.php properly. .. note:: When using the Annotation Mapping Driver you have to either setup your autoloader in the cli-config.php correctly to find all the entities, or you can use the second argument of the ``EntityManagerHelper`` to specify all the paths of your entities (or mapping files), i.e. ``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);`` Entity Generation ----------------- Generate entity classes and method stubs from your mapping information. .. code-block:: php $ php doctrine orm:generate-entities $ php doctrine orm:generate-entities --update-entities $ php doctrine orm:generate-entities --regenerate-entities This command is not suited for constant usage. It is a little helper and does not support all the mapping edge cases very well. You still have to put work in your entities after using this command. It is possible to use the EntityGenerator on code that you have already written. It will not be lost. The EntityGenerator will only append new code to your file and will not delete the old code. However this approach may still be prone to error and we suggest you use code repositories such as GIT or SVN to make backups of your code. It makes sense to generate the entity code if you are using entities as Data Access Objects only and don't put much additional logic on them. If you are however putting much more logic on the entities you should refrain from using the entity-generator and code your entities manually. .. note:: Even if you specified Inheritance options in your XML or YAML Mapping files the generator cannot generate the base and child classes for you correctly, because it doesn't know which class is supposed to extend which. You have to adjust the entity code manually for inheritance to work! Convert Mapping Information --------------------------- Convert mapping information between supported formats. This is an **execute one-time** command. It should not be necessary for you to call this method multiple times, especially when using the ``--from-database`` flag. Converting an existing database schema into mapping files only solves about 70-80% of the necessary mapping information. Additionally the detection from an existing database cannot detect inverse associations, inheritance types, entities with foreign keys as primary keys and many of the semantical operations on associations such as cascade. .. note:: There is no need to convert YAML or XML mapping files to annotations every time you make changes. All mapping drivers are first class citizens in Doctrine 2 and can be used as runtime mapping for the ORM. See the docs on XML and YAML Mapping for an example how to register this metadata drivers as primary mapping source. To convert some mapping information between the various supported formats you can use the ``ClassMetadataExporter`` to get exporter instances for the different formats: .. code-block:: php getExporter('yml', '/path/to/export/yml'); Now you can export some ``ClassMetadata`` instances: .. code-block:: php getClassMetadata('Entities\User'), $em->getClassMetadata('Entities\Profile') ); $exporter->setMetadata($classes); $exporter->export(); This functionality is also available from the command line to convert your loaded mapping information to another format. The ``orm:convert-mapping`` command accepts two arguments, the type to convert to and the path to generate it: .. code-block:: php $ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml Reverse Engineering ------------------- You can use the ``DatabaseDriver`` to reverse engineer a database to an array of ``ClassMetadataInfo`` instances and generate YAML, XML, etc. from them. .. note:: Reverse Engineering is a **one-time** process that can get you started with a project. Converting an existing database schema into mapping files only detects about 70-80% of the necessary mapping information. Additionally the detection from an existing database cannot detect inverse associations, inheritance types, entities with foreign keys as primary keys and many of the semantical operations on associations such as cascade. First you need to retrieve the metadata instances with the ``DatabaseDriver``: .. code-block:: php getConfiguration()->setMetadataDriverImpl( new \Doctrine\ORM\Mapping\Driver\DatabaseDriver( $em->getConnection()->getSchemaManager() ) ); $cmf = new DisconnectedClassMetadataFactory(); $cmf->setEntityManager($em); $metadata = $cmf->getAllMetadata(); Now you can get an exporter instance and export the loaded metadata to yml: .. code-block:: php getExporter('yml', '/path/to/export/yml'); $exporter->setMetadata($metadata); $exporter->export(); You can also reverse engineer a database using the ``orm:convert-mapping`` command: .. code-block:: php $ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml .. note:: Reverse Engineering is not always working perfectly depending on special cases. It will only detect Many-To-One relations (even if they are One-To-One) and will try to create entities from Many-To-Many tables. It also has problems with naming of foreign keys that have multiple column names. Any Reverse Engineered Database-Schema needs considerable manual work to become a useful domain model. Runtime vs Development Mapping Validation ----------------------------------------- For performance reasons Doctrine 2 has to skip some of the necessary validation of metadata mappings. You have to execute this validation in your development workflow to verify the associations are correctly defined. You can either use the Doctrine Command Line Tool: .. code-block:: php doctrine orm:validate-schema Or you can trigger the validation manually: .. code-block:: php validateMapping(); if (count($errors) > 0) { // Lots of errors! echo implode("\n\n", $errors); } If the mapping is invalid the errors array contains a positive number of elements with error messages. .. warning:: One mapping option that is not validated is the use of the referenced column name. It has to point to the equivalent primary key otherwise Doctrine will not work. .. note:: One common error is to use a backlash in front of the fully-qualified class-name. Whenever a FQCN is represented inside a string (such as in your mapping definitions) you have to drop the prefix backslash. PHP does this with ``get_class()`` or Reflection methods for backwards compatibility reasons. Adding own commands ------------------- You can also add your own commands on-top of the Doctrine supported tools if you are using a manually built console script. To include a new command on Doctrine Console, you need to do modify the ``doctrine.php`` file a little: .. code-block:: php setCatchExceptions(true); $cli->setHelperSet($helperSet); // Register All Doctrine Commands ConsoleRunner::addCommands($cli); // Register your own command $cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand); // Runs console application $cli->run(); Additionally, include multiple commands (and overriding previously defined ones) is possible through the command: .. code-block:: php addCommands(array( new \MyProject\Tools\Console\Commands\MyCustomCommand(), new \MyProject\Tools\Console\Commands\SomethingCommand(), new \MyProject\Tools\Console\Commands\AnotherCommand(), new \MyProject\Tools\Console\Commands\OneMoreCommand(), )); doctrine2-2.4.8/docs/en/reference/transactions-and-concurrency.rst000066400000000000000000000311021257105210500252330ustar00rootroot00000000000000Transactions and Concurrency ============================ Transaction Demarcation ----------------------- Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very important because if not done properly it can negatively affect the performance of your application. Many databases and database abstraction layers like PDO by default operate in auto-commit mode, which means that every single SQL statement is wrapped in a small transaction. Without any explicit transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap. For the most part, Doctrine 2 already takes care of proper transaction demarcation for you: All the write operations (INSERT/UPDATE/DELETE) are queued until ``EntityManager#flush()`` is invoked which wraps all of these changes in a single transaction. However, Doctrine 2 also allows (and encourages) you to take over and control transaction demarcation yourself. These are two ways to deal with transactions when using the Doctrine ORM and are now described in more detail. Approach 1: Implicitly ~~~~~~~~~~~~~~~~~~~~~~ The first approach is to use the implicit transaction handling provided by the Doctrine ORM EntityManager. Given the following code snippet, without any explicit transaction demarcation: .. code-block:: php setName('George'); $em->persist($user); $em->flush(); Since we do not do any custom transaction demarcation in the above code, ``EntityManager#flush()`` will begin and commit/rollback a transaction. This behavior is made possible by the aggregation of the DML operations by the Doctrine ORM and is sufficient if all the data manipulation that is part of a unit of work happens through the domain model and thus the ORM. Approach 2: Explicitly ~~~~~~~~~~~~~~~~~~~~~~ The explicit alternative is to use the ``Doctrine\DBAL\Connection`` API directly to control the transaction boundaries. The code then looks like this: .. code-block:: php getConnection()->beginTransaction(); // suspend auto-commit try { //... do some work $user = new User; $user->setName('George'); $em->persist($user); $em->flush(); $em->getConnection()->commit(); } catch (Exception $e) { $em->getConnection()->rollback(); $em->close(); throw $e; } Explicit transaction demarcation is required when you want to include custom DBAL operations in a unit of work or when you want to make use of some methods of the ``EntityManager`` API that require an active transaction. Such methods will throw a ``TransactionRequiredException`` to inform you of that requirement. A more convenient alternative for explicit transaction demarcation is the use of provided control abstractions in the form of ``Connection#transactional($func)`` and ``EntityManager#transactional($func)``. When used, these control abstractions ensure that you never forget to rollback the transaction or close the ``EntityManager``, apart from the obvious code reduction. An example that is functionally equivalent to the previously shown code looks as follows: .. code-block:: php transactional(function($em) { //... do some work $user = new User; $user->setName('George'); $em->persist($user); }); The difference between ``Connection#transactional($func)`` and ``EntityManager#transactional($func)`` is that the latter abstraction flushes the ``EntityManager`` prior to transaction commit and also closes the ``EntityManager`` properly when an exception occurs (in addition to rolling back the transaction). Exception Handling ~~~~~~~~~~~~~~~~~~ When using implicit transaction demarcation and an exception occurs during ``EntityManager#flush()``, the transaction is automatically rolled back and the ``EntityManager`` closed. When using explicit transaction demarcation and an exception occurs, the transaction should be rolled back immediately and the ``EntityManager`` closed by invoking ``EntityManager#close()`` and subsequently discarded, as demonstrated in the example above. This can be handled elegantly by the control abstractions shown earlier. Note that when catching ``Exception`` you should generally re-throw the exception. If you intend to recover from some exceptions, catch them explicitly in earlier catch blocks (but do not forget to rollback the transaction and close the ``EntityManager`` there as well). All other best practices of exception handling apply similarly (i.e. either log or re-throw, not both, etc.). As a result of this procedure, all previously managed or removed instances of the ``EntityManager`` become detached. The state of the detached objects will be the state at the point at which the transaction was rolled back. The state of the objects is in no way rolled back and thus the objects are now out of synch with the database. The application can continue to use the detached objects, knowing that their state is potentially no longer accurate. If you intend to start another unit of work after an exception has occurred you should do that with a new ``EntityManager``. Locking Support --------------- Doctrine 2 offers support for Pessimistic- and Optimistic-locking strategies natively. This allows to take very fine-grained control over what kind of locking is required for your Entities in your application. Optimistic Locking ~~~~~~~~~~~~~~~~~~ Database transactions are fine for concurrency control during a single request. However, a database transaction should not span across requests, the so-called "user think time". Therefore a long-running "business transaction" that spans multiple requests needs to involve several database transactions. Thus, database transactions alone can no longer control concurrency during such a long-running business transaction. Concurrency control becomes the partial responsibility of the application itself. Doctrine has integrated support for automatic optimistic locking via a version field. In this approach any entity that should be protected against concurrent modifications during long-running business transactions gets a version field that is either a simple number (mapping type: integer) or a timestamp (mapping type: datetime). When changes to such an entity are persisted at the end of a long-running conversation the version of the entity is compared to the version in the database and if they don't match, an ``OptimisticLockException`` is thrown, indicating that the entity has been modified by someone else already. You designate a version field in an entity as follows. In this example we'll use an integer. .. code-block:: php find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion); // do the work $em->flush(); } catch(OptimisticLockException $e) { echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; } Or you can use ``EntityManager#lock()`` to find out: .. code-block:: php find('User', $theEntityId); try { // assert version $em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion); } catch(OptimisticLockException $e) { echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; } Important Implementation Notes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can easily get the optimistic locking workflow wrong if you compare the wrong versions. Say you have Alice and Bob editing a hypothetical blog post: - Alice reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request) - Bob reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request) - Bob updates the headline to "Bar", upgrading the optimistic lock version to 2 (POST Request of a Form) - Alice updates the headline to "Baz", ... (POST Request of a Form) Now at the last stage of this scenario the blog post has to be read again from the database before Alice's headline can be applied. At this point you will want to check if the blog post is still at version 1 (which it is not in this scenario). Using optimistic locking correctly, you *have* to add the version as an additional hidden field (or into the SESSION for more safety). Otherwise you cannot verify the version is still the one being originally read from the database when Alice performed her GET request for the blog post. If this happens you might see lost updates you wanted to prevent with Optimistic Locking. See the example code, The form (GET Request): .. code-block:: php find('BlogPost', 123456); echo ''; echo ''; And the change headline action (POST Request): .. code-block:: php find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion); Pessimistic Locking ~~~~~~~~~~~~~~~~~~~ Doctrine 2 supports Pessimistic Locking at the database level. No attempt is being made to implement pessimistic locking inside Doctrine, rather vendor-specific and ANSI-SQL commands are used to acquire row-level locks. Every Entity can be part of a pessimistic lock, there is no special metadata required to use this feature. However for Pessimistic Locking to work you have to disable the Auto-Commit Mode of your Database and start a transaction around your pessimistic lock use-case using the "Approach 2: Explicit Transaction Demarcation" described above. Doctrine 2 will throw an Exception if you attempt to acquire an pessimistic lock and no transaction is running. Doctrine 2 currently supports two pessimistic lock modes: - Pessimistic Write (``Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE``), locks the underlying database rows for concurrent Read and Write Operations. - Pessimistic Read (``Doctrine\DBAL\LockMode::PESSIMISTIC_READ``), locks other concurrent requests that attempt to update or lock rows in write mode. You can use pessimistic locks in three different scenarios: 1. Using ``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)`` or ``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`` 2. Using ``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)`` or ``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`` 3. Using ``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)`` or ``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`` doctrine2-2.4.8/docs/en/reference/unitofwork-associations.rst000066400000000000000000000053551257105210500243520ustar00rootroot00000000000000Association Updates: Owning Side and Inverse Side ================================================= When mapping bidirectional associations it is important to understand the concept of the owning and inverse sides. The following general rules apply: - Relationships may be bidirectional or unidirectional. - A bidirectional relationship has both an owning side and an inverse side - A unidirectional relationship only has an owning side. - Doctrine will **only** check the owning side of an association for changes. Bidirectional Associations -------------------------- The following rules apply to **bidirectional** associations: - The inverse side has to use the ``mappedBy`` attribute of the OneToOne, OneToMany, or ManyToMany mapping declaration. The mappedBy attribute contains the name of the association-field on the owning side. - The owning side has to use the ``inversedBy`` attribute of the OneToOne, ManyToOne, or ManyToMany mapping declaration. The inversedBy attribute contains the name of the association-field on the inverse-side. - ManyToOne is always the owning side of a bidirectional association. - OneToMany is always the inverse side of a bidirectional association. - The owning side of a OneToOne association is the entity with the table containing the foreign key. - You can pick the owning side of a many-to-many association yourself. Important concepts ------------------ **Doctrine will only check the owning side of an association for changes.** To fully understand this, remember how bidirectional associations are maintained in the object world. There are 2 references on each side of the association and these 2 references both represent the same association but can change independently of one another. Of course, in a correct application the semantics of the bidirectional association are properly maintained by the application developer (that's his responsibility). Doctrine needs to know which of these two in-memory references is the one that should be persisted and which not. This is what the owning/inverse concept is mainly used for. **Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)** The owning side of a bidirectional association is the side Doctrine "looks at" when determining the state of the association, and consequently whether there is anything to do to update the association in the database. .. note:: "Owning side" and "inverse side" are technical concepts of the ORM technology, not concepts of your domain model. What you consider as the owning side in your domain model can be different from what the owning side is for Doctrine. These are unrelated. doctrine2-2.4.8/docs/en/reference/unitofwork.rst000066400000000000000000000153471257105210500216570ustar00rootroot00000000000000Doctrine Internals explained ============================ Object relational mapping is a complex topic and sufficiently understanding how Doctrine works internally helps you use its full power. How Doctrine keeps track of Objects ----------------------------------- Doctrine uses the Identity Map pattern to track objects. Whenever you fetch an object from the database, Doctrine will keep a reference to this object inside its UnitOfWork. The array holding all the entity references is two-levels deep and has the keys "root entity name" and "id". Since Doctrine allows composite keys the id is a sorted, serialized version of all the key columns. This allows Doctrine room for optimizations. If you call the EntityManager and ask for an entity with a specific ID twice, it will return the same instance: .. code-block:: php public function testIdentityMap() { $objectA = $this->entityManager->find('EntityName', 1); $objectB = $this->entityManager->find('EntityName', 1); $this->assertSame($objectA, $objectB) } Only one SELECT query will be fired against the database here. In the second ``EntityManager#find()`` call Doctrine will check the identity map first and doesn't need to make that database roundtrip. Even if you get a proxy object first then fetch the object by the same id you will still end up with the same reference: .. code-block:: php public function testIdentityMapReference() { $objectA = $this->entityManager->getReference('EntityName', 1); // check for proxyinterface $this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA); $objectB = $this->entityManager->find('EntityName', 1); $this->assertSame($objectA, $objectB) } The identity map being indexed by primary keys only allows shortcuts when you ask for objects by primary key. Assume you have the following ``persons`` table: :: id | name ------------- 1 | Benjamin 2 | Bud Take the following example where two consecutive calls are made against a repository to fetch an entity by a set of criteria: .. code-block:: php public function testIdentityMapRepositoryFindBy() { $repository = $this->entityManager->getRepository('Person'); $objectA = $repository->findOneBy(array('name' => 'Benjamin')); $objectB = $repository->findOneBy(array('name' => 'Benjamin')); $this->assertSame($objectA, $objectB); } This query will still return the same references and `$objectA` and `$objectB` are indeed referencing the same object. However when checking your SQL logs you will realize that two queries have been executed against the database. Doctrine only knows objects by id, so a query for different criteria has to go to the database, even if it was executed just before. But instead of creating a second Person object Doctrine first gets the primary key from the row and check if it already has an object inside the UnitOfWork with that primary key. In our example it finds an object and decides to return this instead of creating a new one. The identity map has a second use-case. When you call ``EntityManager#flush`` Doctrine will ask the identity map for all objects that are currently managed. This means you don't have to call ``EntityManager#persist`` over and over again to pass known objects to the EntityManager. This is a NO-OP for known entities, but leads to much code written that is confusing to other developers. The following code WILL update your database with the changes made to the ``Person`` object, even if you did not call ``EntityManager#persist``: .. code-block:: php find("Person", 1); $user->setName("Guilherme"); $entityManager->flush(); How Doctrine Detects Changes ---------------------------- Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI). This means you map php objects into a relational database that don't necessarily know about the database at all. A natural question would now be, "how does Doctrine even detect objects have changed?". For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch an object from the database Doctrine will keep a copy of all the properties and associations inside the UnitOfWork. Because variables in the PHP language are subject to "copy-on-write" the memory usage of a PHP request that only reads objects from the database is the same as if Doctrine did not keep this variable copy. Only if you start changing variables PHP will create new variables internally that consume new memory. Now whenever you call ``EntityManager#flush`` Doctrine will iterate over the Identity Map and for each object compares the original property and association values with the values that are currently set on the object. If changes are detected then the object is queued for a SQL UPDATE operation. Only the fields that actually changed are updated. This process has an obvious performance impact. The larger the size of the UnitOfWork is, the longer this computation takes. There are several ways to optimize the performance of the Flush Operation: - Mark entities as read only. These entities can only be inserted or removed, but are never updated. They are omitted in the changeset calculation. - Temporarily mark entities as read only. If you have a very large UnitOfWork but know that a large set of entities has not changed, just mark them as read only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``. - Flush only a single entity with ``$entityManager->flush($entity)``. - Use :doc:`Change Tracking Policies ` to use more explicit strategies of notifying the UnitOfWork what objects/properties changed. Query Internals --------------- The different ORM Layers ------------------------ Doctrine ships with a set of layers with different responsibilities. This section gives a short explanation of each layer. Hydration ~~~~~~~~~ Responsible for creating a final result from a raw database statement and a result-set mapping object. The developer can choose which kind of result he wishes to be hydrated. Default result-types include: - SQL to Entities - SQL to structured Arrays - SQL to simple scalar result arrays - SQL to a single result variable Hydration to entities and arrays is one of most complex parts of Doctrine algorithm-wise. It can built results with for example: - Single table selects - Joins with n:1 or 1:n cardinality, grouping belonging to the same parent. - Mixed results of objects and scalar values - Hydration of results by a given scalar value as key. Persisters ~~~~~~~~~~ tbr UnitOfWork ~~~~~~~~~~ tbr ResultSetMapping ~~~~~~~~~~~~~~~~ tbr DQL Parser ~~~~~~~~~~ tbr SQLWalker ~~~~~~~~~ tbr EntityManager ~~~~~~~~~~~~~ tbr ClassMetadataFactory ~~~~~~~~~~~~~~~~~~~~ tbr doctrine2-2.4.8/docs/en/reference/working-with-associations.rst000066400000000000000000000542031257105210500245700ustar00rootroot00000000000000Working with Associations ========================= Associations between entities are represented just like in regular object-oriented PHP, with references to other objects or collections of objects. When it comes to persistence, it is important to understand three main things: - The :doc:`concept of owning and inverse sides ` in bidirectional associations. - If an entity is removed from a collection, the association is removed, not the entity itself. A collection of entities always only represents the association to the containing entities, not the entity itself. - Collection-valued :ref:`persistent fields ` have to be instances of the ``Doctrine\Common\Collections\Collection`` interface. Changes to associations in your code are not synchronized to the database directly, but upon calling ``EntityManager#flush()``. To describe all the concepts of working with associations we introduce a specific set of example entities that show all the different flavors of association management in Doctrine. Association Example Entities ---------------------------- We will use a simple comment system with Users and Comments as entities to show examples of association management. See the PHP docblocks of each association in the following example for information about its type and if it's the owning or inverse side. .. code-block:: php commentsRead; } public function setFirstComment(Comment $c) { $this->firstComment = $c; } } The interaction code would then look like in the following snippet (``$em`` here is an instance of the EntityManager): .. code-block:: php find('User', $userId); // unidirectional many to many $comment = $em->find('Comment', $readCommentId); $user->getReadComments()->add($comment); $em->flush(); // unidirectional many to one $myFirstComment = new Comment(); $user->setFirstComment($myFirstComment); $em->persist($myFirstComment); $em->flush(); In the case of bi-directional associations you have to update the fields on both sides: .. code-block:: php commentsAuthored; } public function getFavoriteComments() { return $this->favorites; } } class Comment { // ... public function getUserFavorites() { return $this->userFavorites; } public function setAuthor(User $author = null) { $this->author = $author; } } // Many-to-Many $user->getFavorites()->add($favoriteComment); $favoriteComment->getUserFavorites()->add($user); $em->flush(); // Many-To-One / One-To-Many Bidirectional $newComment = new Comment(); $user->getAuthoredComments()->add($newComment); $newComment->setAuthor($user); $em->persist($newComment); $em->flush(); Notice how always both sides of the bidirectional association are updated. The previous unidirectional associations were simpler to handle. Removing Associations --------------------- Removing an association between two entities is similarly straight-forward. There are two strategies to do so, by key and by element. Here are some examples: .. code-block:: php getComments()->removeElement($comment); $comment->setAuthor(null); $user->getFavorites()->removeElement($comment); $comment->getUserFavorites()->removeElement($user); // Remove by Key $user->getComments()->remove($ithComment); $comment->setAuthor(null); You need to call ``$em->flush()`` to make persist these changes in the database permanently. Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently simpler to handle. Also note that if you use type-hinting in your methods, i.e. ``setAddress(Address $address)``, PHP will only allow null values if ``null`` is set as default value. Otherwise setAddress(null) will fail for removing the association. If you insist on type-hinting a typical way to deal with this is to provide a special method, like ``removeAddress()``. This can also provide better encapsulation as it hides the internal meaning of not having an address. When working with collections, keep in mind that a Collection is essentially an ordered map (just like a PHP array). That is why the ``remove`` operation accepts an index/key. ``removeElement`` is a separate method that has O(n) complexity using ``array_search``, where n is the size of the map. .. note:: Since Doctrine always only looks at the owning side of a bidirectional association for updates, it is not necessary for write operations that an inverse collection of a bidirectional one-to-many or many-to-many association is updated. This knowledge can often be used to improve performance by avoiding the loading of the inverse collection. You can also clear the contents of a whole collection using the ``Collections::clear()`` method. You should be aware that using this method can lead to a straight and optimized database delete or update call during the flush operation that is not aware of entities that have been re-added to the collection. Say you clear a collection of tags by calling ``$post->getTags()->clear();`` and then call ``$post->getTags()->add($tag)``. This will not recognize the tag having already been added previously and will consequently issue two separate database calls. Association Management Methods ------------------------------ It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained. The following code shows updates to the previous User and Comment example that encapsulate much of the association management code: .. code-block:: php commentsRead[] = $comment; } public function addComment(Comment $comment) { if (count($this->commentsAuthored) == 0) { $this->setFirstComment($comment); } $this->comments[] = $comment; $comment->setAuthor($this); } private function setFirstComment(Comment $c) { $this->firstComment = $c; } public function addFavorite(Comment $comment) { $this->favorites->add($comment); $comment->addUserFavorite($this); } public function removeFavorite(Comment $comment) { $this->favorites->removeElement($comment); $comment->removeUserFavorite($this); } } class Comment { // .. public function addUserFavorite(User $user) { $this->userFavorites[] = $user; } public function removeUserFavorite(User $user) { $this->userFavorites->removeElement($user); } } You will notice that ``addUserFavorite`` and ``removeUserFavorite`` do not call ``addFavorite`` and ``removeFavorite``, thus the bidirectional association is strictly-speaking still incomplete. However if you would naively add the ``addFavorite`` in ``addUserFavorite``, you end up with an infinite loop, so more work is needed. As you can see, proper bidirectional association management in plain OOP is a non-trivial task and encapsulating all the details inside the classes can be challenging. .. note:: If you want to make sure that your collections are perfectly encapsulated you should not return them from a ``getCollectionName()`` method directly, but call ``$collection->toArray()``. This way a client programmer for the entity cannot circumvent the logic you implement on your entity for association management. For example: .. code-block:: php commentsRead->toArray(); } } This will however always initialize the collection, with all the performance penalties given the size. In some scenarios of large collections it might even be a good idea to completely hide the read access behind methods on the EntityRepository. There is no single, best way for association management. It greatly depends on the requirements of your concrete domain model as well as your preferences. Synchronizing Bidirectional Collections --------------------------------------- In the case of Many-To-Many associations you as the developer have the responsibility of keeping the collections on the owning and inverse side in sync when you apply changes to them. Doctrine can only guarantee a consistent state for the hydration, not for your client code. Using the User-Comment entities from above, a very simple example can show the possible caveats you can encounter: .. code-block:: php getFavorites()->add($favoriteComment); // not calling $favoriteComment->getUserFavorites()->add($user); $user->getFavorites()->contains($favoriteComment); // TRUE $favoriteComment->getUserFavorites()->contains($user); // FALSE There are two approaches to handle this problem in your code: 1. Ignore updating the inverse side of bidirectional collections, BUT never read from them in requests that changed their state. In the next Request Doctrine hydrates the consistent collection state again. 2. Always keep the bidirectional collections in sync through association management methods. Reads of the Collections directly after changes are consistent then. Transitive persistence / Cascade Operations ------------------------------------------- Persisting, removing, detaching and merging individual entities can become pretty cumbersome, especially when a highly interweaved object graph is involved. Therefore Doctrine 2 provides a mechanism for transitive persistence through cascading of these operations. Each association to another entity or a collection of entities can be configured to automatically cascade certain operations. By default, no operations are cascaded. The following cascade options exist: - persist : Cascades persist operations to the associated entities. - remove : Cascades remove operations to the associated entities. - merge : Cascades merge operations to the associated entities. - detach : Cascades detach operations to the associated entities. - all : Cascades persist, remove, merge and detach operations to associated entities. .. note:: Cascade operations are performed in memory. That means collections and related entities are fetched into memory, even if they are still marked as lazy when the cascade operation is about to be performed. However this approach allows entity lifecycle events to be performed for each of these operations. However, pulling objects graph into memory on cascade can cause considerable performance overhead, especially when cascading collections are large. Makes sure to weigh the benefits and downsides of each cascade operation that you define. To rely on the database level cascade operations for the delete operation instead, you can configure each join column with the **onDelete** option. See the respective mapping driver chapters for more information. The following example is an extension to the User-Comment example of this chapter. Suppose in our application a user is created whenever he writes his first comment. In this case we would use the following code: .. code-block:: php addComment($myFirstComment); $em->persist($user); $em->persist($myFirstComment); $em->flush(); Even if you *persist* a new User that contains our new Comment this code would fail if you removed the call to ``EntityManager#persist($myFirstComment)``. Doctrine 2 does not cascade the persist operation to all nested entities that are new as well. More complicated is the deletion of all of a user's comments when he is removed from the system: .. code-block:: php $user = $em->find('User', $deleteUserId); foreach ($user->getAuthoredComments() AS $comment) { $em->remove($comment); } $em->remove($user); $em->flush(); Without the loop over all the authored comments Doctrine would use an UPDATE statement only to set the foreign key to NULL and only the User would be deleted from the database during the flush()-Operation. To have Doctrine handle both cases automatically we can change the ``User#commentsAuthored`` property to cascade both the "persist" and the "remove" operation. .. code-block:: php addresses = new ArrayCollection(); } public function newStandingData(StandingData $sd) { $this->standingData = $sd; } public function removeAddress($pos) { unset($this->addresses[$pos]); } } Now two examples of what happens when you remove the references: .. code-block:: php find("Addressbook\Contact", $contactId); $contact->newStandingData(new StandingData("Firstname", "Lastname", "Street")); $contact->removeAddress(1); $em->flush(); In this case you have not only changed the ``Contact`` entity itself but you have also removed the references for standing data and as well as one address reference. When flush is called not only are the references removed but both the old standing data and the one address entity are also deleted from the database. Filtering Collections --------------------- .. filtering-collections: Collections have a filtering API that allows to slice parts of data from a collection. If the collection has not been loaded from the database yet, the filtering API can work on the SQL level to make optimized access to large collections. .. code-block:: php find('Group', $groupId); $userCollection = $group->getUsers(); $criteria = Criteria::create() ->where(Criteria::expr()->eq("birthday", "1982-02-17")) ->orderBy(array("username" => "ASC")) ->setFirstResult(0) ->setMaxResults(20) ; $birthdayUsers = $userCollection->matching($criteria); .. tip:: You can move the access of slices of collections into dedicated methods of an entity. For example ``Group#getTodaysBirthdayUsers()``. The Criteria has a limited matching language that works both on the SQL and on the PHP collection level. This means you can use collection matching interchangeably, independent of in-memory or sql-backed collections. .. code-block:: php find('CMS\Article', 1234); $article->setHeadline('Hello World dude!'); $article2 = $entityManager->find('CMS\Article', 1234); echo $article2->getHeadline(); In this case the Article is accessed from the entity manager twice, but modified in between. Doctrine 2 realizes this and will only ever give you access to one instance of the Article with ID 1234, no matter how often do you retrieve it from the EntityManager and even no matter what kind of Query method you are using (find, Repository Finder or DQL). This is called "Identity Map" pattern, which means Doctrine keeps a map of each entity and ids that have been retrieved per PHP request and keeps returning you the same instances. In the previous example the echo prints "Hello World dude!" to the screen. You can even verify that ``$article`` and ``$article2`` are indeed pointing to the same instance by running the following code: .. code-block:: php comments = new ArrayCollection(); } public function getAuthor() { return $this->author; } public function getComments() { return $this->comments; } } $article = $em->find('Article', 1); This code only retrieves the ``Article`` instance with id 1 executing a single SELECT statement against the user table in the database. You can still access the associated properties author and comments and the associated objects they contain. This works by utilizing the lazy loading pattern. Instead of passing you back a real Author instance and a collection of comments Doctrine will create proxy instances for you. Only if you access these proxies for the first time they will go through the EntityManager and load their state from the database. This lazy-loading process happens behind the scenes, hidden from your code. See the following code: .. code-block:: php find('Article', 1); // accessing a method of the user instance triggers the lazy-load echo "Author: " . $article->getAuthor()->getName() . "\n"; // Lazy Loading Proxies pass instanceof tests: if ($article->getAuthor() instanceof User) { // a User Proxy is a generated "UserProxy" class } // accessing the comments as an iterator triggers the lazy-load // retrieving ALL the comments of this article from the database // using a single SELECT statement foreach ($article->getComments() AS $comment) { echo $comment->getText() . "\n\n"; } // Article::$comments passes instanceof tests for the Collection interface // But it will NOT pass for the ArrayCollection interface if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) { echo "This will always be true!"; } A slice of the generated proxy classes code looks like the following piece of code. A real proxy class override ALL public methods along the lines of the ``getName()`` method shown below: .. code-block:: php _load(); return parent::getName(); } // .. other public methods of User } .. warning:: Traversing the object graph for parts that are lazy-loaded will easily trigger lots of SQL queries and will perform badly if used to heavily. Make sure to use DQL to fetch-join all the parts of the object-graph that you need as efficiently as possible. Persisting entities ------------------- An entity can be made persistent by passing it to the ``EntityManager#persist($entity)`` method. By applying the persist operation on some entity, that entity becomes MANAGED, which means that its persistence is from now on managed by an EntityManager. As a result the persistent state of such an entity will subsequently be properly synchronized with the database when ``EntityManager#flush()`` is invoked. .. note:: Invoking the ``persist`` method on an entity does NOT cause an immediate SQL INSERT to be issued on the database. Doctrine applies a strategy called "transactional write-behind", which means that it will delay most SQL commands until ``EntityManager#flush()`` is invoked which will then issue all necessary SQL statements to synchronize your objects with the database in the most efficient way and a single, short transaction, taking care of maintaining referential integrity. Example: .. code-block:: php setName('Mr.Right'); $em->persist($user); $em->flush(); .. note:: Generated entity identifiers / primary keys are guaranteed to be available after the next successful flush operation that involves the entity in question. You can not rely on a generated identifier to be available directly after invoking ``persist``. The inverse is also true. You can not rely on a generated identifier being not available after a failed flush operation. The semantics of the persist operation, applied on an entity X, are as follows: - If X is a new entity, it becomes managed. The entity X will be entered into the database as a result of the flush operation. - If X is a preexisting managed entity, it is ignored by the persist operation. However, the persist operation is cascaded to entities referenced by X, if the relationships from X to these other entities are mapped with cascade=PERSIST or cascade=ALL (see "Transitive Persistence"). - If X is a removed entity, it becomes managed. - If X is a detached entity, an exception will be thrown on flush. Removing entities ----------------- An entity can be removed from persistent storage by passing it to the ``EntityManager#remove($entity)`` method. By applying the ``remove`` operation on some entity, that entity becomes REMOVED, which means that its persistent state will be deleted once ``EntityManager#flush()`` is invoked. .. note:: Just like ``persist``, invoking ``remove`` on an entity does NOT cause an immediate SQL DELETE to be issued on the database. The entity will be deleted on the next invocation of ``EntityManager#flush()`` that involves that entity. This means that entities scheduled for removal can still be queried for and appear in query and collection results. See the section on :ref:`Database and UnitOfWork Out-Of-Sync ` for more information. Example: .. code-block:: php remove($user); $em->flush(); The semantics of the remove operation, applied to an entity X are as follows: - If X is a new entity, it is ignored by the remove operation. However, the remove operation is cascaded to entities referenced by X, if the relationship from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). - If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). - If X is a detached entity, an InvalidArgumentException will be thrown. - If X is a removed entity, it is ignored by the remove operation. - A removed entity X will be removed from the database as a result of the flush operation. After an entity has been removed its in-memory state is the same as before the removal, except for generated identifiers. Removing an entity will also automatically delete any existing records in many-to-many join tables that link this entity. The action taken depends on the value of the ``@joinColumn`` mapping attribute "onDelete". Either Doctrine issues a dedicated ``DELETE`` statement for records of each join table or it depends on the foreign key semantics of onDelete="CASCADE". Deleting an object with all its associated objects can be achieved in multiple ways with very different performance impacts. 1. If an association is marked as ``CASCADE=REMOVE`` Doctrine 2 will fetch this association. If its a Single association it will pass this entity to ´EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()\`. In both cases the cascade remove semantics are applied recursively. For large object graphs this removal strategy can be very costly. 2. Using a DQL ``DELETE`` statement allows you to delete multiple entities of a type with a single command and without hydrating these entities. This can be very efficient to delete large object graphs from the database. 3. Using foreign key semantics ``onDelete="CASCADE"`` can force the database to remove all associated objects internally. This strategy is a bit tricky to get right but can be very powerful and fast. You should be aware however that using strategy 1 (``CASCADE=REMOVE``) completely by-passes any foreign key ``onDelete=CASCADE`` option, because Doctrine will fetch and remove all associated entities explicitly nevertheless. Detaching entities ------------------ An entity is detached from an EntityManager and thus no longer managed by invoking the ``EntityManager#detach($entity)`` method on it or by cascading the detach operation to it. Changes made to the detached entity, if any (including removal of the entity), will not be synchronized to the database after the entity has been detached. Doctrine will not hold on to any references to a detached entity. Example: .. code-block:: php detach($entity); The semantics of the detach operation, applied to an entity X are as follows: - If X is a managed entity, the detach operation causes it to become detached. The detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X. - If X is a new or detached entity, it is ignored by the detach operation. - If X is a removed entity, the detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X. There are several situations in which an entity is detached automatically without invoking the ``detach`` method: - When ``EntityManager#clear()`` is invoked, all entities that are currently managed by the EntityManager instance become detached. - When serializing an entity. The entity retrieved upon subsequent unserialization will be detached (This is the case for all entities that are serialized and stored in some cache, i.e. when using the Query Result Cache). The ``detach`` operation is usually not as frequently needed and used as ``persist`` and ``remove``. Merging entities ---------------- Merging entities refers to the merging of (usually detached) entities into the context of an EntityManager so that they become managed again. To merge the state of an entity into an EntityManager use the ``EntityManager#merge($entity)`` method. The state of the passed entity will be merged into a managed copy of this entity and this copy will subsequently be returned. Example: .. code-block:: php merge($detachedEntity); // $entity now refers to the fully managed copy returned by the merge operation. // The EntityManager $em now manages the persistence of $entity as usual. .. note:: When you want to serialize/unserialize entities you have to make all entity properties protected, never private. The reason for this is, if you serialize a class that was a proxy instance before, the private variables won't be serialized and a PHP Notice is thrown. The semantics of the merge operation, applied to an entity X, are as follows: - If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity. - If X is a new entity instance, a new managed copy X' will be created and the state of X is copied onto this managed instance. - If X is a removed entity instance, an InvalidArgumentException will be thrown. - If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been mapped with the cascade element value MERGE or ALL (see "Transitive Persistence"). - For all entities Y referenced by relationships from X having the cascade element value MERGE or ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.) - If X is an entity merged to X', with a reference to another entity Y, where cascade=MERGE or cascade=ALL is not specified, then navigation of the same association from X' yields a reference to a managed object Y' with the same persistent identity as Y. The ``merge`` operation will throw an ``OptimisticLockException`` if the entity being merged uses optimistic locking through a version field and the versions of the entity being merged and the managed copy don't match. This usually means that the entity has been modified while being detached. The ``merge`` operation is usually not as frequently needed and used as ``persist`` and ``remove``. The most common scenario for the ``merge`` operation is to reattach entities to an EntityManager that come from some cache (and are therefore detached) and you want to modify and persist such an entity. .. warning:: If you need to perform multiple merges of entities that share certain subparts of their object-graphs and cascade merge, then you have to call ``EntityManager#clear()`` between the successive calls to ``EntityManager#merge()``. Otherwise you might end up with multiple copies of the "same" object in the database, however with different ids. .. note:: If you load some detached entities from a cache and you do not need to persist or delete them or otherwise make use of them without the need for persistence services there is no need to use ``merge``. I.e. you can simply pass detached objects from a cache directly to the view. Synchronization with the Database --------------------------------- The state of persistent entities is synchronized with the database on flush of an ``EntityManager`` which commits the underlying ``UnitOfWork``. The synchronization involves writing any updates to persistent entities and their relationships to the database. Thereby bidirectional relationships are persisted based on the references held by the owning side of the relationship as explained in the Association Mapping chapter. When ``EntityManager#flush()`` is called, Doctrine inspects all managed, new and removed entities and will perform the following operations. .. _workingobjects_database_uow_outofsync: Effects of Database and UnitOfWork being Out-Of-Sync ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As soon as you begin to change the state of entities, call persist or remove the contents of the UnitOfWork and the database will drive out of sync. They can only be synchronized by calling ``EntityManager#flush()``. This section describes the effects of database and UnitOfWork being out of sync. - Entities that are scheduled for removal can still be queried from the database. They are returned from DQL and Repository queries and are visible in collections. - Entities that are passed to ``EntityManager#persist`` do not turn up in query results. - Entities that have changed will not be overwritten with the state from the database. This is because the identity map will detect the construction of an already existing entity and assumes its the most up to date version. ``EntityManager#flush()`` is never called implicitly by Doctrine. You always have to trigger it manually. Synchronizing New and Managed Entities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The flush operation applies to a managed entity with the following semantics: - The entity itself is synchronized to the database using a SQL UPDATE statement, only if at least one persistent field has changed. - No SQL updates are executed if the entity did not change. The flush operation applies to a new entity with the following semantics: - The entity itself is synchronized to the database using a SQL INSERT statement. For all (initialized) relationships of the new or managed entity the following semantics apply to each associated entity X: - If X is new and persist operations are configured to cascade on the relationship, X will be persisted. - If X is new and no persist operations are configured to cascade on the relationship, an exception will be thrown as this indicates a programming error. - If X is removed and persist operations are configured to cascade on the relationship, an exception will be thrown as this indicates a programming error (X would be re-persisted by the cascade). - If X is detached and persist operations are configured to cascade on the relationship, an exception will be thrown (This is semantically the same as passing X to persist()). Synchronizing Removed Entities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The flush operation applies to a removed entity by deleting its persistent state from the database. No cascade options are relevant for removed entities on flush, the cascade remove option is already executed during ``EntityManager#remove($entity)``. The size of a Unit of Work ~~~~~~~~~~~~~~~~~~~~~~~~~~ The size of a Unit of Work mainly refers to the number of managed entities at a particular point in time. The cost of flushing ~~~~~~~~~~~~~~~~~~~~ How costly a flush operation is, mainly depends on two factors: - The size of the EntityManager's current UnitOfWork. - The configured change tracking policies You can get the size of a UnitOfWork as follows: .. code-block:: php getUnitOfWork()->size(); The size represents the number of managed entities in the Unit of Work. This size affects the performance of flush() operations due to change tracking (see "Change Tracking Policies") and, of course, memory consumption, so you may want to check it from time to time during development. .. note:: Do not invoke ``flush`` after every change to an entity or every single invocation of persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the performance of your application. Instead, form units of work that operate on your objects and call ``flush`` when you are done. While serving a single HTTP request there should be usually no need for invoking ``flush`` more than 0-2 times. Direct access to a Unit of Work ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can get direct access to the Unit of Work by calling ``EntityManager#getUnitOfWork()``. This will return the UnitOfWork instance the EntityManager is currently using. .. code-block:: php getUnitOfWork(); .. note:: Directly manipulating a UnitOfWork is not recommended. When working directly with the UnitOfWork API, respect methods marked as INTERNAL by not using them and carefully read the API documentation. Entity State ~~~~~~~~~~~~ As outlined in the architecture overview an entity can be in one of four possible states: NEW, MANAGED, REMOVED, DETACHED. If you explicitly need to find out what the current state of an entity is in the context of a certain ``EntityManager`` you can ask the underlying ``UnitOfWork``: .. code-block:: php getUnitOfWork()->getEntityState($entity)) { case UnitOfWork::STATE_MANAGED: ... case UnitOfWork::STATE_REMOVED: ... case UnitOfWork::STATE_DETACHED: ... case UnitOfWork::STATE_NEW: ... } An entity is in MANAGED state if it is associated with an ``EntityManager`` and it is not REMOVED. An entity is in REMOVED state after it has been passed to ``EntityManager#remove()`` until the next flush operation of the same EntityManager. A REMOVED entity is still associated with an ``EntityManager`` until the next flush operation. An entity is in DETACHED state if it has persistent state and identity but is currently not associated with an ``EntityManager``. An entity is in NEW state if has no persistent state and identity and is not associated with an ``EntityManager`` (for example those just created via the "new" operator). Querying -------- Doctrine 2 provides the following ways, in increasing level of power and flexibility, to query for persistent objects. You should always start with the simplest one that suits your needs. By Primary Key ~~~~~~~~~~~~~~ The most basic way to query for a persistent object is by its identifier / primary key using the ``EntityManager#find($entityName, $id)`` method. Here is an example: .. code-block:: php find('MyProject\Domain\User', $id); The return value is either the found entity instance or null if no instance could be found with the given identifier. Essentially, ``EntityManager#find()`` is just a shortcut for the following: .. code-block:: php getRepository('MyProject\Domain\User')->find($id); ``EntityManager#getRepository($entityName)`` returns a repository object which provides many ways to retrieve entities of the specified type. By default, the repository instance is of type ``Doctrine\ORM\EntityRepository``. You can also use custom repository classes as shown later. By Simple Conditions ~~~~~~~~~~~~~~~~~~~~ To query for one or more entities based on several conditions that form a logical conjunction, use the ``findBy`` and ``findOneBy`` methods on a repository as follows: .. code-block:: php getRepository('MyProject\Domain\User')->findBy(array('age' => 20)); // All users that are 20 years old and have a surname of 'Miller' $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller')); // A single user by its nickname $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); You can also load by owning side associations through the repository: .. code-block:: php find('MyProject\Domain\Phonenumber', 1234); $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId())); Be careful that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself. The ``EntityRepository#findBy()`` method additionally accepts orderings, limit and offset as second to fourth parameters: .. code-block:: php getRepository('MyProject\Domain\User')->findBy(array('age' => 20), array('name' => 'ASC'), 10, 0); If you pass an array of values Doctrine will convert the query into a WHERE field IN (..) query automatically: .. code-block:: php getRepository('MyProject\Domain\User')->findBy(array('age' => array(20, 30, 40))); // translates roughly to: SELECT * FROM users WHERE age IN (20, 30, 40) An EntityRepository also provides a mechanism for more concise calls through its use of ``__call``. Thus, the following two examples are equivalent: .. code-block:: php getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); // A single user by its nickname (__call magic) $user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb'); By Criteria ~~~~~~~~~~~ .. versionadded:: 2.3 The Repository implement the ``Doctrine\Common\Collections\Selectable`` interface. That means you can build ``Doctrine\Common\Collections\Criteria`` and pass them to the ``matching($criteria)`` method. See the :ref:`Working with Associations: Filtering collections `. By Eager Loading ~~~~~~~~~~~~~~~~ Whenever you query for an entity that has persistent associations and these associations are mapped as EAGER, they will automatically be loaded together with the entity being queried and is thus immediately available to your application. By Lazy Loading ~~~~~~~~~~~~~~~ Whenever you have a managed entity instance at hand, you can traverse and use any associations of that entity that are configured LAZY as if they were in-memory already. Doctrine will automatically load the associated objects on demand through the concept of lazy-loading. By DQL ~~~~~~ The most powerful and flexible method to query for persistent objects is the Doctrine Query Language, an object query language. DQL enables you to query for persistent objects in the language of objects. DQL understands classes, fields, inheritance and associations. DQL is syntactically very similar to the familiar SQL but *it is not SQL*. A DQL query is represented by an instance of the ``Doctrine\ORM\Query`` class. You create a query using ``EntityManager#createQuery($dql)``. Here is a simple example: .. code-block:: php createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30"); $users = $q->getResult(); Note that this query contains no knowledge about the relational schema, only about the object model. DQL supports positional as well as named parameters, many functions, (fetch) joins, aggregates, subqueries and much more. Detailed information about DQL and its syntax as well as the Doctrine class can be found in :doc:`the dedicated chapter `. For programmatically building up queries based on conditions that are only known at runtime, Doctrine provides the special ``Doctrine\ORM\QueryBuilder`` class. More information on constructing queries with a QueryBuilder can be found :doc:`in Query Builder chapter `. By Native Queries ~~~~~~~~~~~~~~~~~ As an alternative to DQL or as a fallback for special SQL statements native queries can be used. Native queries are built by using a hand-crafted SQL query and a ResultSetMapping that describes how the SQL result set should be transformed by Doctrine. More information about native queries can be found in :doc:`the dedicated chapter `. Custom Repositories ~~~~~~~~~~~~~~~~~~~ By default the EntityManager returns a default implementation of ``Doctrine\ORM\EntityRepository`` when you call ``EntityManager#getRepository($entityClass)``. You can overwrite this behaviour by specifying the class name of your own Entity Repository in the Annotation, XML or YAML metadata. In large applications that require lots of specialized DQL queries using a custom repository is one recommended way of grouping these queries in a central location. .. code-block:: php _em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"') ->getResult(); } } You can access your repository now by calling: .. code-block:: php getRepository('MyDomain\Model\User')->getAllAdminUsers(); doctrine2-2.4.8/docs/en/reference/xml-mapping.rst000066400000000000000000000630431257105210500216750ustar00rootroot00000000000000XML Mapping =========== The XML mapping driver enables you to provide the ORM metadata in form of XML documents. The XML driver is backed by an XML Schema document that describes the structure of a mapping document. The most recent version of the XML Schema document is available online at `http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd `_. In order to point to the latest version of the document of a particular stable release branch, just append the release number, i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with XML mapping files is to use an IDE/editor that can provide code-completion based on such an XML Schema document. The following is an outline of a XML mapping document with the proper xmlns/xsi setup for the latest code in trunk. .. code-block:: xml ... The XML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: - Each entity/mapped superclass must get its own dedicated XML mapping document. - The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.). For example an Entity with the fully qualified class-name "MyProject" would require a mapping file "MyProject.Entities.User.dcm.xml" unless the extension is changed. - All mapping documents should get the extension ".dcm.xml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough. - .. code-block:: php setFileExtension('.xml'); It is recommended to put all XML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the XmlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: .. code-block:: php setMetadataDriverImpl($driver); Simplified XML Driver ~~~~~~~~~~~~~~~~~~~~~ The Symfony project sponsored a driver that simplifies usage of the XML Driver. The changes between the original driver are: 1. File Extension is .orm.xml 2. Filenames are shortened, "MyProject\Entities\User" will become User.orm.xml 3. You can add a global file and add multiple entities in this file. Configuration of this client works a little bit different: .. code-block:: php '/path/to/files1', 'OtherProject\Entities' => '/path/to/files2' ); $driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver($namespaces); $driver->setGlobalBasename('global'); // global.orm.xml Example ------- As a quick start, here is a small example document that makes use of several common elements: .. code-block:: xml // Doctrine.Tests.ORM.Mapping.User.dcm.xml Be aware that class-names specified in the XML files should be fully qualified. XML-Element Reference --------------------- The XML-Element reference explains all the tags and attributes that the Doctrine Mapping XSD Schema defines. You should read the Basic-, Association- and Inheritance Mapping chapters to understand what each of this definitions means in detail. Defining an Entity ~~~~~~~~~~~~~~~~~~ Each XML Mapping File contains the definition of one entity, specified as the ```` element as a direct child of the ```` element: .. code-block:: xml Required attributes: - name - The fully qualified class-name of the entity. Optional attributes: - **table** - The Table-Name to be used for this entity. Otherwise the Unqualified Class-Name is used by default. - **repository-class** - The fully qualified class-name of an alternative ``Doctrine\ORM\EntityRepository`` implementation to be used with this entity. - **inheritance-type** - The type of inheritance, defaults to none. A more detailed description follows in the *Defining Inheritance Mappings* section. - **read-only** - (>= 2.1) Specifies that this entity is marked as read only and not considered for change-tracking. Entities of this type can be persisted and removed though. Defining Fields ~~~~~~~~~~~~~~~ Each entity class can contain zero to infinite fields that are managed by Doctrine. You can define them using the ```` element as a children to the ```` element. The field element is only used for primitive types that are not the ID of the entity. For the ID mapping you have to use the ```` element. .. code-block:: xml Required attributes: - name - The name of the Property/Field on the given Entity PHP class. Optional attributes: - type - The ``Doctrine\DBAL\Types\Type`` name, defaults to "string" - column - Name of the column in the database, defaults to the field name. - length - The length of the given type, for use with strings only. - unique - Should this field contain a unique value across the table? Defaults to false. - nullable - Should this field allow NULL as a value? Defaults to false. - version - Should this field be used for optimistic locking? Only works on fields with type integer or datetime. - scale - Scale of a decimal type. - precision - Precision of a decimal type. - column-definition - Optional alternative SQL representation for this column. This definition begin after the field-name and has to specify the complete column definition. Using this feature will turn this field dirty for Schema-Tool update commands at all times. Defining Identity and Generator Strategies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An entity has to have at least one ```` element. For composite keys you can specify more than one id-element, however surrogate keys are recommended for use with Doctrine 2. The Id field allows to define properties of the identifier and allows a subset of the ```` element attributes: .. code-block:: xml Required attributes: - name - The name of the Property/Field on the given Entity PHP class. - type - The ``Doctrine\DBAL\Types\Type`` name, preferably "string" or "integer". Optional attributes: - column - Name of the column in the database, defaults to the field name. Using the simplified definition above Doctrine will use no identifier strategy for this entity. That means you have to manually set the identifier before calling ``EntityManager#persist($entity)``. This is the so called ``ASSIGNED`` strategy. If you want to switch the identifier generation strategy you have to nest a ```` element inside the id-element. This of course only works for surrogate keys. For composite keys you always have to use the ``ASSIGNED`` strategy. .. code-block:: xml The following values are allowed for the ```` strategy attribute: - AUTO - Automatic detection of the identifier strategy based on the preferred solution of the database vendor. - IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs available to Doctrine AFTER the INSERT statement has been executed. - SEQUENCE - Use of a database sequence to retrieve the entity-ids. This is possible before the INSERT statement is executed. If you are using the SEQUENCE strategy you can define an additional element to describe the sequence: .. code-block:: xml Required attributes for ````: - sequence-name - The name of the sequence Optional attributes for ````: - allocation-size - By how much steps should the sequence be incremented when a value is retrieved. Defaults to 1 - initial-value - What should the initial value of the sequence be. **NOTE** If you want to implement a cross-vendor compatible application you have to specify and additionally define the element, if Doctrine chooses the sequence strategy for a platform. Defining a Mapped Superclass ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sometimes you want to define a class that multiple entities inherit from, which itself is not an entity however. The chapter on *Inheritance Mapping* describes a Mapped Superclass in detail. You can define it in XML using the ```` tag. .. code-block:: xml Required attributes: - name - Class name of the mapped superclass. You can nest any number of ```` and unidirectional ```` or ```` associations inside a mapped superclass. Defining Inheritance Mappings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are currently two inheritance persistence strategies that you can choose from when defining entities that inherit from each other. Single Table inheritance saves the fields of the complete inheritance hierarchy in a single table, joined table inheritance creates a table for each entity combining the fields using join conditions. You can specify the inheritance type in the ```` element and then use the ```` and ```` attributes. .. code-block:: xml The allowed values for inheritance-type attribute are ``JOINED`` or ``SINGLE_TABLE``. .. note:: All inheritance related definitions have to be defined on the root entity of the hierarchy. Defining Lifecycle Callbacks ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can define the lifecycle callback methods on your entities using the ```` element: .. code-block:: xml Defining One-To-One Relations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can define One-To-One Relations/Associations using the ```` element. The required and optional attributes depend on the associations being on the inverse or owning side. For the inverse side the mapping is as simple as: .. code-block:: xml Required attributes for inverse One-To-One: - field - Name of the property/field on the entity's PHP class. - target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! - mapped-by - Name of the field on the owning side (here Address entity) that contains the owning side association. For the owning side this mapping would look like: .. code-block:: xml Required attributes for owning One-to-One: - field - Name of the property/field on the entity's PHP class. - target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! Optional attributes for owning One-to-One: - inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference. - orphan-removal - If true, the inverse side entity is always deleted when the owning side entity is. Defaults to false. - fetch - Either LAZY or EAGER, defaults to LAZY. This attribute makes only sense on the owning side, the inverse side *ALWAYS* has to use the ``FETCH`` strategy. The definition for the owning side relies on a bunch of mapping defaults for the join column names. Without the nested ```` element Doctrine assumes to foreign key to be called ``user_id`` on the Address Entities table. This is because the ``MyProject\Address`` entity is the owning side of this association, which means it contains the foreign key. The completed explicitly defined mapping is: .. code-block:: xml Defining Many-To-One Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The many-to-one association is *ALWAYS* the owning side of any bidirectional association. This simplifies the mapping compared to the one-to-one case. The minimal mapping for this association looks like: .. code-block:: xml Required attributes: - field - Name of the property/field on the entity's PHP class. - target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! Optional attributes: - inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference. - orphan-removal - If true the entity on the inverse side is always deleted when the owning side entity is and it is not connected to any other owning side entity anymore. Defaults to false. - fetch - Either LAZY or EAGER, defaults to LAZY. This definition relies on a bunch of mapping defaults with regards to the naming of the join-column/foreign key. The explicitly defined mapping includes a ```` tag nested inside the many-to-one association tag: .. code-block:: xml The join-column attribute ``name`` specifies the column name of the foreign key and the ``referenced-column-name`` attribute specifies the name of the primary key column on the User entity. Defining One-To-Many Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The one-to-many association is *ALWAYS* the inverse side of any association. There exists no such thing as a uni-directional one-to-many association, which means this association only ever exists for bi-directional associations. .. code-block:: xml Required attributes: - field - Name of the property/field on the entity's PHP class. - target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! - mapped-by - Name of the field on the owning side (here Phonenumber entity) that contains the owning side association. Optional attributes: - fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY. - index-by: Index the collection by a field on the target entity. Defining Many-To-Many Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From all the associations the many-to-many has the most complex definition. When you rely on the mapping defaults you can omit many definitions and rely on their implicit values. .. code-block:: xml Required attributes: - field - Name of the property/field on the entity's PHP class. - target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash! Optional attributes: - mapped-by - Name of the field on the owning side that contains the owning side association if the defined many-to-many association is on the inverse side. - inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference. - fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY. - index-by: Index the collection by a field on the target entity. The mapping defaults would lead to a join-table with the name "User\_Group" being created that contains two columns "user\_id" and "group\_id". The explicit definition of this mapping would be: .. code-block:: xml Here both the ```` and ```` tags are necessary to tell Doctrine for which side the specified join-columns apply. These are nested inside a ```` attribute which allows to specify the table name of the many-to-many join-table. Cascade Element ~~~~~~~~~~~~~~~ Doctrine allows cascading of several UnitOfWork operations to related entities. You can specify the cascade operations in the ```` element inside any of the association mapping tags. .. code-block:: xml Besides ```` the following operations can be specified by their respective tags: - ```` - ```` - ```` - ```` Join Column Element ~~~~~~~~~~~~~~~~~~~ In any explicitly defined association mapping you will need the ```` tag. It defines how the foreign key and primary key names are called that are used for joining two entities. Required attributes: - name - The column name of the foreign key. - referenced-column-name - The column name of the associated entities primary key Optional attributes: - unique - If the join column should contain a UNIQUE constraint. This makes sense for Many-To-Many join-columns only to simulate a one-to-many unidirectional using a join-table. - nullable - should the join column be nullable, defaults to true. - on-delete - Foreign Key Cascade action to perform when entity is deleted, defaults to NO ACTION/RESTRICT but can be set to "CASCADE". Defining Order of To-Many Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can require one-to-many or many-to-many associations to be retrieved using an additional ``ORDER BY``. .. code-block:: xml Defining Indexes or Unique Constraints ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To define additional indexes or unique constraints on the entities table you can use the ```` and ```` elements: .. code-block:: xml You have to specify the column and not the entity-class field names in the index and unique-constraint definitions. Derived Entities ID syntax ~~~~~~~~~~~~~~~~~~~~~~~~~~ If the primary key of an entity contains a foreign key to another entity we speak of a derived entity relationship. You can define this in XML with the "association-key" attribute in the ```` tag. .. code-block:: xml doctrine2-2.4.8/docs/en/reference/yaml-mapping.rst000066400000000000000000000065211257105210500220350ustar00rootroot00000000000000YAML Mapping ============ The YAML mapping driver enables you to provide the ORM metadata in form of YAML documents. The YAML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions: - Each entity/mapped superclass must get its own dedicated YAML mapping document. - The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.). - All mapping documents should get the extension ".dcm.yml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough. .. code-block:: php setFileExtension('.yml'); It is recommended to put all YAML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the YamlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this: .. code-block:: php setMetadataDriverImpl($driver); Simplified YAML Driver ~~~~~~~~~~~~~~~~~~~~~~ The Symfony project sponsored a driver that simplifies usage of the YAML Driver. The changes between the original driver are: - File Extension is .orm.yml - Filenames are shortened, "MyProject\\Entities\\User" will become User.orm.yml - You can add a global file and add multiple entities in this file. Configuration of this client works a little bit different: .. code-block:: php 'MyProject\Entities', '/path/to/files2' => 'OtherProject\Entities' ); $driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver($namespaces); $driver->setGlobalBasename('global'); // global.orm.yml Example ------- As a quick start, here is a small example document that makes use of several common elements: .. code-block:: yaml # Doctrine.Tests.ORM.Mapping.User.dcm.yml Doctrine\Tests\ORM\Mapping\User: type: entity table: cms_users indexes: name_index: columns: [ name ] id: id: type: integer generator: strategy: AUTO fields: name: type: string length: 50 oneToOne: address: targetEntity: Address joinColumn: name: address_id referencedColumnName: id oneToMany: phonenumbers: targetEntity: Phonenumber mappedBy: user cascade: ["persist", "merge"] manyToMany: groups: targetEntity: Group joinTable: name: cms_users_groups joinColumns: user_id: referencedColumnName: id inverseJoinColumns: group_id: referencedColumnName: id lifecycleCallbacks: prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] postPersist: [ doStuffOnPostPersist ] Be aware that class-names specified in the YAML files should be fully qualified. doctrine2-2.4.8/docs/en/toc.rst000066400000000000000000000043101257105210500162630ustar00rootroot00000000000000Welcome to Doctrine 2 ORM's documentation! ========================================== Tutorials --------- .. toctree:: :maxdepth: 1 tutorials/getting-started tutorials/getting-started-database tutorials/getting-started-models tutorials/working-with-indexed-associations tutorials/extra-lazy-associations tutorials/composite-primary-keys tutorials/ordered-associations tutorials/override-field-association-mappings-in-subclasses tutorials/pagination.rst Reference Guide --------------- .. toctree:: :maxdepth: 1 :numbered: reference/architecture reference/installation reference/configuration.rst reference/faq reference/basic-mapping reference/association-mapping reference/inheritance-mapping reference/working-with-objects reference/working-with-associations reference/events reference/unitofwork reference/unitofwork-associations reference/transactions-and-concurrency reference/batch-processing reference/dql-doctrine-query-language reference/query-builder reference/native-sql reference/change-tracking-policies reference/partial-objects reference/xml-mapping reference/yaml-mapping reference/annotations-reference reference/php-mapping reference/caching reference/improving-performance reference/tools reference/metadata-drivers reference/best-practices reference/limitations-and-known-issues reference/filters.rst reference/namingstrategy.rst reference/advanced-configuration.rst Cookbook -------- .. toctree:: :maxdepth: 1 cookbook/aggregate-fields cookbook/custom-mapping-types cookbook/decorator-pattern cookbook/dql-custom-walkers cookbook/dql-user-defined-functions cookbook/implementing-arrayaccess-for-domain-objects cookbook/implementing-the-notify-changetracking-policy cookbook/implementing-wakeup-or-clone cookbook/integrating-with-codeigniter cookbook/resolve-target-entity-listener cookbook/sql-table-prefixes cookbook/strategy-cookbook-introduction cookbook/validation-of-entities cookbook/working-with-datetime cookbook/mysql-enums cookbook/advanced-field-value-conversion-using-custom-mapping-types cookbook/entities-in-session doctrine2-2.4.8/docs/en/tutorials/000077500000000000000000000000001257105210500167745ustar00rootroot00000000000000doctrine2-2.4.8/docs/en/tutorials/composite-primary-keys.rst000066400000000000000000000255031257105210500241670ustar00rootroot00000000000000Composite and Foreign Keys as Primary Key ========================================= .. versionadded:: 2.1 Doctrine 2 supports composite primary keys natively. Composite keys are a very powerful relational database concept and we took good care to make sure Doctrine 2 supports as many of the composite primary key use-cases. For Doctrine 2.0 composite keys of primitive data-types are supported, for Doctrine 2.1 even foreign keys as primary keys are supported. This tutorial shows how the semantics of composite primary keys work and how they map to the database. General Considerations ~~~~~~~~~~~~~~~~~~~~~~ Every entity with a composite key cannot use an id generator other than "ASSIGNED". That means the ID fields have to have their values set before you call ``EntityManager#persist($entity)``. Primitive Types only ~~~~~~~~~~~~~~~~~~~~ Even in version 2.0 you can have composite keys as long as they only consist of the primitive types ``integer`` and ``string``. Suppose you want to create a database of cars and use the model-name and year of production as primary keys: .. configuration-block:: .. code-block:: php name = $name; $this->year = $year; } public function getModelName() { return $this->name; } public function getYearOfProduction() { return $this->year; } } .. code-block:: xml .. code-block:: yaml VehicleCatalogue\Model\Car: type: entity id: name: type: string year: type: integer Now you can use this entity: .. code-block:: php persist($car); $em->flush(); And for querying you can use arrays to both DQL and EntityRepositories: .. code-block:: php find("VehicleCatalogue\Model\Car", array("name" => "Audi A8", "year" => 2010)); $dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1"; $audi = $em->createQuery($dql) ->setParameter(1, array("name" => "Audi A8", "year" => 2010)) ->getSingleResult(); You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name`` and to ``year`` to the related entities. .. note:: This example shows how you can nicely solve the requirement for existing values before ``EntityManager#persist()``: By adding them as mandatory values for the constructor. Identity through foreign Entities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. note:: Identity through foreign entities is only supported with Doctrine 2.1 There are tons of use-cases where the identity of an Entity should be determined by the entity of one or many parent entities. - Dynamic Attributes of an Entity (for example Article). Each Article has many attributes with primary key "article_id" and "attribute_name". - Address object of a Person, the primary key of the address is "user_id". This is not a case of a composite primary key, but the identity is derived through a foreign entity and a foreign key. - Join Tables with metadata can be modelled as Entity, for example connections between two articles with a little description and a score. The semantics of mapping identity through foreign entities are easy: - Only allowed on Many-To-One or One-To-One associations. - Plug an ``@Id`` annotation onto every association. - Set an attribute ``association-key`` with the field name of the association in XML. - Set a key ``associationKey:`` with the field name of the association in YAML. Use-Case 1: Dynamic Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We keep up the example of an Article with arbitrary attributes, the mapping looks like this: .. configuration-block:: .. code-block:: php attributes[$name] = new ArticleAttribute($name, $value, $this); } } /** * @Entity */ class ArticleAttribute { /** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */ private $article; /** @Id @Column(type="string") */ private $attribute; /** @Column(type="string") */ private $value; public function __construct($name, $value, $article) { $this->attribute = $name; $this->value = $value; $this->article = $article; } } .. code-block:: xml .. code-block:: yaml Application\Model\ArticleAttribute: type: entity id: article: associationKey: true attribute: type: string fields: value: type: string manyToOne: article: targetEntity: Article inversedBy: attributes Use-Case 2: Simple Derived Identity ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sometimes you have the requirement that two objects are related by a One-To-One association and that the dependent class should re-use the primary key of the class it depends on. One good example for this is a user-address relationship: .. configuration-block:: .. code-block:: php customer = $customer; $this->items = new ArrayCollection(); $this->created = new \DateTime("now"); } } /** @Entity */ class Product { /** @Id @Column(type="integer") @GeneratedValue */ private $id; /** @Column(type="string") */ private $name; /** @Column(type="decimal") */ private $currentPrice; public function getCurrentPrice() { return $this->currentPrice; } } /** @Entity */ class OrderItem { /** @Id @ManyToOne(targetEntity="Order") */ private $order; /** @Id @ManyToOne(targetEntity="Product") */ private $product; /** @Column(type="integer") */ private $amount = 1; /** @Column(type="decimal") */ private $offeredPrice; public function __construct(Order $order, Product $product, $amount = 1) { $this->order = $order; $this->product = $product; $this->offeredPrice = $product->getCurrentPrice(); } } Performance Considerations ~~~~~~~~~~~~~~~~~~~~~~~~~~ Using composite keys always comes with a performance hit compared to using entities with a simple surrogate key. This performance impact is mostly due to additional PHP code that is necessary to handle this kind of keys, most notably when using derived identifiers. On the SQL side there is not much overhead as no additional or unexpected queries have to be executed to manage entities with derived foreign keys. doctrine2-2.4.8/docs/en/tutorials/extra-lazy-associations.rst000066400000000000000000000063211257105210500243250ustar00rootroot00000000000000Extra Lazy Associations ======================= .. versionadded:: 2.1 In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundreds of comments. In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This can lead to pretty serious performance problems, if your associations contain several hundreds or thousands of entities. With Doctrine 2.1 a feature called **Extra Lazy** is introduced for associations. Associations are marked as **Lazy** by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection: - ``Collection#contains($entity)`` - ``Collection#count()`` - ``Collection#slice($offset, $length = null)`` For each of this three methods the following semantics apply: - For each call, if the Collection is not yet loaded, issue a straight SELECT statement against the database. - For each call, if the collection is already loaded, fallback to the default functionality for lazy collections. No additional SELECT statements are executed. Additionally even with Doctrine 2.0 the following methods do not trigger the collection load: - ``Collection#add($entity)`` - ``Collection#offsetSet($key, $entity)`` - ArrayAccess with no specific key ``$coll[] = $entity``, it does not work when setting specific keys like ``$coll[0] = $entity``. With extra lazy collections you can now not only add entities to large collections but also paginate them easily using a combination of ``count`` and ``slice``. Enabling Extra-Lazy Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The mapping configuration is simple. Instead of using the default value of ``fetch="LAZY"`` you have to switch to extra lazy as shown in these examples: .. configuration-block:: .. code-block:: php .. code-block:: yaml Doctrine\Tests\Models\CMS\CmsGroup: type: entity # ... manyToMany: users: targetEntity: CmsUser mappedBy: groups fetch: EXTRA_LAZY doctrine2-2.4.8/docs/en/tutorials/getting-started-database.rst000066400000000000000000000023301257105210500243730ustar00rootroot00000000000000Getting Started: Database First =============================== .. note:: *Development Workflows* When you :doc:`Code First `, you start with developing Objects and then map them onto your database. When you :doc:`Model First `, you are modelling your application using tools (for example UML) and generate database schema and PHP code from this model. When you have a :doc:`Database First `, you already have a database schema and generate the corresponding PHP code from it. .. note:: This getting started guide is in development. Development of new applications often starts with an existing database schema. When the database schema is the starting point for your application, then development is said to use the *Database First* approach to Doctrine. In this workflow you would modify the database schema first and then regenerate the PHP code to use with this schema. You need a flexible code-generator for this task and up to Doctrine 2.2, the code generator hasn't been flexible enough to achieve this. We spinned off a subproject, Doctrine CodeGenerator, that will fill this gap and allow you to do *Database First* development. doctrine2-2.4.8/docs/en/tutorials/getting-started-models.rst000066400000000000000000000021261257105210500241150ustar00rootroot00000000000000Getting Started: Model First ============================ .. note:: *Development Workflows* When you :doc:`Code First `, you start with developing Objects and then map them onto your database. When you :doc:`Model First `, you are modelling your application using tools (for example UML) and generate database schema and PHP code from this model. When you have a :doc:`Database First `, then you already have a database schema and generate the corresponding PHP code from it. .. note:: This getting started guide is in development. There are applications when you start with a high-level description of the model using modelling tools such as UML. Modelling tools could also be Excel, XML or CSV files that describe the model in some structured way. If your application is using a modelling tool, then the development workflow is said to be a *Model First* approach to Doctrine2. In this workflow you always change the model description and then regenerate both PHP code and database schema from this model. doctrine2-2.4.8/docs/en/tutorials/getting-started.rst000066400000000000000000001367341257105210500226510ustar00rootroot00000000000000Getting Started with Doctrine ============================= This guide covers getting started with the Doctrine ORM. After working through the guide you should know: - How to install and configure Doctrine by connecting it to a database - Mapping PHP objects to database tables - Generating a database schema from PHP objects - Using the ``EntityManager`` to insert, update, delete and find objects in the database. Guide Assumptions ----------------- This guide is designed for beginners that haven't worked with Doctrine ORM before. There are some prerequesites for the tutorial that have to be installed: - PHP 5.3.3 or above - Composer Package Manager (`Install Composer `_) The code of this tutorial is `available on Github `_. .. note:: This tutorial assumes you work with Doctrine 2.4 and above. Some of the code will not work with lower versions. What is Doctrine? ----------------- Doctrine 2 is an `object-relational mapper (ORM) `_ for PHP 5.3.3+ that provides transparent persistence for PHP objects. It uses the Data Mapper pattern at the heart, aiming for a complete separation of your domain/business logic from the persistence in a relational database management system. The benefit of Doctrine for the programmer is the ability to focus on the object-oriented business logic and worry about persistence only as a secondary problem. This doesn't mean persistence is downplayed by Doctrine 2, however it is our belief that there are considerable benefits for object-oriented programming if persistence and entities are kept separated. What are Entities? ~~~~~~~~~~~~~~~~~~ Entities are PHP Objects that can be identified over many requests by a unique identifier or primary key. These classes don't need to extend any abstract base class or interface. An entity class must not be final or contain final methods. Additionally it must not implement **clone** nor **wakeup** or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`. An entity contains persistable properties. A persistable property is an instance variable of the entity that is saved into and retrieved from the database by Doctrine's data mapping capabilities. An Example Model: Bug Tracker ----------------------------- For this Getting Started Guide for Doctrine we will implement the Bug Tracker domain model from the `Zend\_Db\_Table `_ documentation. Reading their documentation we can extract the requirements: - A Bugs has a description, creation date, status, reporter and engineer - A bug can occur on different products (platforms) - Products have a name. - Bug Reporter and Engineers are both Users of the System. - A user can create new bugs. - The assigned engineer can close a bug. - A user can see all his reported or assigned bugs. - Bugs can be paginated through a list-view. Setup Project ------------- Create a new empty folder for this tutorial project, for example ``doctrine2-tutorial`` and create a new file ``composer.json`` with the following contents: :: { "require": { "doctrine/orm": "2.*", "symfony/yaml": "2.*" }, "autoload": { "psr-0": {"": "src/"} } } Install Doctrine using the Composer Dependency Management tool, by calling: :: $ composer install This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM, Symfony YAML and Symfony Console. Both Symfony dependencies are optional but will be used in this tutorial. You can prepare the directory structure: :: project |-- composer.json |-- config | |-- xml | `-- yaml `-- src Obtaining the EntityManager --------------------------- Doctrine's public interface is the EntityManager, it provides the access point to the complete lifecycle management of your entities and transforms entities from and back to persistence. You have to configure and create it to use your entities with Doctrine 2. I will show the configuration steps and then discuss them step by step: .. code-block:: php 'pdo_sqlite', 'path' => __DIR__ . '/db.sqlite', ); // obtaining the entity manager $entityManager = EntityManager::create($conn, $config); The first require statement sets up the autoloading capabilities of Doctrine using the Composer autoload. The second block consists of the instantiation of the ORM ``Configuration`` object using the Setup helper. It assumes a bunch of defaults that you don't have to bother about for now. You can read up on the configuration details in the :doc:`reference chapter on configuration <../reference/configuration>`. The third block shows the configuration options required to connect to a database, in my case a file-based sqlite database. All the configuration options for all the shipped drivers are given in the `DBAL Configuration section of the manual `_. The last block shows how the ``EntityManager`` is obtained from a factory method. Generating the Database Schema ------------------------------ Now that we have defined the Metadata Mappings and bootstrapped the EntityManager we want to generate the relational database schema from it. Doctrine has a Command-Line-Interface that allows you to access the SchemaTool, a component that generates the required tables to work with the metadata. For the command-line tool to work a cli-config.php file has to be present in the project root directory, where you will execute the doctrine command. Its a fairly simple file: .. code-block:: php id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } } Note how the properties have getter and setter methods defined except ``$id``. To access data from entities Doctrine 2 uses the Reflection API, so it is possible for Doctrine to access the value of ``$id``. You don't have to take Doctrine into account when designing access to the state of your objects. The next step for persistence with Doctrine is to describe the structure of the ``Product`` entity to Doctrine using a metadata language. The metadata language describes how entities, their properties and references should be persisted and what constraints should be applied to them. Metadata for entities are configured using a XML, YAML or Docblock Annotations. This Getting Started Guide will show the mappings for all Mapping Drivers. References in the text will be made to the XML mapping. .. configuration-block:: .. code-block:: php .. code-block:: yaml # config/yaml/Product.dcm.yml Product: type: entity table: products id: id: type: integer generator: strategy: AUTO fields: name: type: string The top-level ``entity`` definition tag specifies information about the class and table-name. The primitive type ``Product::$name`` is defined as ``field`` attributes. The Id property is defined with the ``id`` tag. The id has a ``generator`` tag nested inside which defines that the primary key generation mechanism automatically uses the database platforms native id generation strategy, for example AUTO INCREMENT in the case of MySql or Sequences in the case of PostgreSql and Oracle. You have to update the database now, because we have a first Entity now: :: $ php vendor/bin/doctrine orm:schema-tool:update --force --dump-sql Specifying both flags ``--force`` and ``-dump-sql`` prints and executes the DDL statements. Now create a new script that will insert products into the database: .. code-block:: php setName($newProductName); $entityManager->persist($product); $entityManager->flush(); echo "Created Product with ID " . $product->getId() . "\n"; Call this script from the command line to see how new products are created: :: $ php create_product.php ORM $ php create_product.php DBAL What is happening here? Using the ``Product`` is pretty standard OOP. The interesting bits are the use of the ``EntityManager`` service. To notify the EntityManager that a new entity should be inserted into the database you have to call ``persist()``. To intiate a transaction to actually perform the insertion, You have to explicitly call ``flush()`` on the ``EntityManager``. This distinction between persist and flush is allows to aggregate all writes (INSERT, UPDATE, DELETE) into one single transaction, which is executed when flush is called. Using this approach the write-performance is significantly better than in a scenario where updates are done for each entity in isolation. Doctrine follows the UnitOfWork pattern which additionally detects all entities that were fetched and have changed during the request. You don't have to keep track of entities yourself, when Doctrine already knowns about them. As a next step we want to fetch a list of all the products. Let's create a new script for this: .. code-block:: php getRepository('Product'); $products = $productRepository->findAll(); foreach ($products as $product) { echo sprintf("-%s\n", $product->getName()); } The ``EntityManager#getRepository()`` method can create a finder object (called repository) for every entity. It is provided by Doctrine and contains some finder methods such as ``findAll()``. Let's continue with displaying the name of a product based on its ID: .. code-block:: php require_once "bootstrap.php"; $id = $argv[1]; $product = $entityManager->find('Product', $id); if ($product === null) { echo "No product found.\n"; exit(1); } echo sprintf("-%s\n", $product->getName()); Updating a product name demonstrates the functionality UnitOfWork of pattern discussed before. We only need to find a product entity and all changes to its properties are written to the database: .. code-block:: php require_once "bootstrap.php"; $id = $argv[1]; $newName = $argv[2]; $product = $entityManager->find('Product', $id); if ($product === null) { echo "Product $id does not exist.\n"; exit(1); } $product->setName($newName); $entityManager->flush(); After calling this script on one of the existing products, you can verify the product name changed by calling the ``show_product.php`` script. Adding Bug and User Entities ---------------------------- We continue with the bug tracker domain, by creating the missing classes ``Bug`` and ``User`` and putting them into ``src/Bug.php`` and ``src/User.php`` respectively. .. code-block:: php id; } public function getDescription() { return $this->description; } public function setDescription($description) { $this->description = $description; } public function setCreated(DateTime $created) { $this->created = $created; } public function getCreated() { return $this->created; } public function setStatus($status) { $this->status = $status; } public function getStatus() { return $this->status; } } .. code-block:: php id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } } All of the properties discussed so far are simple string and integer values, for example the id fields of the entities, their names, description, status and change dates. With just the scalar values this model cannot describe the dynamics that we want. We want to model references between entities. References between objects are foreign keys in the database. You never have to work with the foreign keys directly, only with objects that represent the foreign key through their own identity. For every foreign key you either have a Doctrine ManyToOne or OneToOne association. On the inverse sides of these foreign keys you can have OneToMany associations. Obviously you can have ManyToMany associations that connect two tables with each other through a join table with two foreign keys. Now that you know the basics about references in Doctrine, we can extend the domain model to match the requirements: .. code-block:: php products = new ArrayCollection(); } } .. code-block:: php reportedBugs = new ArrayCollection(); $this->assignedBugs = new ArrayCollection(); } } Whenever an entity is recreated from the database, an Collection implementation of the type Doctrine is injected into your entity instead of an array. Compared to the ArrayCollection this implementation helps the Doctrine ORM understand the changes that have happened to the collection which are noteworthy for persistence. .. warning:: Lazy load proxies always contain an instance of Doctrine's EntityManager and all its dependencies. Therefore a var\_dump() will possibly dump a very large recursive structure which is impossible to render and read. You have to use ``Doctrine\Common\Util\Debug::dump()`` to restrict the dumping to a human readable level. Additionally you should be aware that dumping the EntityManager to a Browser may take several minutes, and the Debug::dump() method just ignores any occurrences of it in Proxy instances. Because we only work with collections for the references we must be careful to implement a bidirectional reference in the domain model. The concept of owning or inverse side of a relation is central to this notion and should always be kept in mind. The following assumptions are made about relations and have to be followed to be able to work with Doctrine 2. These assumptions are not unique to Doctrine 2 but are best practices in handling database relations and Object-Relational Mapping. - Changes to Collections are saved or updated, when the entity on the *owning* side of the collection is saved or updated. - Saving an Entity at the inverse side of a relation never triggers a persist operation to changes to the collection. - In a one-to-one relation the entity holding the foreign key of the related entity on its own database table is *always* the owning side of the relation. - In a many-to-many relation, both sides can be the owning side of the relation. However in a bi-directional many-to-many relation only one is allowed to be. - In a many-to-one relation the Many-side is the owning side by default, because it holds the foreign key. - The OneToMany side of a relation is inverse by default, since the foreign key is saved on the Many side. A OneToMany relation can only be the owning side, if its implemented using a ManyToMany relation with join table and restricting the one side to allow only UNIQUE values per database constraint. .. note:: Consistency of bi-directional references on the inverse side of a relation have to be managed in userland application code. Doctrine cannot magically update your collections to be consistent. In the case of Users and Bugs we have references back and forth to the assigned and reported bugs from a user, making this relation bi-directional. We have to change the code to ensure consistency of the bi-directional reference: .. code-block:: php assignedToBug($this); $this->engineer = $engineer; } public function setReporter($reporter) { $reporter->addReportedBug($this); $this->reporter = $reporter; } public function getEngineer() { return $this->engineer; } public function getReporter() { return $this->reporter; } } .. code-block:: php reportedBugs[] = $bug; } public function assignedToBug($bug) { $this->assignedBugs[] = $bug; } } I chose to name the inverse methods in past-tense, which should indicate that the actual assigning has already taken place and the methods are only used for ensuring consistency of the references. This approach is my personal preference, you can choose whatever method to make this work. You can see from ``User::addReportedBug()`` and ``User::assignedToBug()`` that using this method in userland alone would not add the Bug to the collection of the owning side in ``Bug::$reporter`` or ``Bug::$engineer``. Using these methods and calling Doctrine for persistence would not update the collections representation in the database. Only using ``Bug::setEngineer()`` or ``Bug::setReporter()`` correctly saves the relation information. We also set both collection instance variables to protected, however with PHP 5.3's new features Doctrine is still able to use Reflection to set and get values from protected and private properties. The ``Bug::$reporter`` and ``Bug::$engineer`` properties are Many-To-One relations, which point to a User. In a normalized relational model the foreign key is saved on the Bug's table, hence in our object-relation model the Bug is at the owning side of the relation. You should always make sure that the use-cases of your domain model should drive which side is an inverse or owning one in your Doctrine mapping. In our example, whenever a new bug is saved or an engineer is assigned to the bug, we don't want to update the User to persist the reference, but the Bug. This is the case with the Bug being at the owning side of the relation. Bugs reference Products by an uni-directional ManyToMany relation in the database that points from Bugs to Products. .. code-block:: php products[] = $product; } public function getProducts() { return $this->products; } } We are now finished with the domain model given the requirements. Now we continue adding metadata mappings for the ``User`` and ``Bug`` as we did for the ``Product`` before: .. configuration-block:: .. code-block:: php .. code-block:: yaml # config/yaml/Bug.dcm.yml Bug: type: entity table: bugs id: id: type: integer generator: strategy: AUTO fields: description: type: text created: type: datetime status: type: string manyToOne: reporter: targetEntity: User inversedBy: reportedBugs engineer: targetEntity: User inversedBy: assignedBugs manyToMany: products: targetEntity: Product Here we have the entity, id and primitive type definitions. The column names are used from the Zend\_Db\_Table examples and have different names than the properties on the Bug class. Additionally for the "created" field it is specified that it is of the Type "DATETIME", which translates the YYYY-mm-dd HH:mm:ss Database format into a PHP DateTime instance and back. After the field definitions the two qualified references to the user entity are defined. They are created by the ``many-to-one`` tag. The class name of the related entity has to be specified with the ``target-entity`` attribute, which is enough information for the database mapper to access the foreign-table. Since ``reporter`` and ``engineer`` are on the owning side of a bi-directional relation we also have to specify the ``inversed-by`` attribute. They have to point to the field names on the inverse side of the relationship. We will see in the next example that the ``inversed-by`` attribute has a counterpart ``mapped-by`` which makes that the inverse side. The last missing property is the ``Bug::$products`` collection. It holds all products where the specific bug is occurring in. Again you have to define the ``target-entity`` and ``field`` attributes on the ``many-to-many`` tag. Furthermore you have to specify the details of the many-to-many join-table and its foreign key columns. The definition is rather complex, however relying on the XML auto-completion I got it working easily, although I forget the schema details all the time. The last missing definition is that of the User entity: .. configuration-block:: .. code-block:: php .. code-block:: yaml # config/xml/User.dcm.yml User: type: entity table: users id: id: type: integer generator: strategy: AUTO fields: name: type: string oneToMany: reportedBugs: targetEntity: Bug mappedBy: reporter assignedBugs: targetEntity: Bug mappedBy: engineer Here are some new things to mention about the ``one-to-many`` tags. Remember that we discussed about the inverse and owning side. Now both reportedBugs and assignedBugs are inverse relations, which means the join details have already been defined on the owning side. Therefore we only have to specify the property on the Bug class that holds the owning sides. This example has a fair overview of the most basic features of the metadata definition language. Implementing more Requirements ------------------------------ For starters we need a create user entities: .. code-block:: php setName($newUsername); $entityManager->persist($user); $entityManager->flush(); echo "Created User with ID " . $user->getId() . "\n"; Now call: :: $ php create_user.php beberlei We now have the data to create a bug and the code for this scenario may look like this: .. code-block:: php find("User", $theReporterId); $engineer = $entityManager->find("User", $theDefaultEngineerId); if (!$reporter || !$engineer) { echo "No reporter and/or engineer found for the input.\n"; exit(1); } $bug = new Bug(); $bug->setDescription("Something does not work!"); $bug->setCreated(new DateTime("now")); $bug->setStatus("OPEN"); foreach ($productIds AS $productId) { $product = $entityManager->find("Product", $productId); $bug->assignToProduct($product); } $bug->setReporter($reporter); $bug->setEngineer($engineer); $entityManager->persist($bug); $entityManager->flush(); echo "Your new Bug Id: ".$bug->getId()."\n"; Since we only have one user and product, probably with the ID of 1, we can call this script with: :: php create_bug.php 1 1 1 This is the first contact with the read API of the EntityManager, showing that a call to ``EntityManager#find($name, $id)`` returns a single instance of an entity queried by primary key. Besides this we see the persist + flush pattern again to save the Bug into the database. See how simple relating Bug, Reporter, Engineer and Products is done by using the discussed methods in the "A first prototype" section. The UnitOfWork will detect this relations when flush is called and relate them in the database appropriately. Queries for Application Use-Cases --------------------------------- List of Bugs ~~~~~~~~~~~~ Using the previous examples we can fill up the database quite a bit, however we now need to discuss how to query the underlying mapper for the required view representations. When opening the application, bugs can be paginated through a list-view, which is the first read-only use-case: .. code-block:: php createQuery($dql); $query->setMaxResults(30); $bugs = $query->getResult(); foreach($bugs AS $bug) { echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n"; echo " Reported by: ".$bug->getReporter()->getName()."\n"; echo " Assigned to: ".$bug->getEngineer()->getName()."\n"; foreach($bug->getProducts() AS $product) { echo " Platform: ".$product->getName()."\n"; } echo "\n"; } The DQL Query in this example fetches the 30 most recent bugs with their respective engineer and reporter in one single SQL statement. The console output of this script is then: :: Something does not work! - 02.04.2010 Reported by: beberlei Assigned to: beberlei Platform: My Product .. note:: **Dql is not Sql** You may wonder why we start writing SQL at the beginning of this use-case. Don't we use an ORM to get rid of all the endless hand-writing of SQL? Doctrine introduces DQL which is best described as **object-query-language** and is a dialect of `OQL `_ and similar to `HQL `_ or `JPQL `_. It does not know the concept of columns and tables, but only those of Entity-Class and property. Using the Metadata we defined before it allows for very short distinctive and powerful queries. An important reason why DQL is favourable to the Query API of most ORMs is its similarity to SQL. The DQL language allows query constructs that most ORMs don't, GROUP BY even with HAVING, Sub-selects, Fetch-Joins of nested classes, mixed results with entities and scalar data such as COUNT() results and much more. Using DQL you should seldom come to the point where you want to throw your ORM into the dumpster, because it doesn't support some the more powerful SQL concepts. Besides handwriting DQL you can however also use the ``QueryBuilder`` retrieved by calling ``$entityManager->createQueryBuilder()`` which is a Query Object around the DQL language. As a last resort you can however also use Native SQL and a description of the result set to retrieve entities from the database. DQL boils down to a Native SQL statement and a ``ResultSetMapping`` instance itself. Using Native SQL you could even use stored procedures for data retrieval, or make use of advanced non-portable database queries like PostgreSql's recursive queries. Array Hydration of the Bug List ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In the previous use-case we retrieved the result as their respective object instances. We are not limited to retrieving objects only from Doctrine however. For a simple list view like the previous one we only need read access to our entities and can switch the hydration from objects to simple PHP arrays instead. This can obviously yield considerable performance benefits for read-only requests. Implementing the same list view as before using array hydration we can rewrite our code: .. code-block:: php createQuery($dql); $bugs = $query->getArrayResult(); foreach ($bugs AS $bug) { echo $bug['description'] . " - " . $bug['created']->format('d.m.Y')."\n"; echo " Reported by: ".$bug['reporter']['name']."\n"; echo " Assigned to: ".$bug['engineer']['name']."\n"; foreach($bug['products'] AS $product) { echo " Platform: ".$product['name']."\n"; } echo "\n"; } There is one significant difference in the DQL query however, we have to add an additional fetch-join for the products connected to a bug. The resulting SQL query for this single select statement is pretty large, however still more efficient to retrieve compared to hydrating objects. Find by Primary Key ~~~~~~~~~~~~~~~~~~~ The next Use-Case is displaying a Bug by primary key. This could be done using DQL as in the previous example with a where clause, however there is a convenience method on the ``EntityManager`` that handles loading by primary key, which we have already seen in the write scenarios: .. code-block:: php find("Bug", (int)$theBugId); echo "Bug: ".$bug->getDescription()."\n"; echo "Engineer: ".$bug->getEngineer()->getName()."\n"; The output of the engineers name is fetched from the database! What is happening? Since we only retrieved the bug by primary key both the engineer and reporter are not immediately loaded from the database but are replaced by LazyLoading proxies. These proxies will load behind the scenes, when the first method is called on them. Sample code of this proxy generated code can be found in the specified Proxy Directory, it looks like: .. code-block:: php _load(); return parent::addReportedBug($bug); } public function assignedToBug($bug) { $this->_load(); return parent::assignedToBug($bug); } } See how upon each method call the proxy is lazily loaded from the database? The call prints: :: $ php show_bug.php 1 Bug: Something does not work! Engineer: beberlei Dashboard of the User --------------------- For the next use-case we want to retrieve the dashboard view, a list of all open bugs the user reported or was assigned to. This will be achieved using DQL again, this time with some WHERE clauses and usage of bound parameters: .. code-block:: php createQuery($dql) ->setParameter(1, $theUserId) ->setMaxResults(15) ->getResult(); echo "You have created or assigned to " . count($myBugs) . " open bugs:\n\n"; foreach ($myBugs AS $bug) { echo $bug->getId() . " - " . $bug->getDescription()."\n"; } Number of Bugs -------------- Until now we only retrieved entities or their array representation. Doctrine also supports the retrieval of non-entities through DQL. These values are called "scalar result values" and may even be aggregate values using COUNT, SUM, MIN, MAX or AVG functions. We will need this knowledge to retrieve the number of open bugs grouped by product: .. code-block:: php createQuery($dql)->getScalarResult(); foreach($productBugs as $productBug) { echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n"; } Updating Entities ----------------- There is a single use-case missing from the requirements, Engineers should be able to close a bug. This looks like: .. code-block:: php status = "CLOSE"; } } .. code-block:: php find("Bug", (int)$theBugId); $bug->close(); $entityManager->flush(); When retrieving the Bug from the database it is inserted into the IdentityMap inside the UnitOfWork of Doctrine. This means your Bug with exactly this id can only exist once during the whole request no matter how often you call ``EntityManager#find()``. It even detects entities that are hydrated using DQL and are already present in the Identity Map. When flush is called the EntityManager loops over all the entities in the identity map and performs a comparison between the values originally retrieved from the database and those values the entity currently has. If at least one of these properties is different the entity is scheduled for an UPDATE against the database. Only the changed columns are updated, which offers a pretty good performance improvement compared to updating all the properties. Entity Repositories ------------------- For now we have not discussed how to separate the Doctrine query logic from your model. In Doctrine 1 there was the concept of ``Doctrine_Table`` instances for this separation. The similar concept in Doctrine2 is called Entity Repositories, integrating the `repository pattern `_ at the heart of Doctrine. Every Entity uses a default repository by default and offers a bunch of convenience methods that you can use to query for instances of that Entity. Take for example our Product entity. If we wanted to Query by name, we can use: .. code-block:: php getRepository('Product') ->findOneBy(array('name' => $productName)); The method ``findOneBy()`` takes an array of fields or association keys and the values to match against. If you want to find all entities matching a condition you can use ``findBy()``, for example querying for all closed bugs: .. code-block:: php getRepository('Bug') ->findBy(array('status' => 'CLOSED')); foreach ($bugs AS $bug) { // do stuff } Compared to DQL these query methods are falling short of functionality very fast. Doctrine offers you a convenient way to extend the functionalities of the default ``EntityRepository`` and put all the specialized DQL query logic on it. For this you have to create a subclass of ``Doctrine\ORM\EntityRepository``, in our case a ``BugRepository`` and group all the previously discussed query functionality in it: .. code-block:: php getEntityManager()->createQuery($dql); $query->setMaxResults($number); return $query->getResult(); } public function getRecentBugsArray($number = 30) { $dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ". "JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC"; $query = $this->getEntityManager()->createQuery($dql); $query->setMaxResults($number); return $query->getArrayResult(); } public function getUsersBugs($userId, $number = 15) { $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ". "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC"; return $this->getEntityManager()->createQuery($dql) ->setParameter(1, $userId) ->setMaxResults($number) ->getResult(); } public function getOpenBugsByProduct() { $dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ". "JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id"; return $this->getEntityManager()->createQuery($dql)->getScalarResult(); } } Don't forget to add a `require_once` call for this class to the bootstrap.php To be able to use this query logic through ``$this->getEntityManager()->getRepository('Bug')`` we have to adjust the metadata slightly. .. configuration-block:: .. code-block:: php .. code-block:: yaml Bug: type: entity repositoryClass: BugRepository Now we can remove our query logic in all the places and instead use them through the EntityRepository. As an example here is the code of the first use case "List of Bugs": .. code-block:: php getRepository('Bug')->getRecentBugs(); foreach($bugs AS $bug) { echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n"; echo " Reported by: ".$bug->getReporter()->getName()."\n"; echo " Assigned to: ".$bug->getEngineer()->getName()."\n"; foreach($bug->getProducts() AS $product) { echo " Platform: ".$product->getName()."\n"; } echo "\n"; } Using EntityRepositories you can avoid coupling your model with specific query logic. You can also re-use query logic easily throughout your application. Conclusion ---------- This tutorial is over here, I hope you had fun. Additional content will be added to this tutorial incrementally, topics will include: - More on Association Mappings - Lifecycle Events triggered in the UnitOfWork - Ordering of Collections Additional details on all the topics discussed here can be found in the respective manual chapters. doctrine2-2.4.8/docs/en/tutorials/ordered-associations.rst000066400000000000000000000062221257105210500236510ustar00rootroot00000000000000Ordering To-Many Associations ----------------------------- There are use-cases when you'll want to sort collections when they are retrieved from the database. In userland you do this as long as you haven't initially saved an entity with its associations into the database. To retrieve a sorted collection from the database you can use the ``@OrderBy`` annotation with an collection that specifies an DQL snippet that is appended to all queries with this collection. Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you can specify the ``@OrderBy`` in the following way: .. configuration-block:: .. code-block:: php .. code-block:: yaml User: type: entity manyToMany: groups: orderBy: { 'name': 'ASC' } targetEntity: Group joinTable: name: users_groups joinColumns: user_id: referencedColumnName: id inverseJoinColumns: group_id: referencedColumnName: id The DQL Snippet in OrderBy is only allowed to consist of unqualified, unquoted field names and of an optional ASC/DESC positional statement. Multiple Fields are separated by a comma (,). The referenced field names have to exist on the ``targetEntity`` class of the ``@ManyToMany`` or ``@OneToMany`` annotation. The semantics of this feature can be described as follows. - ``@OrderBy`` acts as an implicit ORDER BY clause for the given fields, that is appended to all the explicitly given ORDER BY items. - All collections of the ordered type are always retrieved in an ordered fashion. - To keep the database impact low, these implicit ORDER BY items are only added to an DQL Query if the collection is fetch joined in the DQL query. Given our previously defined example, the following would not add ORDER BY, since g is not fetch joined: .. code-block:: sql SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10 However the following: .. code-block:: sql SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ...would internally be rewritten to: .. code-block:: sql SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC You can reverse the order with an explicit DQL ORDER BY: .. code-block:: sql SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC ...is internally rewritten to: .. code-block:: sql SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC doctrine2-2.4.8/docs/en/tutorials/override-field-association-mappings-in-subclasses.rst000066400000000000000000000050131257105210500313240ustar00rootroot00000000000000Override Field Association Mappings In Subclasses ------------------------------------------------- Sometimes there is a need to persist entities but override all or part of the mapping metadata. Sometimes also the mapping to override comes from entities using traits where the traits have mapping metadata. This tutorial explains how to override mapping metadata, i.e. attributes and associations metadata in particular. The example here shows the overriding of a class that uses a trait but is similar when extending a base class as shown at the end of this tutorial. Suppose we have a class ExampleEntityWithOverride. This class uses trait ExampleTrait: .. code-block:: php createQuery($dql) ->setFirstResult(0) ->setMaxResults(100); $paginator = new Paginator($query, $fetchJoinCollection = true); $c = count($paginator); foreach ($paginator as $post) { echo $post->getHeadline() . "\n"; } Paginating Doctrine queries is not as simple as you might think in the beginning. If you have complex fetch-join scenarios with one-to-many or many-to-many associations using the "default" LIMIT functionality of database vendors is not sufficient to get the correct results. By default the pagination extension does the following steps to compute the correct result: - Perform a Count query using `DISTINCT` keyword. - Perform a Limit Subquery with `DISTINCT` to find all ids of the entity in from on the current page. - Perform a WHERE IN query to get all results for the current page. This behavior is only necessary if you actually fetch join a to-many collection. You can disable this behavior by setting the ``$fetchJoinCollection`` flag to ``false``; in that case only 2 instead of the 3 queries described are executed. We hope to automate the detection for this in the future. doctrine2-2.4.8/docs/en/tutorials/working-with-indexed-associations.rst000066400000000000000000000236011257105210500262740ustar00rootroot00000000000000Working with Indexed Associations ================================= .. note:: This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series. Doctrine 2 collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in the first version of Doctrine keys retrieved from the database were always numerical unless ``INDEX BY`` was used. Starting with Doctrine 2.1 you can index your collections by a value in the related entity. This is a first step towards full ordered hashmap support through the Doctrine ORM. The feature works like an implicit ``INDEX BY`` for the selected association but has several downsides also: - You have to manage both the key and field if you want to change the index by field value. - On each request the keys are regenerated from the field value not from the previous collection key. - Values of the Index-By keys are never considered during persistence, it only exists for accessing purposes. - Fields that are used for the index by feature **HAVE** to be unique in the database. The behavior for multiple entities with the same index-by field value is undefined. As an example we will design a simple stock exchange list view. The domain consists of the entity ``Stock`` and ``Market`` where each Stock has a symbol and is traded on a single market. Instead of having a numerical list of stocks traded on a market they will be indexed by their symbol, which is unique across all markets. Mapping Indexed Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can map indexed associations by adding: * ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation. * ``index-by`` attribute to any ```` or ```` xml element. * ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files. The code and mappings for the Market entity looks like this: .. configuration-block:: .. code-block:: php name = $name; $this->stocks = new ArrayCollection(); } public function getId() { return $this->id; } public function getName() { return $this->name; } public function addStock(Stock $stock) { $this->stocks[$stock->getSymbol()] = $stock; } public function getStock($symbol) { if (!isset($this->stocks[$symbol])) { throw new \InvalidArgumentException("Symbol is not traded on this market."); } return $this->stocks[$symbol]; } public function getStocks() { return $this->stocks->toArray(); } } .. code-block:: xml .. code-block:: yaml Doctrine\Tests\Models\StockExchange\Market: type: entity id: id: type: integer generator: strategy: AUTO fields: name: type:string oneToMany: stocks: targetEntity: Stock mappedBy: market indexBy: symbol Inside the ``addStock()`` method you can see how we directly set the key of the association to the symbol, so that we can work with the indexed association directly after invoking ``addStock()``. Inside ``getStock($symbol)`` we pick a stock traded on the particular market by symbol. If this stock doesn't exist an exception is thrown. The ``Stock`` entity doesn't contain any special instructions that are new, but for completeness here are the code and mappings for it: .. configuration-block:: .. code-block:: php symbol = $symbol; $this->market = $market; $market->addStock($this); } public function getSymbol() { return $this->symbol; } } .. code-block:: xml .. code-block:: yaml Doctrine\Tests\Models\StockExchange\Stock: type: entity id: id: type: integer generator: strategy: AUTO fields: symbol: type: string manyToOne: market: targetEntity: Market inversedBy: stocks Querying indexed associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now that we defined the stocks collection to be indexed by symbol we can take a look at some code, that makes use of the indexing. First we will populate our database with two example stocks traded on a single market: .. code-block:: php persist($market); $em->persist($stock1); $em->persist($stock2); $em->flush(); This code is not particular interesting since the indexing feature is not yet used. In a new request we could now query for the market: .. code-block:: php find("Doctrine\Tests\Models\StockExchange\Market", $marketId); // Access the stocks by symbol now: $stock = $market->getStock($symbol); echo $stock->getSymbol(); // will print "AAPL" The implementation ``Market::addStock()`` in combination with ``indexBy`` allows to access the collection consistently by the Stock symbol. It does not matter if Stock is managed by Doctrine or not. The same applies to DQL queries: The ``indexBy`` configuration acts as implicit "INDEX BY" to a join association. .. code-block:: php createQuery($dql) ->setParameter(1, $marketId) ->getSingleResult(); // Access the stocks by symbol now: $stock = $market->getStock($symbol); echo $stock->getSymbol(); // will print "AAPL" If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally indexed associations also work with the ``Collection::slice()`` functionality, no matter if marked as LAZY or EXTRA_LAZY. Outlook into the Future ~~~~~~~~~~~~~~~~~~~~~~~ For the inverse side of a many-to-many associations there will be a way to persist the keys and the order as a third and fourth parameter into the join table. This feature is discussed in `DDC-213 `_ This feature cannot be implemented for One-To-Many associations, because they are never the owning side. doctrine2-2.4.8/doctrine-mapping.xsd000066400000000000000000000603311257105210500173770ustar00rootroot00000000000000 doctrine2-2.4.8/lib/000077500000000000000000000000001257105210500141625ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/000077500000000000000000000000001257105210500157315ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/000077500000000000000000000000001257105210500163665ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/AbstractQuery.php000066400000000000000000000604311257105210500216740ustar00rootroot00000000000000. */ namespace Doctrine\ORM; use Doctrine\Common\Util\ClassUtils; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\ORMInvalidArgumentException; /** * Base contract for ORM queries. Base class for Query and NativeQuery. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Konsta Vesterinen */ abstract class AbstractQuery { /* Hydration mode constants */ /** * Hydrates an object graph. This is the default behavior. */ const HYDRATE_OBJECT = 1; /** * Hydrates an array graph. */ const HYDRATE_ARRAY = 2; /** * Hydrates a flat, rectangular result set with scalar values. */ const HYDRATE_SCALAR = 3; /** * Hydrates a single scalar value. */ const HYDRATE_SINGLE_SCALAR = 4; /** * Very simple object hydrator (optimized for performance). */ const HYDRATE_SIMPLEOBJECT = 5; /** * The parameter map of this query. * * @var \Doctrine\Common\Collections\ArrayCollection */ protected $parameters; /** * The user-specified ResultSetMapping to use. * * @var \Doctrine\ORM\Query\ResultSetMapping */ protected $_resultSetMapping; /** * The entity manager used by this query object. * * @var \Doctrine\ORM\EntityManager */ protected $_em; /** * The map of query hints. * * @var array */ protected $_hints = array(); /** * The hydration mode. * * @var integer */ protected $_hydrationMode = self::HYDRATE_OBJECT; /** * @param \Doctrine\DBAL\Cache\QueryCacheProfile */ protected $_queryCacheProfile; /** * Whether or not expire the result cache. * * @var boolean */ protected $_expireResultCache = false; /** * @param \Doctrine\DBAL\Cache\QueryCacheProfile */ protected $_hydrationCacheProfile; /** * Initializes a new instance of a class derived from AbstractQuery. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->_em = $em; $this->parameters = new ArrayCollection(); } /** * Gets the SQL query that corresponds to this query object. * The returned SQL syntax depends on the connection driver that is used * by this query object at the time of this method call. * * @return string SQL query */ abstract public function getSQL(); /** * Retrieves the associated EntityManager of this Query instance. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->_em; } /** * Frees the resources used by the query object. * * Resets Parameters, Parameter Types and Query Hints. * * @return void */ public function free() { $this->parameters = new ArrayCollection(); $this->_hints = array(); } /** * Get all defined parameters. * * @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters. */ public function getParameters() { return $this->parameters; } /** * Gets a query parameter. * * @param mixed $key The key (index or name) of the bound parameter. * * @return mixed The value of the bound parameter. */ public function getParameter($key) { $filteredParameters = $this->parameters->filter( function ($parameter) use ($key) { // Must not be identical because of string to integer conversion return ($key == $parameter->getName()); } ); return count($filteredParameters) ? $filteredParameters->first() : null; } /** * Sets a collection of query parameters. * * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters * * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setParameters($parameters) { // BC compatibility with 2.3- if (is_array($parameters)) { $parameterCollection = new ArrayCollection(); foreach ($parameters as $key => $value) { $parameter = new Query\Parameter($key, $value); $parameterCollection->add($parameter); } $parameters = $parameterCollection; } $this->parameters = $parameters; return $this; } /** * Sets a query parameter. * * @param string|int $key The parameter position or name. * @param mixed $value The parameter value. * @param string|null $type The parameter type. If specified, the given value will be run through * the type conversion of this type. This is usually not needed for * strings and numeric types. * * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setParameter($key, $value, $type = null) { $filteredParameters = $this->parameters->filter( function ($parameter) use ($key) { // Must not be identical because of string to integer conversion return ($key == $parameter->getName()); } ); if (count($filteredParameters)) { $parameter = $filteredParameters->first(); $parameter->setValue($value, $type); return $this; } $parameter = new Query\Parameter($key, $value, $type); $this->parameters->add($parameter); return $this; } /** * Processes an individual parameter value. * * @param mixed $value * * @return array * * @throws ORMInvalidArgumentException */ public function processParameterValue($value) { if (is_array($value)) { foreach ($value as $key => $paramValue) { $paramValue = $this->processParameterValue($paramValue); $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue; } return $value; } if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) { $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value); if ($value === null) { throw ORMInvalidArgumentException::invalidIdentifierBindingEntity(); } } if ($value instanceof Mapping\ClassMetadata) { return $value->name; } return $value; } /** * Sets the ResultSetMapping that should be used for hydration. * * @param \Doctrine\ORM\Query\ResultSetMapping $rsm * * @return \Doctrine\ORM\AbstractQuery */ public function setResultSetMapping(Query\ResultSetMapping $rsm) { $this->translateNamespaces($rsm); $this->_resultSetMapping = $rsm; return $this; } /** * Allows to translate entity namespaces to full qualified names. * * @param Query\ResultSetMapping $rsm * * @return void */ private function translateNamespaces(Query\ResultSetMapping $rsm) { $entityManager = $this->_em; $translate = function ($alias) use ($entityManager) { return $entityManager->getClassMetadata($alias)->getName(); }; $rsm->aliasMap = array_map($translate, $rsm->aliasMap); $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses); } /** * Set a cache profile for hydration caching. * * If no result cache driver is set in the QueryCacheProfile, the default * result cache driver is used from the configuration. * * Important: Hydration caching does NOT register entities in the * UnitOfWork when retrieved from the cache. Never use result cached * entities for requests that also flush the EntityManager. If you want * some form of caching with UnitOfWork registration you should use * {@see AbstractQuery::setResultCacheProfile()}. * * @example * $lifetime = 100; * $resultKey = "abc"; * $query->setHydrationCacheProfile(new QueryCacheProfile()); * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey)); * * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile * * @return \Doctrine\ORM\AbstractQuery */ public function setHydrationCacheProfile(QueryCacheProfile $profile = null) { if ( ! $profile->getResultCacheDriver()) { $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl(); $profile = $profile->setResultCacheDriver($resultCacheDriver); } $this->_hydrationCacheProfile = $profile; return $this; } /** * @return \Doctrine\DBAL\Cache\QueryCacheProfile */ public function getHydrationCacheProfile() { return $this->_hydrationCacheProfile; } /** * Set a cache profile for the result cache. * * If no result cache driver is set in the QueryCacheProfile, the default * result cache driver is used from the configuration. * * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile * * @return \Doctrine\ORM\AbstractQuery */ public function setResultCacheProfile(QueryCacheProfile $profile = null) { if ( ! $profile->getResultCacheDriver()) { $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl(); $profile = $profile->setResultCacheDriver($resultCacheDriver); } $this->_queryCacheProfile = $profile; return $this; } /** * Defines a cache driver to be used for caching result sets and implicitly enables caching. * * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver * * @return \Doctrine\ORM\AbstractQuery * * @throws ORMException */ public function setResultCacheDriver($resultCacheDriver = null) { if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) { throw ORMException::invalidResultCacheDriver(); } $this->_queryCacheProfile = $this->_queryCacheProfile ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver) : new QueryCacheProfile(0, null, $resultCacheDriver); return $this; } /** * Returns the cache driver used for caching result sets. * * @deprecated * * @return \Doctrine\Common\Cache\Cache Cache driver */ public function getResultCacheDriver() { if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) { return $this->_queryCacheProfile->getResultCacheDriver(); } return $this->_em->getConfiguration()->getResultCacheImpl(); } /** * Set whether or not to cache the results of this query and if so, for * how long and which ID to use for the cache entry. * * @param boolean $bool * @param integer $lifetime * @param string $resultCacheId * * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function useResultCache($bool, $lifetime = null, $resultCacheId = null) { if ($bool) { $this->setResultCacheLifetime($lifetime); $this->setResultCacheId($resultCacheId); return $this; } $this->_queryCacheProfile = null; return $this; } /** * Defines how long the result cache will be active before expire. * * @param integer $lifetime How long the cache entry is valid. * * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setResultCacheLifetime($lifetime) { $lifetime = ($lifetime !== null) ? (int) $lifetime : 0; $this->_queryCacheProfile = $this->_queryCacheProfile ? $this->_queryCacheProfile->setLifetime($lifetime) : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl()); return $this; } /** * Retrieves the lifetime of resultset cache. * * @deprecated * * @return integer */ public function getResultCacheLifetime() { return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0; } /** * Defines if the result cache is active or not. * * @param boolean $expire Whether or not to force resultset cache expiration. * * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function expireResultCache($expire = true) { $this->_expireResultCache = $expire; return $this; } /** * Retrieves if the resultset cache is active or not. * * @return boolean */ public function getExpireResultCache() { return $this->_expireResultCache; } /** * @return QueryCacheProfile */ public function getQueryCacheProfile() { return $this->_queryCacheProfile; } /** * Change the default fetch mode of an association for this query. * * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY * * @param string $class * @param string $assocName * @param int $fetchMode * * @return AbstractQuery */ public function setFetchMode($class, $assocName, $fetchMode) { if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) { $fetchMode = Mapping\ClassMetadata::FETCH_LAZY; } $this->_hints['fetchMode'][$class][$assocName] = $fetchMode; return $this; } /** * Defines the processing mode to be used during hydration / result set transformation. * * @param integer $hydrationMode Doctrine processing mode to be used during hydration process. * One of the Query::HYDRATE_* constants. * * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setHydrationMode($hydrationMode) { $this->_hydrationMode = $hydrationMode; return $this; } /** * Gets the hydration mode currently used by the query. * * @return integer */ public function getHydrationMode() { return $this->_hydrationMode; } /** * Gets the list of results for the query. * * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT). * * @param int $hydrationMode * * @return array */ public function getResult($hydrationMode = self::HYDRATE_OBJECT) { return $this->execute(null, $hydrationMode); } /** * Gets the array of results for the query. * * Alias for execute(null, HYDRATE_ARRAY). * * @return array */ public function getArrayResult() { return $this->execute(null, self::HYDRATE_ARRAY); } /** * Gets the scalar results for the query. * * Alias for execute(null, HYDRATE_SCALAR). * * @return array */ public function getScalarResult() { return $this->execute(null, self::HYDRATE_SCALAR); } /** * Get exactly one result or null. * * @param int $hydrationMode * * @return mixed * * @throws NonUniqueResultException */ public function getOneOrNullResult($hydrationMode = null) { $result = $this->execute(null, $hydrationMode); if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { return null; } if ( ! is_array($result)) { return $result; } if (count($result) > 1) { throw new NonUniqueResultException; } return array_shift($result); } /** * Gets the single result of the query. * * Enforces the presence as well as the uniqueness of the result. * * If the result is not unique, a NonUniqueResultException is thrown. * If there is no result, a NoResultException is thrown. * * @param integer $hydrationMode * * @return mixed * * @throws NonUniqueResultException If the query result is not unique. * @throws NoResultException If the query returned no result. */ public function getSingleResult($hydrationMode = null) { $result = $this->execute(null, $hydrationMode); if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { throw new NoResultException; } if ( ! is_array($result)) { return $result; } if (count($result) > 1) { throw new NonUniqueResultException; } return array_shift($result); } /** * Gets the single scalar result of the query. * * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR). * * @return mixed * * @throws QueryException If the query result is not unique. */ public function getSingleScalarResult() { return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR); } /** * Sets a query hint. If the hint name is not recognized, it is silently ignored. * * @param string $name The name of the hint. * @param mixed $value The value of the hint. * * @return \Doctrine\ORM\AbstractQuery */ public function setHint($name, $value) { $this->_hints[$name] = $value; return $this; } /** * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned. * * @param string $name The name of the hint. * * @return mixed The value of the hint or FALSE, if the hint name is not recognized. */ public function getHint($name) { return isset($this->_hints[$name]) ? $this->_hints[$name] : false; } /** * Check if the query has a hint * * @param string $name The name of the hint * * @return bool False if the query does not have any hint */ public function hasHint($name) { return isset($this->_hints[$name]); } /** * Return the key value map of query hints that are currently set. * * @return array */ public function getHints() { return $this->_hints; } /** * Executes the query and returns an IterableResult that can be used to incrementally * iterate over the result. * * @param ArrayCollection|array|null $parameters The query parameters. * @param integer|null $hydrationMode The hydration mode to use. * * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ public function iterate($parameters = null, $hydrationMode = null) { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } if ( ! empty($parameters)) { $this->setParameters($parameters); } $stmt = $this->_doExecute(); return $this->_em->newHydrator($this->_hydrationMode)->iterate( $stmt, $this->_resultSetMapping, $this->_hints ); } /** * Executes the query. * * @param ArrayCollection|array|null $parameters Query parameters. * @param integer|null $hydrationMode Processing mode to be used during the hydration process. * * @return mixed */ public function execute($parameters = null, $hydrationMode = null) { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } if ( ! empty($parameters)) { $this->setParameters($parameters); } $setCacheEntry = function() {}; if ($this->_hydrationCacheProfile !== null) { list($cacheKey, $realCacheKey) = $this->getHydrationCacheId(); $queryCacheProfile = $this->getHydrationCacheProfile(); $cache = $queryCacheProfile->getResultCacheDriver(); $result = $cache->fetch($cacheKey); if (isset($result[$realCacheKey])) { return $result[$realCacheKey]; } if ( ! $result) { $result = array(); } $setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) { $result[$realCacheKey] = $data; $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime()); }; } $stmt = $this->_doExecute(); if (is_numeric($stmt)) { $setCacheEntry($stmt); return $stmt; } $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll( $stmt, $this->_resultSetMapping, $this->_hints ); $setCacheEntry($data); return $data; } /** * Get the result cache id to use to store the result set cache entry. * Will return the configured id if it exists otherwise a hash will be * automatically generated for you. * * @return array ($key, $hash) */ protected function getHydrationCacheId() { $parameters = array(); foreach ($this->getParameters() as $parameter) { $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue()); } $sql = $this->getSQL(); $queryCacheProfile = $this->getHydrationCacheProfile(); $hints = $this->getHints(); $hints['hydrationMode'] = $this->getHydrationMode(); ksort($hints); return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints); } /** * Set the result cache id to use to store the result set cache entry. * If this is not explicitly set by the developer then a hash is automatically * generated for you. * * @param string $id * * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setResultCacheId($id) { $this->_queryCacheProfile = $this->_queryCacheProfile ? $this->_queryCacheProfile->setCacheKey($id) : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl()); return $this; } /** * Get the result cache id to use to store the result set cache entry if set. * * @deprecated * * @return string */ public function getResultCacheId() { return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null; } /** * Executes the query and returns a the resulting Statement object. * * @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results. */ abstract protected function _doExecute(); /** * Cleanup Query resource when clone is called. * * @return void */ public function __clone() { $this->parameters = new ArrayCollection(); $this->_hints = array(); } } doctrine2-2.4.8/lib/Doctrine/ORM/Configuration.php000066400000000000000000000557501257105210500217220ustar00rootroot00000000000000. */ namespace Doctrine\ORM; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\CachedReader; use Doctrine\Common\Annotations\SimpleAnnotationReader; use Doctrine\Common\Cache\ArrayCache; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver; use Doctrine\ORM\Mapping\DefaultEntityListenerResolver; use Doctrine\ORM\Mapping\DefaultNamingStrategy; use Doctrine\ORM\Mapping\DefaultQuoteStrategy; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\Mapping\EntityListenerResolver; use Doctrine\ORM\Mapping\NamingStrategy; use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\Repository\DefaultRepositoryFactory; use Doctrine\ORM\Repository\RepositoryFactory; /** * Configuration container for all configuration options of Doctrine. * It combines all configuration options from DBAL & ORM. * * @since 2.0 * @internal When adding a new configuration option just write a getter/setter pair. * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Configuration extends \Doctrine\DBAL\Configuration { /** * Sets the directory where Doctrine generates any necessary proxy class files. * * @param string $dir * * @return void */ public function setProxyDir($dir) { $this->_attributes['proxyDir'] = $dir; } /** * Gets the directory where Doctrine generates any necessary proxy class files. * * @return string|null */ public function getProxyDir() { return isset($this->_attributes['proxyDir']) ? $this->_attributes['proxyDir'] : null; } /** * Gets a boolean flag that indicates whether proxy classes should always be regenerated * during each script execution. * * @return boolean */ public function getAutoGenerateProxyClasses() { return isset($this->_attributes['autoGenerateProxyClasses']) ? $this->_attributes['autoGenerateProxyClasses'] : true; } /** * Sets a boolean flag that indicates whether proxy classes should always be regenerated * during each script execution. * * @param boolean $bool * * @return void */ public function setAutoGenerateProxyClasses($bool) { $this->_attributes['autoGenerateProxyClasses'] = $bool; } /** * Gets the namespace where proxy classes reside. * * @return string|null */ public function getProxyNamespace() { return isset($this->_attributes['proxyNamespace']) ? $this->_attributes['proxyNamespace'] : null; } /** * Sets the namespace where proxy classes reside. * * @param string $ns * * @return void */ public function setProxyNamespace($ns) { $this->_attributes['proxyNamespace'] = $ns; } /** * Sets the cache driver implementation that is used for metadata caching. * * @param MappingDriver $driverImpl * * @return void * * @todo Force parameter to be a Closure to ensure lazy evaluation * (as soon as a metadata cache is in effect, the driver never needs to initialize). */ public function setMetadataDriverImpl(MappingDriver $driverImpl) { $this->_attributes['metadataDriverImpl'] = $driverImpl; } /** * Adds a new default annotation driver with a correctly configured annotation reader. If $useSimpleAnnotationReader * is true, the notation `@Entity` will work, otherwise, the notation `@ORM\Entity` will be supported. * * @param array $paths * @param bool $useSimpleAnnotationReader * * @return AnnotationDriver */ public function newDefaultAnnotationDriver($paths = array(), $useSimpleAnnotationReader = true) { AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php'); if ($useSimpleAnnotationReader) { // Register the ORM Annotations in the AnnotationRegistry $reader = new SimpleAnnotationReader(); $reader->addNamespace('Doctrine\ORM\Mapping'); $cachedReader = new CachedReader($reader, new ArrayCache()); return new AnnotationDriver($cachedReader, (array) $paths); } return new AnnotationDriver( new CachedReader(new AnnotationReader(), new ArrayCache()), (array) $paths ); } /** * Adds a namespace under a certain alias. * * @param string $alias * @param string $namespace * * @return void */ public function addEntityNamespace($alias, $namespace) { $this->_attributes['entityNamespaces'][$alias] = $namespace; } /** * Resolves a registered namespace alias to the full namespace. * * @param string $entityNamespaceAlias * * @return string * * @throws ORMException */ public function getEntityNamespace($entityNamespaceAlias) { if ( ! isset($this->_attributes['entityNamespaces'][$entityNamespaceAlias])) { throw ORMException::unknownEntityNamespace($entityNamespaceAlias); } return trim($this->_attributes['entityNamespaces'][$entityNamespaceAlias], '\\'); } /** * Sets the entity alias map. * * @param array $entityNamespaces * * @return void */ public function setEntityNamespaces(array $entityNamespaces) { $this->_attributes['entityNamespaces'] = $entityNamespaces; } /** * Retrieves the list of registered entity namespace aliases. * * @return array */ public function getEntityNamespaces() { return $this->_attributes['entityNamespaces']; } /** * Gets the cache driver implementation that is used for the mapping metadata. * * @return MappingDriver|null * * @throws ORMException */ public function getMetadataDriverImpl() { return isset($this->_attributes['metadataDriverImpl']) ? $this->_attributes['metadataDriverImpl'] : null; } /** * Gets the cache driver implementation that is used for the query cache (SQL cache). * * @return \Doctrine\Common\Cache\Cache|null */ public function getQueryCacheImpl() { return isset($this->_attributes['queryCacheImpl']) ? $this->_attributes['queryCacheImpl'] : null; } /** * Sets the cache driver implementation that is used for the query cache (SQL cache). * * @param \Doctrine\Common\Cache\Cache $cacheImpl * * @return void */ public function setQueryCacheImpl(Cache $cacheImpl) { $this->_attributes['queryCacheImpl'] = $cacheImpl; } /** * Gets the cache driver implementation that is used for the hydration cache (SQL cache). * * @return \Doctrine\Common\Cache\Cache|null */ public function getHydrationCacheImpl() { return isset($this->_attributes['hydrationCacheImpl']) ? $this->_attributes['hydrationCacheImpl'] : null; } /** * Sets the cache driver implementation that is used for the hydration cache (SQL cache). * * @param \Doctrine\Common\Cache\Cache $cacheImpl * * @return void */ public function setHydrationCacheImpl(Cache $cacheImpl) { $this->_attributes['hydrationCacheImpl'] = $cacheImpl; } /** * Gets the cache driver implementation that is used for metadata caching. * * @return \Doctrine\Common\Cache\Cache|null */ public function getMetadataCacheImpl() { return isset($this->_attributes['metadataCacheImpl']) ? $this->_attributes['metadataCacheImpl'] : null; } /** * Sets the cache driver implementation that is used for metadata caching. * * @param \Doctrine\Common\Cache\Cache $cacheImpl * * @return void */ public function setMetadataCacheImpl(Cache $cacheImpl) { $this->_attributes['metadataCacheImpl'] = $cacheImpl; } /** * Adds a named DQL query to the configuration. * * @param string $name The name of the query. * @param string $dql The DQL query string. * * @return void */ public function addNamedQuery($name, $dql) { $this->_attributes['namedQueries'][$name] = $dql; } /** * Gets a previously registered named DQL query. * * @param string $name The name of the query. * * @return string The DQL query. * * @throws ORMException */ public function getNamedQuery($name) { if ( ! isset($this->_attributes['namedQueries'][$name])) { throw ORMException::namedQueryNotFound($name); } return $this->_attributes['namedQueries'][$name]; } /** * Adds a named native query to the configuration. * * @param string $name The name of the query. * @param string $sql The native SQL query string. * @param Query\ResultSetMapping $rsm The ResultSetMapping used for the results of the SQL query. * * @return void */ public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm) { $this->_attributes['namedNativeQueries'][$name] = array($sql, $rsm); } /** * Gets the components of a previously registered named native query. * * @param string $name The name of the query. * * @return array A tuple with the first element being the SQL string and the second * element being the ResultSetMapping. * * @throws ORMException */ public function getNamedNativeQuery($name) { if ( ! isset($this->_attributes['namedNativeQueries'][$name])) { throw ORMException::namedNativeQueryNotFound($name); } return $this->_attributes['namedNativeQueries'][$name]; } /** * Ensures that this Configuration instance contains settings that are * suitable for a production environment. * * @return void * * @throws ORMException If a configuration setting has a value that is not * suitable for a production environment. */ public function ensureProductionSettings() { if ( ! $this->getQueryCacheImpl()) { throw ORMException::queryCacheNotConfigured(); } if ( ! $this->getMetadataCacheImpl()) { throw ORMException::metadataCacheNotConfigured(); } if ($this->getAutoGenerateProxyClasses()) { throw ORMException::proxyClassesAlwaysRegenerating(); } } /** * Registers a custom DQL function that produces a string value. * Such a function can then be used in any DQL statement in any place where string * functions are allowed. * * DQL function names are case-insensitive. * * @param string $name * @param string $className * * @return void * * @throws ORMException */ public function addCustomStringFunction($name, $className) { if (Query\Parser::isInternalFunction($name)) { throw ORMException::overwriteInternalDQLFunctionNotAllowed($name); } $this->_attributes['customStringFunctions'][strtolower($name)] = $className; } /** * Gets the implementation class name of a registered custom string DQL function. * * @param string $name * * @return string|null */ public function getCustomStringFunction($name) { $name = strtolower($name); return isset($this->_attributes['customStringFunctions'][$name]) ? $this->_attributes['customStringFunctions'][$name] : null; } /** * Sets a map of custom DQL string functions. * * Keys must be function names and values the FQCN of the implementing class. * The function names will be case-insensitive in DQL. * * Any previously added string functions are discarded. * * @param array $functions The map of custom DQL string functions. * * @return void */ public function setCustomStringFunctions(array $functions) { foreach ($functions as $name => $className) { $this->addCustomStringFunction($name, $className); } } /** * Registers a custom DQL function that produces a numeric value. * Such a function can then be used in any DQL statement in any place where numeric * functions are allowed. * * DQL function names are case-insensitive. * * @param string $name * @param string $className * * @return void * * @throws ORMException */ public function addCustomNumericFunction($name, $className) { if (Query\Parser::isInternalFunction($name)) { throw ORMException::overwriteInternalDQLFunctionNotAllowed($name); } $this->_attributes['customNumericFunctions'][strtolower($name)] = $className; } /** * Gets the implementation class name of a registered custom numeric DQL function. * * @param string $name * * @return string|null */ public function getCustomNumericFunction($name) { $name = strtolower($name); return isset($this->_attributes['customNumericFunctions'][$name]) ? $this->_attributes['customNumericFunctions'][$name] : null; } /** * Sets a map of custom DQL numeric functions. * * Keys must be function names and values the FQCN of the implementing class. * The function names will be case-insensitive in DQL. * * Any previously added numeric functions are discarded. * * @param array $functions The map of custom DQL numeric functions. * * @return void */ public function setCustomNumericFunctions(array $functions) { foreach ($functions as $name => $className) { $this->addCustomNumericFunction($name, $className); } } /** * Registers a custom DQL function that produces a date/time value. * Such a function can then be used in any DQL statement in any place where date/time * functions are allowed. * * DQL function names are case-insensitive. * * @param string $name * @param string $className * * @return void * * @throws ORMException */ public function addCustomDatetimeFunction($name, $className) { if (Query\Parser::isInternalFunction($name)) { throw ORMException::overwriteInternalDQLFunctionNotAllowed($name); } $this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className; } /** * Gets the implementation class name of a registered custom date/time DQL function. * * @param string $name * * @return string|null */ public function getCustomDatetimeFunction($name) { $name = strtolower($name); return isset($this->_attributes['customDatetimeFunctions'][$name]) ? $this->_attributes['customDatetimeFunctions'][$name] : null; } /** * Sets a map of custom DQL date/time functions. * * Keys must be function names and values the FQCN of the implementing class. * The function names will be case-insensitive in DQL. * * Any previously added date/time functions are discarded. * * @param array $functions The map of custom DQL date/time functions. * * @return void */ public function setCustomDatetimeFunctions(array $functions) { foreach ($functions as $name => $className) { $this->addCustomDatetimeFunction($name, $className); } } /** * Sets the custom hydrator modes in one pass. * * @param array $modes An array of ($modeName => $hydrator). * * @return void */ public function setCustomHydrationModes($modes) { $this->_attributes['customHydrationModes'] = array(); foreach ($modes as $modeName => $hydrator) { $this->addCustomHydrationMode($modeName, $hydrator); } } /** * Gets the hydrator class for the given hydration mode name. * * @param string $modeName The hydration mode name. * * @return string|null The hydrator class name. */ public function getCustomHydrationMode($modeName) { return isset($this->_attributes['customHydrationModes'][$modeName]) ? $this->_attributes['customHydrationModes'][$modeName] : null; } /** * Adds a custom hydration mode. * * @param string $modeName The hydration mode name. * @param string $hydrator The hydrator class name. * * @return void */ public function addCustomHydrationMode($modeName, $hydrator) { $this->_attributes['customHydrationModes'][$modeName] = $hydrator; } /** * Sets a class metadata factory. * * @param string $cmfName * * @return void */ public function setClassMetadataFactoryName($cmfName) { $this->_attributes['classMetadataFactoryName'] = $cmfName; } /** * @return string */ public function getClassMetadataFactoryName() { if ( ! isset($this->_attributes['classMetadataFactoryName'])) { $this->_attributes['classMetadataFactoryName'] = 'Doctrine\ORM\Mapping\ClassMetadataFactory'; } return $this->_attributes['classMetadataFactoryName']; } /** * Adds a filter to the list of possible filters. * * @param string $name The name of the filter. * @param string $className The class name of the filter. */ public function addFilter($name, $className) { $this->_attributes['filters'][$name] = $className; } /** * Gets the class name for a given filter name. * * @param string $name The name of the filter. * * @return string The class name of the filter, or null of it is not * defined. */ public function getFilterClassName($name) { return isset($this->_attributes['filters'][$name]) ? $this->_attributes['filters'][$name] : null; } /** * Sets default repository class. * * @since 2.2 * * @param string $className * * @return void * * @throws ORMException If not is a \Doctrine\Common\Persistence\ObjectRepository */ public function setDefaultRepositoryClassName($className) { $reflectionClass = new \ReflectionClass($className); if ( ! $reflectionClass->implementsInterface('Doctrine\Common\Persistence\ObjectRepository')) { 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'; } /** * Sets naming strategy. * * @since 2.3 * * @param NamingStrategy $namingStrategy * * @return void */ public function setNamingStrategy(NamingStrategy $namingStrategy) { $this->_attributes['namingStrategy'] = $namingStrategy; } /** * Gets naming strategy.. * * @since 2.3 * * @return NamingStrategy */ public function getNamingStrategy() { if ( ! isset($this->_attributes['namingStrategy'])) { $this->_attributes['namingStrategy'] = new DefaultNamingStrategy(); } return $this->_attributes['namingStrategy']; } /** * Sets quote strategy. * * @since 2.3 * * @param \Doctrine\ORM\Mapping\QuoteStrategy $quoteStrategy * * @return void */ public function setQuoteStrategy(QuoteStrategy $quoteStrategy) { $this->_attributes['quoteStrategy'] = $quoteStrategy; } /** * Gets quote strategy. * * @since 2.3 * * @return \Doctrine\ORM\Mapping\QuoteStrategy */ public function getQuoteStrategy() { if ( ! isset($this->_attributes['quoteStrategy'])) { $this->_attributes['quoteStrategy'] = new DefaultQuoteStrategy(); } return $this->_attributes['quoteStrategy']; } /** * Set the entity listener resolver. * * @since 2.4 * @param \Doctrine\ORM\Mapping\EntityListenerResolver $resolver */ public function setEntityListenerResolver(EntityListenerResolver $resolver) { $this->_attributes['entityListenerResolver'] = $resolver; } /** * Get the entity listener resolver. * * @since 2.4 * @return \Doctrine\ORM\Mapping\EntityListenerResolver */ public function getEntityListenerResolver() { if ( ! isset($this->_attributes['entityListenerResolver'])) { $this->_attributes['entityListenerResolver'] = new DefaultEntityListenerResolver(); } return $this->_attributes['entityListenerResolver']; } /** * Set the entity repository factory. * * @since 2.4 * @param \Doctrine\ORM\Repository\RepositoryFactory $repositoryFactory */ public function setRepositoryFactory(RepositoryFactory $repositoryFactory) { $this->_attributes['repositoryFactory'] = $repositoryFactory; } /** * Get the entity repository factory. * * @since 2.4 * @return \Doctrine\ORM\Repository\RepositoryFactory */ public function getRepositoryFactory() { return isset($this->_attributes['repositoryFactory']) ? $this->_attributes['repositoryFactory'] : new DefaultRepositoryFactory(); } } doctrine2-2.4.8/lib/Doctrine/ORM/Decorator/000077500000000000000000000000001257105210500203105ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php000066400000000000000000000132651257105210500254420ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Decorator; use Doctrine\DBAL\LockMode; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Common\Persistence\ObjectManagerDecorator; /** * Base class for EntityManager decorators * * @since 2.4 * @author Lars Strojny wrapped = $wrapped; } /** * {@inheritdoc} */ public function getConnection() { return $this->wrapped->getConnection(); } /** * {@inheritdoc} */ public function getExpressionBuilder() { return $this->wrapped->getExpressionBuilder(); } /** * {@inheritdoc} */ public function beginTransaction() { return $this->wrapped->beginTransaction(); } /** * {@inheritdoc} */ public function transactional($func) { return $this->wrapped->transactional($func); } /** * {@inheritdoc} */ public function commit() { return $this->wrapped->commit(); } /** * {@inheritdoc} */ public function rollback() { return $this->wrapped->rollback(); } /** * {@inheritdoc} */ public function createQuery($dql = '') { return $this->wrapped->createQuery($dql); } /** * {@inheritdoc} */ public function createNamedQuery($name) { return $this->wrapped->createNamedQuery($name); } /** * {@inheritdoc} */ public function createNativeQuery($sql, ResultSetMapping $rsm) { return $this->wrapped->createNativeQuery($sql, $rsm); } /** * {@inheritdoc} */ public function createNamedNativeQuery($name) { return $this->wrapped->createNamedNativeQuery($name); } /** * {@inheritdoc} */ public function createQueryBuilder() { return $this->wrapped->createQueryBuilder(); } /** * {@inheritdoc} */ public function getReference($entityName, $id) { return $this->wrapped->getReference($entityName, $id); } /** * {@inheritdoc} */ public function getPartialReference($entityName, $identifier) { return $this->wrapped->getPartialReference($entityName, $identifier); } /** * {@inheritdoc} */ public function close() { return $this->wrapped->close(); } /** * {@inheritdoc} */ public function copy($entity, $deep = false) { return $this->wrapped->copy($entity, $deep); } /** * {@inheritdoc} */ public function lock($entity, $lockMode, $lockVersion = null) { return $this->wrapped->lock($entity, $lockMode, $lockVersion); } /** * {@inheritdoc} */ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion = null) { return $this->wrapped->find($entityName, $id, $lockMode, $lockVersion); } /** * {@inheritdoc} */ public function flush($entity = null) { return $this->wrapped->flush($entity); } /** * {@inheritdoc} */ public function getEventManager() { return $this->wrapped->getEventManager(); } /** * {@inheritdoc} */ public function getConfiguration() { return $this->wrapped->getConfiguration(); } /** * {@inheritdoc} */ public function isOpen() { return $this->wrapped->isOpen(); } /** * {@inheritdoc} */ public function getUnitOfWork() { return $this->wrapped->getUnitOfWork(); } /** * {@inheritdoc} */ public function getHydrator($hydrationMode) { return $this->wrapped->getHydrator($hydrationMode); } /** * {@inheritdoc} */ public function newHydrator($hydrationMode) { return $this->wrapped->newHydrator($hydrationMode); } /** * {@inheritdoc} */ public function getProxyFactory() { return $this->wrapped->getProxyFactory(); } /** * {@inheritdoc} */ public function getFilters() { return $this->wrapped->getFilters(); } /** * {@inheritdoc} */ public function isFiltersStateClean() { return $this->wrapped->isFiltersStateClean(); } /** * {@inheritdoc} */ public function hasFilters() { return $this->wrapped->hasFilters(); } } doctrine2-2.4.8/lib/Doctrine/ORM/EntityManager.php000066400000000000000000000566501257105210500216620ustar00rootroot00000000000000. */ namespace Doctrine\ORM; use Exception; use Doctrine\Common\EventManager; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\DBAL\Connection; use Doctrine\DBAL\LockMode; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Query\FilterCollection; use Doctrine\Common\Util\ClassUtils; /** * The EntityManager is the central access point to ORM functionality. * * It is a facade to all different ORM subsystems such as UnitOfWork, * Query Language and Repository API. Instantiation is done through * the static create() method. The quickest way to obtain a fully * configured EntityManager is: * * use Doctrine\ORM\Tools\Setup; * use Doctrine\ORM\EntityManager; * * $paths = array('/path/to/entity/mapping/files'); * * $config = Setup::createAnnotationMetadataConfiguration($paths); * $dbParams = array('driver' => 'pdo_sqlite', 'memory' => true); * $entityManager = EntityManager::create($dbParams, $config); * * For more information see * {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html} * * You should never attempt to inherit from the EntityManager: Inheritance * is not a valid extension point for the EntityManager. Instead you * should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator} * and wrap your entity manager in a decorator. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ /* final */class EntityManager implements EntityManagerInterface { /** * The used Configuration. * * @var \Doctrine\ORM\Configuration */ private $config; /** * The database connection used by the EntityManager. * * @var \Doctrine\DBAL\Connection */ private $conn; /** * The metadata factory, used to retrieve the ORM metadata of entity classes. * * @var \Doctrine\ORM\Mapping\ClassMetadataFactory */ private $metadataFactory; /** * The UnitOfWork used to coordinate object-level transactions. * * @var \Doctrine\ORM\UnitOfWork */ private $unitOfWork; /** * The event manager that is the central point of the event system. * * @var \Doctrine\Common\EventManager */ private $eventManager; /** * The proxy factory used to create dynamic proxies. * * @var \Doctrine\ORM\Proxy\ProxyFactory */ private $proxyFactory; /** * The repository factory used to create dynamic repositories. * * @var \Doctrine\ORM\Repository\RepositoryFactory */ private $repositoryFactory; /** * The expression builder instance used to generate query expressions. * * @var \Doctrine\ORM\Query\Expr */ private $expressionBuilder; /** * Whether the EntityManager is closed or not. * * @var bool */ private $closed = false; /** * Collection of query filters. * * @var \Doctrine\ORM\Query\FilterCollection */ private $filterCollection; /** * Creates a new EntityManager that operates on the given database connection * and uses the given Configuration and EventManager implementations. * * @param \Doctrine\DBAL\Connection $conn * @param \Doctrine\ORM\Configuration $config * @param \Doctrine\Common\EventManager $eventManager */ protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager) { $this->conn = $conn; $this->config = $config; $this->eventManager = $eventManager; $metadataFactoryClassName = $config->getClassMetadataFactoryName(); $this->metadataFactory = new $metadataFactoryClassName; $this->metadataFactory->setEntityManager($this); $this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl()); $this->repositoryFactory = $config->getRepositoryFactory(); $this->unitOfWork = new UnitOfWork($this); $this->proxyFactory = new ProxyFactory( $this, $config->getProxyDir(), $config->getProxyNamespace(), $config->getAutoGenerateProxyClasses() ); } /** * {@inheritDoc} */ public function getConnection() { return $this->conn; } /** * Gets the metadata factory used to gather the metadata of classes. * * @return \Doctrine\ORM\Mapping\ClassMetadataFactory */ public function getMetadataFactory() { return $this->metadataFactory; } /** * {@inheritDoc} */ public function getExpressionBuilder() { if ($this->expressionBuilder === null) { $this->expressionBuilder = new Query\Expr; } return $this->expressionBuilder; } /** * {@inheritDoc} */ public function beginTransaction() { $this->conn->beginTransaction(); } /** * {@inheritDoc} */ public function transactional($func) { if (!is_callable($func)) { throw new \InvalidArgumentException('Expected argument of type "callable", got "' . gettype($func) . '"'); } $this->conn->beginTransaction(); try { $return = call_user_func($func, $this); $this->flush(); $this->conn->commit(); return $return ?: true; } catch (Exception $e) { $this->close(); $this->conn->rollback(); throw $e; } } /** * {@inheritDoc} */ public function commit() { $this->conn->commit(); } /** * {@inheritDoc} */ public function rollback() { $this->conn->rollback(); } /** * Returns the ORM metadata descriptor for a class. * * The class name must be the fully-qualified class name without a leading backslash * (as it is returned by get_class($obj)) or an aliased class name. * * Examples: * MyProject\Domain\User * sales:PriceRequest * * @param string $className * * @return \Doctrine\ORM\Mapping\ClassMetadata * * @internal Performance-sensitive method. */ public function getClassMetadata($className) { return $this->metadataFactory->getMetadataFor($className); } /** * {@inheritDoc} */ public function createQuery($dql = '') { $query = new Query($this); if ( ! empty($dql)) { $query->setDql($dql); } return $query; } /** * {@inheritDoc} */ public function createNamedQuery($name) { return $this->createQuery($this->config->getNamedQuery($name)); } /** * {@inheritDoc} */ public function createNativeQuery($sql, ResultSetMapping $rsm) { $query = new NativeQuery($this); $query->setSql($sql); $query->setResultSetMapping($rsm); return $query; } /** * {@inheritDoc} */ public function createNamedNativeQuery($name) { list($sql, $rsm) = $this->config->getNamedNativeQuery($name); return $this->createNativeQuery($sql, $rsm); } /** * {@inheritDoc} */ public function createQueryBuilder() { return new QueryBuilder($this); } /** * Flushes all changes to objects that have been queued up to now to the database. * This effectively synchronizes the in-memory state of managed objects with the * database. * * If an entity is explicitly passed to this method only this entity and * the cascade-persist semantics + scheduled inserts/removals are synchronized. * * @param null|object|array $entity * * @return void * * @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that * makes use of optimistic locking fails. */ public function flush($entity = null) { $this->errorIfClosed(); $this->unitOfWork->commit($entity); } /** * Finds an Entity by its identifier. * * @param string $entityName * @param mixed $id * @param integer $lockMode * @param integer|null $lockVersion * * @return object|null The entity instance or NULL if the entity can not be found. * * @throws OptimisticLockException * @throws ORMInvalidArgumentException * @throws TransactionRequiredException * @throws ORMException */ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion = null) { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); if (is_object($id) && $this->metadataFactory->hasMetadataFor(ClassUtils::getClass($id))) { $id = $this->unitOfWork->getSingleIdentifierValue($id); if ($id === null) { throw ORMInvalidArgumentException::invalidIdentifierBindingEntity(); } } if ( ! is_array($id)) { $id = array($class->identifier[0] => $id); } $sortedId = array(); foreach ($class->identifier as $identifier) { if ( ! isset($id[$identifier])) { throw ORMException::missingIdentifierField($class->name, $identifier); } $sortedId[$identifier] = $id[$identifier]; } $unitOfWork = $this->getUnitOfWork(); // Check identity map first if (($entity = $unitOfWork->tryGetById($sortedId, $class->rootEntityName)) !== false) { if ( ! ($entity instanceof $class->name)) { return null; } switch ($lockMode) { case LockMode::OPTIMISTIC: $this->lock($entity, $lockMode, $lockVersion); break; case LockMode::PESSIMISTIC_READ: case LockMode::PESSIMISTIC_WRITE: $persister = $unitOfWork->getEntityPersister($class->name); $persister->refresh($sortedId, $entity, $lockMode); break; } return $entity; // Hit! } $persister = $unitOfWork->getEntityPersister($class->name); switch ($lockMode) { case LockMode::NONE: return $persister->load($sortedId); case LockMode::OPTIMISTIC: if ( ! $class->isVersioned) { throw OptimisticLockException::notVersioned($class->name); } $entity = $persister->load($sortedId); $unitOfWork->lock($entity, $lockMode, $lockVersion); return $entity; default: if ( ! $this->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } return $persister->load($sortedId, null, null, array(), $lockMode); } } /** * {@inheritDoc} */ public function getReference($entityName, $id) { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); if ( ! is_array($id)) { $id = array($class->identifier[0] => $id); } $sortedId = array(); foreach ($class->identifier as $identifier) { if ( ! isset($id[$identifier])) { throw ORMException::missingIdentifierField($class->name, $identifier); } $sortedId[$identifier] = $id[$identifier]; } // Check identity map first, if its already in there just return it. if (($entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName)) !== false) { return ($entity instanceof $class->name) ? $entity : null; } if ($class->subClasses) { return $this->find($entityName, $sortedId); } if ( ! is_array($sortedId)) { $sortedId = array($class->identifier[0] => $sortedId); } $entity = $this->proxyFactory->getProxy($class->name, $sortedId); $this->unitOfWork->registerManaged($entity, $sortedId, array()); return $entity; } /** * {@inheritDoc} */ public function getPartialReference($entityName, $identifier) { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); // Check identity map first, if its already in there just return it. if (($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) !== false) { return ($entity instanceof $class->name) ? $entity : null; } if ( ! is_array($identifier)) { $identifier = array($class->identifier[0] => $identifier); } $entity = $class->newInstance(); $class->setIdentifierValues($entity, $identifier); $this->unitOfWork->registerManaged($entity, $identifier, array()); $this->unitOfWork->markReadOnly($entity); return $entity; } /** * Clears the EntityManager. All entities that are currently managed * by this EntityManager become detached. * * @param string|null $entityName if given, only entities of this type will get detached * * @return void */ public function clear($entityName = null) { $this->unitOfWork->clear($entityName); } /** * {@inheritDoc} */ public function close() { $this->clear(); $this->closed = true; } /** * Tells the EntityManager to make an instance managed and persistent. * * The entity will be entered into the database at or before transaction * commit or as a result of the flush operation. * * NOTE: The persist operation always considers entities that are not yet known to * this EntityManager as NEW. Do not pass detached entities to the persist operation. * * @param object $entity The instance to make managed and persistent. * * @return void * * @throws ORMInvalidArgumentException */ public function persist($entity) { if ( ! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()' , $entity); } $this->errorIfClosed(); $this->unitOfWork->persist($entity); } /** * Removes an entity instance. * * A removed entity will be removed from the database at or before transaction commit * or as a result of the flush operation. * * @param object $entity The entity instance to remove. * * @return void * * @throws ORMInvalidArgumentException */ public function remove($entity) { if ( ! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()' , $entity); } $this->errorIfClosed(); $this->unitOfWork->remove($entity); } /** * Refreshes the persistent state of an entity from the database, * overriding any local changes that have not yet been persisted. * * @param object $entity The entity to refresh. * * @return void * * @throws ORMInvalidArgumentException */ public function refresh($entity) { if ( ! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()' , $entity); } $this->errorIfClosed(); $this->unitOfWork->refresh($entity); } /** * Detaches an entity from the EntityManager, causing a managed entity to * become detached. Unflushed changes made to the entity if any * (including removal of the entity), will not be synchronized to the database. * Entities which previously referenced the detached entity will continue to * reference it. * * @param object $entity The entity to detach. * * @return void * * @throws ORMInvalidArgumentException */ public function detach($entity) { if ( ! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()' , $entity); } $this->unitOfWork->detach($entity); } /** * Merges the state of a detached entity into the persistence context * of this EntityManager and returns the managed copy of the entity. * The entity passed to merge will not become associated/managed with this EntityManager. * * @param object $entity The detached entity to merge into the persistence context. * * @return object The managed copy of the entity. * * @throws ORMInvalidArgumentException */ public function merge($entity) { if ( ! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()' , $entity); } $this->errorIfClosed(); return $this->unitOfWork->merge($entity); } /** * {@inheritDoc} * * @todo Implementation need. This is necessary since $e2 = clone $e1; throws an E_FATAL when access anything on $e: * Fatal error: Maximum function nesting level of '100' reached, aborting! */ public function copy($entity, $deep = false) { throw new \BadMethodCallException("Not implemented."); } /** * {@inheritDoc} */ public function lock($entity, $lockMode, $lockVersion = null) { $this->unitOfWork->lock($entity, $lockMode, $lockVersion); } /** * Gets the repository for an entity class. * * @param string $entityName The name of the entity. * * @return \Doctrine\ORM\EntityRepository The repository class. */ public function getRepository($entityName) { return $this->repositoryFactory->getRepository($this, $entityName); } /** * Determines whether an entity instance is managed in this EntityManager. * * @param object $entity * * @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise. */ public function contains($entity) { return $this->unitOfWork->isScheduledForInsert($entity) || $this->unitOfWork->isInIdentityMap($entity) && ! $this->unitOfWork->isScheduledForDelete($entity); } /** * {@inheritDoc} */ public function getEventManager() { return $this->eventManager; } /** * {@inheritDoc} */ public function getConfiguration() { return $this->config; } /** * Throws an exception if the EntityManager is closed or currently not active. * * @return void * * @throws ORMException If the EntityManager is closed. */ private function errorIfClosed() { if ($this->closed) { throw ORMException::entityManagerClosed(); } } /** * {@inheritDoc} */ public function isOpen() { return (!$this->closed); } /** * {@inheritDoc} */ public function getUnitOfWork() { return $this->unitOfWork; } /** * {@inheritDoc} */ public function getHydrator($hydrationMode) { return $this->newHydrator($hydrationMode); } /** * {@inheritDoc} */ public function newHydrator($hydrationMode) { switch ($hydrationMode) { case Query::HYDRATE_OBJECT: return new Internal\Hydration\ObjectHydrator($this); case Query::HYDRATE_ARRAY: return new Internal\Hydration\ArrayHydrator($this); case Query::HYDRATE_SCALAR: return new Internal\Hydration\ScalarHydrator($this); case Query::HYDRATE_SINGLE_SCALAR: return new Internal\Hydration\SingleScalarHydrator($this); case Query::HYDRATE_SIMPLEOBJECT: return new Internal\Hydration\SimpleObjectHydrator($this); default: if (($class = $this->config->getCustomHydrationMode($hydrationMode)) !== null) { return new $class($this); } } throw ORMException::invalidHydrationMode($hydrationMode); } /** * {@inheritDoc} */ public function getProxyFactory() { return $this->proxyFactory; } /** * {@inheritDoc} */ public function initializeObject($obj) { $this->unitOfWork->initializeObject($obj); } /** * Factory method to create EntityManager instances. * * @param mixed $conn An array with the connection parameters or an existing Connection instance. * @param Configuration $config The Configuration instance to use. * @param EventManager $eventManager The EventManager instance to use. * * @return EntityManager The created EntityManager. * * @throws \InvalidArgumentException * @throws ORMException */ public static function create($conn, Configuration $config, EventManager $eventManager = null) { if ( ! $config->getMetadataDriverImpl()) { throw ORMException::missingMappingDriverImpl(); } switch (true) { case (is_array($conn)): $conn = \Doctrine\DBAL\DriverManager::getConnection( $conn, $config, ($eventManager ?: new EventManager()) ); break; case ($conn instanceof Connection): if ($eventManager !== null && $conn->getEventManager() !== $eventManager) { throw ORMException::mismatchedEventManager(); } break; default: throw new \InvalidArgumentException("Invalid argument: " . $conn); } return new EntityManager($conn, $config, $conn->getEventManager()); } /** * {@inheritDoc} */ public function getFilters() { if (null === $this->filterCollection) { $this->filterCollection = new FilterCollection($this); } return $this->filterCollection; } /** * {@inheritDoc} */ public function isFiltersStateClean() { return null === $this->filterCollection || $this->filterCollection->isClean(); } /** * {@inheritDoc} */ public function hasFilters() { return null !== $this->filterCollection; } } doctrine2-2.4.8/lib/Doctrine/ORM/EntityManagerInterface.php000066400000000000000000000204251257105210500234720ustar00rootroot00000000000000. */ namespace Doctrine\ORM; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\Query\ResultSetMapping; /** * EntityManager interface * * @since 2.4 * @author Lars Strojny * $qb = $em->createQueryBuilder(); * $expr = $em->getExpressionBuilder(); * $qb->select('u')->from('User', 'u') * ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2))); * * * @return \Doctrine\ORM\Query\Expr */ public function getExpressionBuilder(); /** * Starts a transaction on the underlying database connection. * * @return void */ public function beginTransaction(); /** * Executes a function in a transaction. * * The function gets passed this EntityManager instance as an (optional) parameter. * * {@link flush} is invoked prior to transaction commit. * * If an exception occurs during execution of the function or flushing or transaction commit, * the transaction is rolled back, the EntityManager closed and the exception re-thrown. * * @param callable $func The function to execute transactionally. * * @return mixed The non-empty value returned from the closure or true instead. */ public function transactional($func); /** * Commits a transaction on the underlying database connection. * * @return void */ public function commit(); /** * Performs a rollback on the underlying database connection. * * @return void */ public function rollback(); /** * Creates a new Query object. * * @param string $dql The DQL string. * * @return Query */ public function createQuery($dql = ''); /** * Creates a Query from a named query. * * @param string $name * * @return Query */ public function createNamedQuery($name); /** * Creates a native SQL query. * * @param string $sql * @param ResultSetMapping $rsm The ResultSetMapping to use. * * @return NativeQuery */ public function createNativeQuery($sql, ResultSetMapping $rsm); /** * Creates a NativeQuery from a named native query. * * @param string $name * * @return NativeQuery */ public function createNamedNativeQuery($name); /** * Create a QueryBuilder instance * * @return QueryBuilder */ public function createQueryBuilder(); /** * Gets a reference to the entity identified by the given type and identifier * without actually loading it, if the entity is not yet loaded. * * @param string $entityName The name of the entity type. * @param mixed $id The entity identifier. * * @return object The entity reference. * * @throws ORMException */ public function getReference($entityName, $id); /** * Gets a partial reference to the entity identified by the given type and identifier * without actually loading it, if the entity is not yet loaded. * * The returned reference may be a partial object if the entity is not yet loaded/managed. * If it is a partial object it will not initialize the rest of the entity state on access. * Thus you can only ever safely access the identifier of an entity obtained through * this method. * * The use-cases for partial references involve maintaining bidirectional associations * without loading one side of the association or to update an entity without loading it. * Note, however, that in the latter case the original (persistent) entity data will * never be visible to the application (especially not event listeners) as it will * never be loaded in the first place. * * @param string $entityName The name of the entity type. * @param mixed $identifier The entity identifier. * * @return object The (partial) entity reference. */ public function getPartialReference($entityName, $identifier); /** * Closes the EntityManager. All entities that are currently managed * by this EntityManager become detached. The EntityManager may no longer * be used after it is closed. * * @return void */ public function close(); /** * Creates a copy of the given entity. Can create a shallow or a deep copy. * * @param object $entity The entity to copy. * @param boolean $deep FALSE for a shallow copy, TRUE for a deep copy. * * @return object The new entity. * * @throws \BadMethodCallException */ public function copy($entity, $deep = false); /** * Acquire a lock on the given entity. * * @param object $entity * @param int $lockMode * @param int|null $lockVersion * * @return void * * @throws OptimisticLockException * @throws PessimisticLockException */ public function lock($entity, $lockMode, $lockVersion = null); /** * Gets the EventManager used by the EntityManager. * * @return \Doctrine\Common\EventManager */ public function getEventManager(); /** * Gets the Configuration used by the EntityManager. * * @return Configuration */ public function getConfiguration(); /** * Check if the Entity manager is open or closed. * * @return bool */ public function isOpen(); /** * Gets the UnitOfWork used by the EntityManager to coordinate operations. * * @return UnitOfWork */ public function getUnitOfWork(); /** * Gets a hydrator for the given hydration mode. * * This method caches the hydrator instances which is used for all queries that don't * selectively iterate over the result. * * @deprecated * * @param int $hydrationMode * * @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator */ public function getHydrator($hydrationMode); /** * Create a new instance for the given hydration mode. * * @param int $hydrationMode * * @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator * * @throws ORMException */ public function newHydrator($hydrationMode); /** * Gets the proxy factory used by the EntityManager to create entity proxies. * * @return \Doctrine\ORM\Proxy\ProxyFactory */ public function getProxyFactory(); /** * Gets the enabled filters. * * @return \Doctrine\ORM\Query\FilterCollection The active filter collection. */ public function getFilters(); /** * Checks whether the state of the filter collection is clean. * * @return boolean True, if the filter collection is clean. */ public function isFiltersStateClean(); /** * Checks whether the Entity Manager has filters. * * @return boolean True, if the EM has a filter collection. */ public function hasFilters(); } doctrine2-2.4.8/lib/Doctrine/ORM/EntityNotFoundException.php000066400000000000000000000024431257105210500237120ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * Exception thrown when a Proxy fails to retrieve an Entity result. * * @author robo * @since 2.0 */ class EntityNotFoundException extends ORMException { /** * Constructor. */ public function __construct() { parent::__construct('Entity was not found.'); } } doctrine2-2.4.8/lib/Doctrine/ORM/EntityRepository.php000066400000000000000000000217341257105210500224620ustar00rootroot00000000000000. */ namespace Doctrine\ORM; use Doctrine\ORM\Query\ResultSetMappingBuilder; use Doctrine\DBAL\LockMode; use Doctrine\Common\Persistence\ObjectRepository; use Doctrine\Common\Collections\Selectable; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\ArrayCollection; /** * An EntityRepository serves as a repository for entities with generic as well as * business specific methods for retrieving entities. * * This class is designed for inheritance and users can subclass this class to * write their own repositories with business-specific methods to locate entities. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EntityRepository implements ObjectRepository, Selectable { /** * @var string */ protected $_entityName; /** * @var EntityManager */ protected $_em; /** * @var \Doctrine\ORM\Mapping\ClassMetadata */ protected $_class; /** * Initializes a new EntityRepository. * * @param EntityManager $em The EntityManager to use. * @param Mapping\ClassMetadata $class The class descriptor. */ public function __construct($em, Mapping\ClassMetadata $class) { $this->_entityName = $class->name; $this->_em = $em; $this->_class = $class; } /** * Creates a new QueryBuilder instance that is prepopulated for this entity name. * * @param string $alias * * @return QueryBuilder */ public function createQueryBuilder($alias) { return $this->_em->createQueryBuilder() ->select($alias) ->from($this->_entityName, $alias); } /** * Creates a new result set mapping builder for this entity. * * The column naming strategy is "INCREMENT". * * @param string $alias * * @return ResultSetMappingBuilder */ public function createResultSetMappingBuilder($alias) { $rsm = new ResultSetMappingBuilder($this->_em, ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT); $rsm->addRootEntityFromClassMetadata($this->_entityName, $alias); return $rsm; } /** * Creates a new Query instance based on a predefined metadata named query. * * @param string $queryName * * @return Query */ public function createNamedQuery($queryName) { return $this->_em->createQuery($this->_class->getNamedQuery($queryName)); } /** * Creates a native SQL query. * * @param string $queryName * * @return NativeQuery */ public function createNativeNamedQuery($queryName) { $queryMapping = $this->_class->getNamedNativeQuery($queryName); $rsm = new Query\ResultSetMappingBuilder($this->_em); $rsm->addNamedNativeQueryMapping($this->_class, $queryMapping); return $this->_em->createNativeQuery($queryMapping['query'], $rsm); } /** * Clears the repository, causing all managed entities to become detached. * * @return void */ public function clear() { $this->_em->clear($this->_class->rootEntityName); } /** * Finds an entity by its primary key / identifier. * * @param mixed $id The identifier. * @param int $lockMode The lock mode. * @param int|null $lockVersion The lock version. * * @return object|null The entity instance or NULL if the entity can not be found. */ public function find($id, $lockMode = LockMode::NONE, $lockVersion = null) { return $this->_em->find($this->_entityName, $id, $lockMode, $lockVersion); } /** * Finds all entities in the repository. * * @return array The entities. */ public function findAll() { return $this->findBy(array()); } /** * Finds entities by a set of criteria. * * @param array $criteria * @param array|null $orderBy * @param int|null $limit * @param int|null $offset * * @return array The objects. */ public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) { $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); return $persister->loadAll($criteria, $orderBy, $limit, $offset); } /** * Finds a single entity by a set of criteria. * * @param array $criteria * @param array|null $orderBy * * @return object|null The entity instance or NULL if the entity can not be found. */ public function findOneBy(array $criteria, array $orderBy = null) { $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); return $persister->load($criteria, null, null, array(), 0, 1, $orderBy); } /** * Adds support for magic finders. * * @param string $method * @param array $arguments * * @return array|object The found entity/entities. * * @throws ORMException * @throws \BadMethodCallException If the method called is an invalid find* method * or no find* method at all and therefore an invalid * method call. */ public function __call($method, $arguments) { switch (true) { case (0 === strpos($method, 'findBy')): $by = substr($method, 6); $method = 'findBy'; break; case (0 === strpos($method, 'findOneBy')): $by = substr($method, 9); $method = 'findOneBy'; break; default: throw new \BadMethodCallException( "Undefined method '$method'. The method name must start with ". "either findBy or findOneBy!" ); } if (empty($arguments)) { throw ORMException::findByRequiresParameter($method . $by); } $fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by)); if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) { switch (count($arguments)) { case 1: return $this->$method(array($fieldName => $arguments[0])); case 2: return $this->$method(array($fieldName => $arguments[0]), $arguments[1]); case 3: return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2]); case 4: return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2], $arguments[3]); default: // Do nothing } } throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by); } /** * @return string */ protected function getEntityName() { return $this->_entityName; } /** * @return string */ public function getClassName() { return $this->getEntityName(); } /** * @return EntityManager */ protected function getEntityManager() { return $this->_em; } /** * @return Mapping\ClassMetadata */ protected function getClassMetadata() { return $this->_class; } /** * Select all elements from a selectable that match the expression and * return a new collection containing these elements. * * @param \Doctrine\Common\Collections\Criteria $criteria * * @return \Doctrine\Common\Collections\Collection */ public function matching(Criteria $criteria) { $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); return new ArrayCollection($persister->loadCriteria($criteria)); } } doctrine2-2.4.8/lib/Doctrine/ORM/Event/000077500000000000000000000000001257105210500174475ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Event/LifecycleEventArgs.php000066400000000000000000000034321257105210500237000ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Event; use Doctrine\ORM\EntityManager; use Doctrine\Common\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs; /** * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions * of entities. * * @link www.doctrine-project.org * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ class LifecycleEventArgs extends BaseLifecycleEventArgs { /** * Retrieves associated Entity. * * @return object */ public function getEntity() { return $this->getObject(); } /** * Retrieves associated EntityManager. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->getObjectManager(); } } doctrine2-2.4.8/lib/Doctrine/ORM/Event/ListenersInvoker.php000066400000000000000000000100431257105210500234640ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Event; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManager; use Doctrine\Common\EventArgs; /** * A method invoker based on entity lifecycle. * * @author Fabio B. Silva * @since 2.4 */ class ListenersInvoker { const INVOKE_NONE = 0; const INVOKE_LISTENERS = 1; const INVOKE_CALLBACKS = 2; const INVOKE_MANAGER = 4; /** * @var \Doctrine\ORM\Mapping\EntityListenerResolver The Entity listener resolver. */ private $resolver; /** * The EventManager used for dispatching events. * * @var \Doctrine\Common\EventManager */ private $eventManager; /** * Initializes a new ListenersInvoker instance. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->eventManager = $em->getEventManager(); $this->resolver = $em->getConfiguration()->getEntityListenerResolver(); } /** * Get the subscribed event systems * * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. * @param string $eventName The entity lifecycle event. * * @return integer Bitmask of subscribed event systems. */ public function getSubscribedSystems(ClassMetadata $metadata, $eventName) { $invoke = self::INVOKE_NONE; if (isset($metadata->lifecycleCallbacks[$eventName])) { $invoke |= self::INVOKE_CALLBACKS; } if (isset($metadata->entityListeners[$eventName])) { $invoke |= self::INVOKE_LISTENERS; } if ($this->eventManager->hasListeners($eventName)) { $invoke |= self::INVOKE_MANAGER; } return $invoke; } /** * Dispatches the lifecycle event of the given entity. * * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. * @param string $eventName The entity lifecycle event. * @param object $entity The Entity on which the event occurred. * @param \Doctrine\Common\EventArgs $event The Event args. * @param integer $invoke Bitmask to invoke listeners. */ public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke) { if($invoke & self::INVOKE_CALLBACKS) { foreach ($metadata->lifecycleCallbacks[$eventName] as $callback) { $entity->$callback($event); } } if($invoke & self::INVOKE_LISTENERS) { foreach ($metadata->entityListeners[$eventName] as $listener) { $class = $listener['class']; $method = $listener['method']; $instance = $this->resolver->resolve($class); $instance->$method($entity, $event); } } if($invoke & self::INVOKE_MANAGER) { $this->eventManager->dispatchEvent($eventName, $event); } } }doctrine2-2.4.8/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php000066400000000000000000000031511257105210500253050ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\EntityManager; use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClassMetadataEventArgs; /** * Class that holds event arguments for a loadMetadata event. * * @author Jonathan H. Wage * @since 2.0 */ class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs { /** * Retrieve associated EntityManager. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->getObjectManager(); } } doctrine2-2.4.8/lib/Doctrine/ORM/Event/OnClearEventArgs.php000066400000000000000000000046471257105210500233350ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Event; use Doctrine\ORM\EntityManager; /** * Provides event arguments for the onClear event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ class OnClearEventArgs extends \Doctrine\Common\EventArgs { /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * @var string */ private $entityClass; /** * Constructor. * * @param \Doctrine\ORM\EntityManager $em * @param string|null $entityClass Optional entity class. */ public function __construct(EntityManager $em, $entityClass = null) { $this->em = $em; $this->entityClass = $entityClass; } /** * Retrieves associated EntityManager. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } /** * Name of the entity class that is cleared, or empty if all are cleared. * * @return string|null */ public function getEntityClass() { return $this->entityClass; } /** * Checks if event clears all entities. * * @return bool */ public function clearsAllEntities() { return ($this->entityClass === null); } } doctrine2-2.4.8/lib/Doctrine/ORM/Event/OnFlushEventArgs.php000066400000000000000000000035221257105210500233570ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs; use Doctrine\ORM\EntityManager; /** * Provides event arguments for the preFlush event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ class OnFlushEventArgs extends EventArgs { /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * Constructor. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; } /** * Retrieve associated EntityManager. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } } doctrine2-2.4.8/lib/Doctrine/ORM/Event/PostFlushEventArgs.php000066400000000000000000000034311257105210500237270ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Event; use Doctrine\ORM\EntityManager; use Doctrine\Common\EventArgs; /** * Provides event arguments for the postFlush event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @author Daniel Freudenberger */ class PostFlushEventArgs extends EventArgs { /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * Constructor. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; } /** * Retrieves associated EntityManager. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } } doctrine2-2.4.8/lib/Doctrine/ORM/Event/PreFlushEventArgs.php000066400000000000000000000034411257105210500235310ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs; use Doctrine\ORM\EntityManager; /** * Provides event arguments for the preFlush event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ class PreFlushEventArgs extends EventArgs { /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * Constructor. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; } /** * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } } doctrine2-2.4.8/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php000066400000000000000000000070271257105210500236760ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs; use Doctrine\ORM\EntityManager; /** * Class that holds event arguments for a preInsert/preUpdate event. * * @author Guilherme Blanco * @author Roman Borschel * @author Benjamin Eberlei * @since 2.0 */ class PreUpdateEventArgs extends LifecycleEventArgs { /** * @var array */ private $entityChangeSet; /** * Constructor. * * @param object $entity * @param EntityManager $em * @param array $changeSet */ public function __construct($entity, EntityManager $em, array &$changeSet) { parent::__construct($entity, $em); $this->entityChangeSet = &$changeSet; } /** * Retrieves entity changeset. * * @return array */ public function getEntityChangeSet() { return $this->entityChangeSet; } /** * Checks if field has a changeset. * * @param string $field * * @return boolean */ public function hasChangedField($field) { return isset($this->entityChangeSet[$field]); } /** * Gets the old value of the changeset of the changed field. * * @param string $field * * @return mixed */ public function getOldValue($field) { $this->assertValidField($field); return $this->entityChangeSet[$field][0]; } /** * Gets the new value of the changeset of the changed field. * * @param string $field * * @return mixed */ public function getNewValue($field) { $this->assertValidField($field); return $this->entityChangeSet[$field][1]; } /** * Sets the new value of this field. * * @param string $field * @param mixed $value * * @return void */ public function setNewValue($field, $value) { $this->assertValidField($field); $this->entityChangeSet[$field][1] = $value; } /** * Asserts the field exists in changeset. * * @param string $field * * @return void * * @throws \InvalidArgumentException */ private function assertValidField($field) { if ( ! isset($this->entityChangeSet[$field])) { throw new \InvalidArgumentException(sprintf( 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', $field, get_class($this->getEntity()) )); } } } doctrine2-2.4.8/lib/Doctrine/ORM/Events.php000066400000000000000000000121221257105210500203410ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * Container for all ORM events. * * This class cannot be instantiated. * * @author Roman Borschel * @since 2.0 */ final class Events { /** * Private constructor. This class is not meant to be instantiated. */ private function __construct() { } /** * The preRemove event occurs for a given entity before the respective * EntityManager remove operation for that entity is executed. * * This is an entity lifecycle event. * * @var string */ const preRemove = 'preRemove'; /** * The postRemove event occurs for an entity after the entity has * been deleted. It will be invoked after the database delete operations. * * This is an entity lifecycle event. * * @var string */ const postRemove = 'postRemove'; /** * The prePersist event occurs for a given entity before the respective * EntityManager persist operation for that entity is executed. * * This is an entity lifecycle event. * * @var string */ const prePersist = 'prePersist'; /** * The postPersist event occurs for an entity after the entity has * been made persistent. It will be invoked after the database insert operations. * Generated primary key values are available in the postPersist event. * * This is an entity lifecycle event. * * @var string */ const postPersist = 'postPersist'; /** * The preUpdate event occurs before the database update operations to * entity data. * * This is an entity lifecycle event. * * @var string */ const preUpdate = 'preUpdate'; /** * The postUpdate event occurs after the database update operations to * entity data. * * This is an entity lifecycle event. * * @var string */ const postUpdate = 'postUpdate'; /** * The postLoad event occurs for an entity after the entity has been loaded * into the current EntityManager from the database or after the refresh operation * has been applied to it. * * Note that the postLoad event occurs for an entity before any associations have been * initialized. Therefore it is not safe to access associations in a postLoad callback * or event handler. * * This is an entity lifecycle event. * * @var string */ const postLoad = 'postLoad'; /** * The loadClassMetadata event occurs after the mapping metadata for a class * has been loaded from a mapping source (annotations/xml/yaml). * * @var string */ const loadClassMetadata = 'loadClassMetadata'; /** * The preFlush event occurs when the EntityManager#flush() operation is invoked, * but before any changes to managed entities have been calculated. This event is * always raised right after EntityManager#flush() call. */ const preFlush = 'preFlush'; /** * The onFlush event occurs when the EntityManager#flush() operation is invoked, * after any changes to managed entities have been determined but before any * actual database operations are executed. The event is only raised if there is * actually something to do for the underlying UnitOfWork. If nothing needs to be done, * the onFlush event is not raised. * * @var string */ const onFlush = 'onFlush'; /** * The postFlush event occurs when the EntityManager#flush() operation is invoked and * after all actual database operations are executed successfully. The event is only raised if there is * actually something to do for the underlying UnitOfWork. If nothing needs to be done, * the postFlush event is not raised. The event won't be raised if an error occurs during the * flush operation. * * @var string */ const postFlush = 'postFlush'; /** * The onClear event occurs when the EntityManager#clear() operation is invoked, * after all references to entities have been removed from the unit of work. * * @var string */ const onClear = 'onClear'; } doctrine2-2.4.8/lib/Doctrine/ORM/Id/000077500000000000000000000000001257105210500167225ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Id/AbstractIdGenerator.php000066400000000000000000000034061257105210500233250ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; abstract class AbstractIdGenerator { /** * Generates an identifier for an entity. * * @param \Doctrine\ORM\EntityManager $em * @param \Doctrine\ORM\Mapping\Entity $entity * * @return mixed */ abstract public function generate(EntityManager $em, $entity); /** * Gets whether this generator is a post-insert generator which means that * {@link generate()} must be called after the entity has been inserted * into the database. * * By default, this method returns FALSE. Generators that have this requirement * must override this method and return TRUE. * * @return boolean */ public function isPostInsertGenerator() { return false; } } doctrine2-2.4.8/lib/Doctrine/ORM/Id/AssignedGenerator.php000066400000000000000000000052461257105210500230460ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; use Doctrine\ORM\ORMException; /** * Special generator for application-assigned identifiers (doesn't really generate anything). * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class AssignedGenerator extends AbstractIdGenerator { /** * Returns the identifier assigned to the given entity. * * @param EntityManager $em * @param object $entity * * @return mixed * * @throws \Doctrine\ORM\ORMException * * @override */ public function generate(EntityManager $em, $entity) { $class = $em->getClassMetadata(get_class($entity)); $idFields = $class->getIdentifierFieldNames(); $identifier = array(); foreach ($idFields as $idField) { $value = $class->getFieldValue($entity, $idField); if ( ! isset($value)) { throw ORMException::entityMissingAssignedIdForField($entity, $idField); } 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. $value = current($em->getUnitOfWork()->getEntityIdentifier($value)); } $identifier[$idField] = $value; } return $identifier; } } doctrine2-2.4.8/lib/Doctrine/ORM/Id/BigIntegerIdentityGenerator.php000066400000000000000000000042571257105210500250430ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; /** * Id generator that obtains IDs from special "identity" columns. These are columns * that automatically get a database-generated, auto-incremented identifier on INSERT. * This generator obtains the last insert id after such an insert. */ class BigIntegerIdentityGenerator extends AbstractIdGenerator { /** * The name of the sequence to pass to lastInsertId(), if any. * * @var string */ private $sequenceName; /** * Constructor. * * @param string|null $sequenceName The name of the sequence to pass to lastInsertId() * to obtain the last generated identifier within the current * database session/connection, if any. */ public function __construct($sequenceName = null) { $this->sequenceName = $sequenceName; } /** * {@inheritdoc} */ public function generate(EntityManager $em, $entity) { return (string)$em->getConnection()->lastInsertId($this->sequenceName); } /** * {@inheritdoc} */ public function isPostInsertGenerator() { return true; } } doctrine2-2.4.8/lib/Doctrine/ORM/Id/IdentityGenerator.php000066400000000000000000000042411257105210500230740ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; /** * Id generator that obtains IDs from special "identity" columns. These are columns * that automatically get a database-generated, auto-incremented identifier on INSERT. * This generator obtains the last insert id after such an insert. */ class IdentityGenerator extends AbstractIdGenerator { /** * The name of the sequence to pass to lastInsertId(), if any. * * @var string */ private $sequenceName; /** * Constructor. * * @param string|null $sequenceName The name of the sequence to pass to lastInsertId() * to obtain the last generated identifier within the current * database session/connection, if any. */ public function __construct($sequenceName = null) { $this->sequenceName = $sequenceName; } /** * {@inheritdoc} */ public function generate(EntityManager $em, $entity) { return (int)$em->getConnection()->lastInsertId($this->sequenceName); } /** * {@inheritdoc} */ public function isPostInsertGenerator() { return true; } } doctrine2-2.4.8/lib/Doctrine/ORM/Id/SequenceGenerator.php000066400000000000000000000071211257105210500230530ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Id; use Serializable; use Doctrine\ORM\EntityManager; /** * Represents an ID generator that uses a database sequence. * * @since 2.0 * @author Roman Borschel */ class SequenceGenerator extends AbstractIdGenerator implements Serializable { /** * The allocation size of the sequence. * * @var int */ private $_allocationSize; /** * The name of the sequence. * * @var string */ private $_sequenceName; /** * @var int */ private $_nextValue = 0; /** * @var int|null */ private $_maxValue = null; /** * Initializes a new sequence generator. * * @param string $sequenceName The name of the sequence. * @param integer $allocationSize The allocation size of the sequence. */ public function __construct($sequenceName, $allocationSize) { $this->_sequenceName = $sequenceName; $this->_allocationSize = $allocationSize; } /** * Generates an ID for the given entity. * * @param EntityManager $em * @param object $entity * * @return integer The generated value. * * @override */ public function generate(EntityManager $em, $entity) { if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) { // Allocate new values $conn = $em->getConnection(); $sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName); $this->_nextValue = (int)$conn->fetchColumn($sql); $this->_maxValue = $this->_nextValue + $this->_allocationSize; } return $this->_nextValue++; } /** * Gets the maximum value of the currently allocated bag of values. * * @return integer|null */ public function getCurrentMaxValue() { return $this->_maxValue; } /** * Gets the next value that will be returned by generate(). * * @return integer */ public function getNextValue() { return $this->_nextValue; } /** * @return string */ public function serialize() { return serialize(array( 'allocationSize' => $this->_allocationSize, 'sequenceName' => $this->_sequenceName )); } /** * @param string $serialized * * @return void */ public function unserialize($serialized) { $array = unserialize($serialized); $this->_sequenceName = $array['sequenceName']; $this->_allocationSize = $array['allocationSize']; } } doctrine2-2.4.8/lib/Doctrine/ORM/Id/TableGenerator.php000066400000000000000000000070601257105210500223340ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; /** * Id generator that uses a single-row database table and a hi/lo algorithm. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class TableGenerator extends AbstractIdGenerator { /** * @var string */ private $_tableName; /** * @var string */ private $_sequenceName; /** * @var int */ private $_allocationSize; /** * @var int|null */ private $_nextValue; /** * @var int|null */ private $_maxValue; /** * @param string $tableName * @param string $sequenceName * @param int $allocationSize */ public function __construct($tableName, $sequenceName = 'default', $allocationSize = 10) { $this->_tableName = $tableName; $this->_sequenceName = $sequenceName; $this->_allocationSize = $allocationSize; } /** * {@inheritdoc} */ public function generate(EntityManager $em, $entity) { if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) { // Allocate new values $conn = $em->getConnection(); if ($conn->getTransactionNestingLevel() === 0) { // use select for update $sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->_tableName, $this->_sequenceName); $currentLevel = $conn->fetchColumn($sql); if ($currentLevel != null) { $this->_nextValue = $currentLevel; $this->_maxValue = $this->_nextValue + $this->_allocationSize; $updateSql = $conn->getDatabasePlatform()->getTableHiLoUpdateNextValSql( $this->_tableName, $this->_sequenceName, $this->_allocationSize ); if ($conn->executeUpdate($updateSql, array(1 => $currentLevel, 2 => $currentLevel+1)) !== 1) { // no affected rows, concurrency issue, throw exception } } else { // no current level returned, TableGenerator seems to be broken, throw exception } } else { // only table locks help here, implement this or throw exception? // or do we want to work with table locks exclusively? } } return $this->_nextValue++; } } doctrine2-2.4.8/lib/Doctrine/ORM/Id/UuidGenerator.php000066400000000000000000000032751257105210500222170ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; /** * Represents an ID generator that uses the database UUID expression * * @since 2.3 * @author Maarten de Keizer */ class UuidGenerator extends AbstractIdGenerator { /** * Generates an ID for the given entity. * * @param EntityManager $em The EntityManager to use. * @param object $entity * * @return string The generated value. * * @override */ public function generate(EntityManager $em, $entity) { $conn = $em->getConnection(); $sql = 'SELECT ' . $conn->getDatabasePlatform()->getGuidExpression(); return $conn->query($sql)->fetchColumn(0); } } doctrine2-2.4.8/lib/Doctrine/ORM/Internal/000077500000000000000000000000001257105210500201425ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php000066400000000000000000000102061257105210500251100ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Internal; /** * The CommitOrderCalculator is used by the UnitOfWork to sort out the * correct order in which changes to entities need to be persisted. * * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco */ class CommitOrderCalculator { const NOT_VISITED = 1; const IN_PROGRESS = 2; const VISITED = 3; /** * @var array */ private $_nodeStates = array(); /** * The nodes to sort. * * @var array */ private $_classes = array(); /** * @var array */ private $_relatedClasses = array(); /** * @var array */ private $_sorted = array(); /** * Clears the current graph. * * @return void */ public function clear() { $this->_classes = $this->_relatedClasses = array(); } /** * Gets a valid commit order for all current nodes. * * Uses a depth-first search (DFS) to traverse the graph. * The desired topological sorting is the reverse postorder of these searches. * * @return array The list of ordered classes. */ public function getCommitOrder() { // Check whether we need to do anything. 0 or 1 node is easy. $nodeCount = count($this->_classes); if ($nodeCount <= 1) { return ($nodeCount == 1) ? array_values($this->_classes) : array(); } // Init foreach ($this->_classes as $node) { $this->_nodeStates[$node->name] = self::NOT_VISITED; } // Go foreach ($this->_classes as $node) { if ($this->_nodeStates[$node->name] == self::NOT_VISITED) { $this->_visitNode($node); } } $sorted = array_reverse($this->_sorted); $this->_sorted = $this->_nodeStates = array(); return $sorted; } /** * @param \Doctrine\ORM\Mapping\ClassMetadata $node * * @return void */ private function _visitNode($node) { $this->_nodeStates[$node->name] = self::IN_PROGRESS; if (isset($this->_relatedClasses[$node->name])) { foreach ($this->_relatedClasses[$node->name] as $relatedNode) { if ($this->_nodeStates[$relatedNode->name] == self::NOT_VISITED) { $this->_visitNode($relatedNode); } } } $this->_nodeStates[$node->name] = self::VISITED; $this->_sorted[] = $node; } /** * @param \Doctrine\ORM\Mapping\ClassMetadata $fromClass * @param \Doctrine\ORM\Mapping\ClassMetadata $toClass * * @return void */ public function addDependency($fromClass, $toClass) { $this->_relatedClasses[$fromClass->name][] = $toClass; } /** * @param string $className * * @return bool */ public function hasClass($className) { return isset($this->_classes[$className]); } /** * @param \Doctrine\ORM\Mapping\ClassMetadata $class * * @return void */ public function addClass($class) { $this->_classes[$class->name] = $class; } } doctrine2-2.4.8/lib/Doctrine/ORM/Internal/Hydration/000077500000000000000000000000001257105210500221035ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php000066400000000000000000000375471257105210500261140ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Internal\Hydration; use PDO; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Events; use Doctrine\ORM\Mapping\ClassMetadata; /** * Base class for all hydrators. A hydrator is a class that provides some form * of transformation of an SQL result set into another structure. * * @since 2.0 * @author Konsta Vesterinen * @author Roman Borschel * @author Guilherme Blanco */ abstract class AbstractHydrator { /** * The ResultSetMapping. * * @var \Doctrine\ORM\Query\ResultSetMapping */ protected $_rsm; /** * The EntityManager instance. * * @var EntityManager */ protected $_em; /** * The dbms Platform instance. * * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ protected $_platform; /** * The UnitOfWork of the associated EntityManager. * * @var \Doctrine\ORM\UnitOfWork */ protected $_uow; /** * The cache used during row-by-row hydration. * * @var array */ protected $_cache = array(); /** * The statement that provides the data to hydrate. * * @var \Doctrine\DBAL\Driver\Statement */ protected $_stmt; /** * The query hints. * * @var array */ protected $_hints; /** * Initializes a new instance of a class derived from AbstractHydrator. * * @param \Doctrine\ORM\EntityManager $em The EntityManager to use. */ public function __construct(EntityManager $em) { $this->_em = $em; $this->_platform = $em->getConnection()->getDatabasePlatform(); $this->_uow = $em->getUnitOfWork(); } /** * Initiates a row-by-row hydration. * * @param object $stmt * @param object $resultSetMapping * @param array $hints * * @return IterableResult */ public function iterate($stmt, $resultSetMapping, array $hints = array()) { $this->_stmt = $stmt; $this->_rsm = $resultSetMapping; $this->_hints = $hints; $evm = $this->_em->getEventManager(); $evm->addEventListener(array(Events::onClear), $this); $this->prepare(); return new IterableResult($this); } /** * Hydrates all rows returned by the passed statement instance at once. * * @param object $stmt * @param object $resultSetMapping * @param array $hints * * @return array */ public function hydrateAll($stmt, $resultSetMapping, array $hints = array()) { $this->_stmt = $stmt; $this->_rsm = $resultSetMapping; $this->_hints = $hints; $this->prepare(); $result = $this->hydrateAllData(); $this->cleanup(); return $result; } /** * Hydrates a single row returned by the current statement instance during * row-by-row hydration with {@link iterate()}. * * @return mixed */ public function hydrateRow() { $row = $this->_stmt->fetch(PDO::FETCH_ASSOC); if ( ! $row) { $this->cleanup(); return false; } $result = array(); $this->hydrateRowData($row, $this->_cache, $result); return $result; } /** * Executes one-time preparation tasks, once each time hydration is started * through {@link hydrateAll} or {@link iterate()}. * * @return void */ protected function prepare() { } /** * Executes one-time cleanup tasks at the end of a hydration that was initiated * through {@link hydrateAll} or {@link iterate()}. * * @return void */ protected function cleanup() { $this->_rsm = null; $this->_stmt->closeCursor(); $this->_stmt = null; } /** * Hydrates a single row from the current statement instance. * * Template method. * * @param array $data The row data. * @param array $cache The cache to use. * @param array $result The result to fill. * * @return void * * @throws HydrationException */ protected function hydrateRowData(array $data, array &$cache, array &$result) { throw new HydrationException("hydrateRowData() not implemented by this hydrator."); } /** * Hydrates all rows from the current statement instance at once. * * @return array */ abstract protected function hydrateAllData(); /** * Processes a row of the result set. * * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY). * Puts the elements of a result row into a new array, grouped by the dql alias * they belong to. The column names in the result set are mapped to their * field names during this procedure as well as any necessary conversions on * the values applied. Scalar values are kept in a specific key 'scalars'. * * @param array $data SQL Result Row. * @param array &$cache Cache for column to field result information. * @param array &$id Dql-Alias => ID-Hash. * @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value? * * @return array An array with all the fields (name => value) of the data row, * grouped by their component alias. */ protected function gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents) { $rowData = array(); foreach ($data as $key => $value) { // Parse each column name only once. Cache the results. if ( ! isset($cache[$key])) { switch (true) { // NOTE: Most of the times it's a field mapping, so keep it first!!! case (isset($this->_rsm->fieldMappings[$key])): $fieldName = $this->_rsm->fieldMappings[$key]; $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); $cache[$key]['fieldName'] = $fieldName; $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; break; case (isset($this->_rsm->scalarMappings[$key])): $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; $cache[$key]['type'] = Type::getType($this->_rsm->typeMappings[$key]); $cache[$key]['isScalar'] = true; break; case (isset($this->_rsm->metaMappings[$key])): // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). $fieldName = $this->_rsm->metaMappings[$key]; $classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$this->_rsm->columnOwnerMap[$key]]); $cache[$key]['isMetaColumn'] = true; $cache[$key]['fieldName'] = $fieldName; $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]); break; default: // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. continue 2; } if (isset($this->_rsm->newObjectMappings[$key])) { $mapping = $this->_rsm->newObjectMappings[$key]; $cache[$key]['isNewObjectParameter'] = true; $cache[$key]['argIndex'] = $mapping['argIndex']; $cache[$key]['objIndex'] = $mapping['objIndex']; $cache[$key]['class'] = new \ReflectionClass($mapping['className']); } } if (isset($cache[$key]['isNewObjectParameter'])) { $class = $cache[$key]['class']; $argIndex = $cache[$key]['argIndex']; $objIndex = $cache[$key]['objIndex']; $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); $rowData['newObjects'][$objIndex]['class'] = $class; $rowData['newObjects'][$objIndex]['args'][$argIndex] = $value; } if (isset($cache[$key]['isScalar'])) { $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); $rowData['scalars'][$cache[$key]['fieldName']] = $value; continue; } $dqlAlias = $cache[$key]['dqlAlias']; if ($cache[$key]['isIdentifier']) { $id[$dqlAlias] .= '|' . $value; } if (isset($cache[$key]['isMetaColumn'])) { if ( ! isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value !== null) { $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; if ($cache[$key]['isIdentifier']) { $nonemptyComponents[$dqlAlias] = true; } } continue; } // in an inheritance hierarchy the same field could be defined several times. // We overwrite this value so long we don't have a non-null value, that value we keep. // Per definition it cannot be that a field is defined several times and has several values. if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) { continue; } $rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) { $nonemptyComponents[$dqlAlias] = true; } } return $rowData; } /** * Processes a row of the result set. * * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that * simply converts column names to field names and properly converts the * values according to their types. The resulting row has the same number * of elements as before. * * @param array $data * @param array $cache * * @return array The processed row. */ protected function gatherScalarRowData(&$data, &$cache) { $rowData = array(); foreach ($data as $key => $value) { // Parse each column name only once. Cache the results. if ( ! isset($cache[$key])) { switch (true) { // NOTE: During scalar hydration, most of the times it's a scalar mapping, keep it first!!! case (isset($this->_rsm->scalarMappings[$key])): $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; $cache[$key]['isScalar'] = true; break; case (isset($this->_rsm->fieldMappings[$key])): $fieldName = $this->_rsm->fieldMappings[$key]; $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); $cache[$key]['fieldName'] = $fieldName; $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; break; case (isset($this->_rsm->metaMappings[$key])): // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). $cache[$key]['isMetaColumn'] = true; $cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key]; $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; break; default: // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. continue 2; } } $fieldName = $cache[$key]['fieldName']; switch (true) { case (isset($cache[$key]['isScalar'])): $rowData[$fieldName] = $value; break; case (isset($cache[$key]['isMetaColumn'])): $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; break; default: $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; } } return $rowData; } /** * Register entity as managed in UnitOfWork. * * @param ClassMetadata $class * @param object $entity * @param array $data * * @return void * * @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow */ protected function registerManaged(ClassMetadata $class, $entity, array $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); } /** * When executed in a hydrate() loop we have to clear internal state to * decrease memory consumption. * * @param mixed $eventArgs * * @return void */ public function onClear($eventArgs) { } } doctrine2-2.4.8/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php000066400000000000000000000256151257105210500254200ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Internal\Hydration; use PDO; use Doctrine\ORM\Mapping\ClassMetadata; /** * The ArrayHydrator produces a nested array "graph" that is often (not always) * interchangeable with the corresponding object graph for read-only access. * * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco */ class ArrayHydrator extends AbstractHydrator { /** * @var array */ private $_ce = array(); /** * @var array */ private $_rootAliases = array(); /** * @var bool */ private $_isSimpleQuery = false; /** * @var array */ private $_identifierMap = array(); /** * @var array */ private $_resultPointers = array(); /** * @var array */ private $_idTemplate = array(); /** * @var int */ private $_resultCounter = 0; /** * {@inheritdoc} */ protected function prepare() { $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1; $this->_identifierMap = array(); $this->_resultPointers = array(); $this->_idTemplate = array(); $this->_resultCounter = 0; foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); $this->_resultPointers[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; } } /** * {@inheritdoc} */ protected function hydrateAllData() { $result = array(); $cache = array(); while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { $this->hydrateRowData($data, $cache, $result); } return $result; } /** * {@inheritdoc} */ protected function hydrateRowData(array $row, array &$cache, array &$result) { // 1) Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { $scalars = $rowData['scalars']; unset($rowData['scalars']); if (empty($rowData)) { ++$this->_resultCounter; } } // 2) Now hydrate the data found in the current row. foreach ($rowData as $dqlAlias => $data) { $index = false; if (isset($this->_rsm->parentAliasMap[$dqlAlias])) { // It's a joined result $parent = $this->_rsm->parentAliasMap[$dqlAlias]; $path = $parent . '.' . $dqlAlias; // missing parent data, skipping as RIGHT JOIN hydration is not supported. if ( ! isset($nonemptyComponents[$parent]) ) { continue; } // Get a reference to the right element in the result tree. // This element will get the associated element attached. if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) { $first = reset($this->_resultPointers); // TODO: Exception if $key === null ? $baseElement =& $this->_resultPointers[$parent][key($first)]; } else if (isset($this->_resultPointers[$parent])) { $baseElement =& $this->_resultPointers[$parent]; } else { unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 continue; } $relationAlias = $this->_rsm->relationMap[$dqlAlias]; $relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias]; // Check the type of the relation (many or single-valued) if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { $oneToOne = false; if (isset($nonemptyComponents[$dqlAlias])) { if ( ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = array(); } $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; if ( ! $indexExists || ! $indexIsValid) { $element = $data; if (isset($this->_rsm->indexByMap[$dqlAlias])) { $baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element; } else { $baseElement[$relationAlias][] = $element; } end($baseElement[$relationAlias]); $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]); } } else if ( ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = array(); } } else { $oneToOne = true; if ( ( ! isset($nonemptyComponents[$dqlAlias])) && ( ! isset($baseElement[$relationAlias])) ) { $baseElement[$relationAlias] = null; } else if ( ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = $data; } } $coll =& $baseElement[$relationAlias]; if (is_array($coll)) { $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne); } } else { // It's a root result element $this->_rootAliases[$dqlAlias] = true; // Mark as root $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0; // if this row has a NULL value for the root result id then make it a null result. if ( ! isset($nonemptyComponents[$dqlAlias]) ) { $result[] = $this->_rsm->isMixed ? array($entityKey => null) : null; $resultKey = $this->_resultCounter; ++$this->_resultCounter; continue; } // Check for an existing element if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $this->_rsm->isMixed ? array($entityKey => $rowData[$dqlAlias]) : $rowData[$dqlAlias]; if (isset($this->_rsm->indexByMap[$dqlAlias])) { $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]]; $result[$resultKey] = $element; } else { $resultKey = $this->_resultCounter; $result[] = $element; ++$this->_resultCounter; } $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; } else { $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; $resultKey = $index; /*if ($this->_rsm->isMixed) { $result[] =& $result[$index]; ++$this->_resultCounter; }*/ } $this->updateResultPointer($result, $index, $dqlAlias, false); } } // Append scalar values to mixed result sets if (isset($scalars)) { if ( ! isset($resultKey) ) { // this only ever happens when no object is fetched (scalar result only) $resultKey = isset($this->_rsm->indexByMap['scalars']) ? $row[$this->_rsm->indexByMap['scalars']] : $this->_resultCounter - 1; } foreach ($scalars as $name => $value) { $result[$resultKey][$name] = $value; } } } /** * Updates the result pointer for an Entity. The result pointers point to the * last seen instance of each Entity type. This is used for graph construction. * * @param array $coll The element. * @param boolean|integer $index Index of the element in the collection. * @param string $dqlAlias * @param boolean $oneToOne Whether it is a single-valued association or not. * * @return void */ private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne) { if ($coll === null) { unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 return; } if ($oneToOne) { $this->_resultPointers[$dqlAlias] =& $coll; return; } if ($index !== false) { $this->_resultPointers[$dqlAlias] =& $coll[$index]; return; } if ( ! $coll) { return; } end($coll); $this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; return; } /** * Retrieve ClassMetadata associated to entity class name. * * @param string $className * * @return \Doctrine\ORM\Mapping\ClassMetadata */ private function getClassMetadata($className) { if ( ! isset($this->_ce[$className])) { $this->_ce[$className] = $this->_em->getClassMetadata($className); } return $this->_ce[$className]; } } doctrine2-2.4.8/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php000066400000000000000000000066641257105210500264500ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Internal\Hydration; class HydrationException extends \Doctrine\ORM\ORMException { /** * @return HydrationException */ public static function nonUniqueResult() { return new self("The result returned by the query was not unique."); } /** * @param string $alias * @param string $parentAlias * * @return HydrationException */ public static function parentObjectOfRelationNotFound($alias, $parentAlias) { return new self("The parent object of entity result with alias '$alias' was not found." . " The parent alias is '$parentAlias'."); } /** * @param string $dqlAlias * * @return HydrationException */ public static function emptyDiscriminatorValue($dqlAlias) { return new self("The DQL alias '" . $dqlAlias . "' contains an entity ". "of an inheritance hierarchy with an empty discriminator value. This means " . "that the database contains inconsistent data with an empty " . "discriminator value in a table row." ); } /** * @since 2.3 * * @param string $entityName * @param string $discrColumnName * @param string $dqlAlias * * @return HydrationException */ public static function missingDiscriminatorColumn($entityName, $discrColumnName, $dqlAlias) { return new self(sprintf( 'The discriminator column "%s" is missing for "%s" using the DQL alias "%s".', $discrColumnName, $entityName, $dqlAlias )); } /** * @since 2.3 * * @param string $entityName * @param string $discrColumnName * @param string $dqlAlias * * @return HydrationException */ public static function missingDiscriminatorMetaMappingColumn($entityName, $discrColumnName, $dqlAlias) { return new self(sprintf( 'The meta mapping for the discriminator column "%s" is missing for "%s" using the DQL alias "%s".', $discrColumnName, $entityName, $dqlAlias )); } /** * @param string $discrValue * @param array $discrMap * * @return HydrationException */ public static function invalidDiscriminatorValue($discrValue, $discrMap) { return new self(sprintf( 'The discriminator value "%s" is invalid. It must be one of "%s".', $discrValue, implode('", "', $discrMap) )); } } doctrine2-2.4.8/lib/Doctrine/ORM/Internal/Hydration/IterableResult.php000066400000000000000000000051751257105210500255520ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Internal\Hydration; /** * Represents a result structure that can be iterated over, hydrating row-by-row * during the iteration. An IterableResult is obtained by AbstractHydrator#iterate(). * * @author robo * @since 2.0 */ class IterableResult implements \Iterator { /** * @var \Doctrine\ORM\Internal\Hydration\AbstractHydrator */ private $_hydrator; /** * @var boolean */ private $_rewinded = false; /** * @var integer */ private $_key = -1; /** * @var object|null */ private $_current = null; /** * @param \Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator */ public function __construct($hydrator) { $this->_hydrator = $hydrator; } /** * @return void * * @throws HydrationException */ public function rewind() { if ($this->_rewinded == true) { throw new HydrationException("Can only iterate a Result once."); } else { $this->_current = $this->next(); $this->_rewinded = true; } } /** * Gets the next set of results. * * @return array */ public function next() { $this->_current = $this->_hydrator->hydrateRow(); $this->_key++; return $this->_current; } /** * @return mixed */ public function current() { return $this->_current; } /** * @return int */ public function key() { return $this->_key; } /** * @return bool */ public function valid() { return ($this->_current!=false); } } doctrine2-2.4.8/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php000066400000000000000000000624411257105210500255460ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Internal\Hydration; use Doctrine\ORM\UnitOfWork; use PDO; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Query; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Proxy\Proxy; /** * The ObjectHydrator constructs an object graph out of an SQL result set. * * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco * @author Fabio B. Silva * * @internal Highly performance-sensitive code. */ class ObjectHydrator extends AbstractHydrator { /** * Local ClassMetadata cache to avoid going to the EntityManager all the time. * This local cache is maintained between hydration runs and not cleared. * * @var array */ private $ce = array(); /* The following parts are reinitialized on every hydration run. */ /** * @var array */ private $identifierMap; /** * @var array */ private $resultPointers; /** * @var array */ private $idTemplate; /** * @var integer */ private $resultCounter; /** * @var array */ private $rootAliases = array(); /** * @var array */ private $initializedCollections = array(); /** * @var array */ private $existingCollections = array(); /** * {@inheritdoc} */ protected function prepare() { $this->identifierMap = $this->resultPointers = $this->idTemplate = array(); $this->resultCounter = 0; if ( ! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) { $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true; } foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->identifierMap[$dqlAlias] = array(); $this->idTemplate[$dqlAlias] = ''; if ( ! isset($this->ce[$className])) { $this->ce[$className] = $this->_em->getClassMetadata($className); } // Remember which associations are "fetch joined", so that we know where to inject // collection stubs or proxies and where not. if ( ! isset($this->_rsm->relationMap[$dqlAlias])) { continue; } if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) { throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]); } $sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; $sourceClass = $this->getClassMetadata($sourceClassName); $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; $this->_hints['fetched'][$this->_rsm->parentAliasMap[$dqlAlias]][$assoc['fieldName']] = true; if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) { continue; } // Mark any non-collection opposite sides as fetched, too. if ($assoc['mappedBy']) { $this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true; continue; } // handle fetch-joined owning side bi-directional one-to-one associations if ($assoc['inversedBy']) { $class = $this->ce[$className]; $inverseAssoc = $class->associationMappings[$assoc['inversedBy']]; if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) { continue; } $this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true; } } } /** * {@inheritdoc} */ protected function cleanup() { $eagerLoad = (isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) && $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] == true; parent::cleanup(); $this->identifierMap = $this->initializedCollections = $this->existingCollections = $this->resultPointers = array(); if ($eagerLoad) { $this->_em->getUnitOfWork()->triggerEagerLoads(); } } /** * {@inheritdoc} */ protected function hydrateAllData() { $result = array(); $cache = array(); while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { $this->hydrateRowData($row, $cache, $result); } // Take snapshots from all newly initialized collections foreach ($this->initializedCollections as $coll) { $coll->takeSnapshot(); } return $result; } /** * Initializes a related collection. * * @param object $entity The entity to which the collection belongs. * @param ClassMetadata $class * @param string $fieldName The name of the field on the entity that holds the collection. * @param string $parentDqlAlias Alias of the parent fetch joining this collection. * * @return \Doctrine\ORM\PersistentCollection */ private function initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias) { $oid = spl_object_hash($entity); $relation = $class->associationMappings[$fieldName]; $value = $class->reflFields[$fieldName]->getValue($entity); if ($value === null) { $value = new ArrayCollection; } if ( ! $value instanceof PersistentCollection) { $value = new PersistentCollection( $this->_em, $this->ce[$relation['targetEntity']], $value ); $value->setOwner($entity, $relation); $class->reflFields[$fieldName]->setValue($entity, $value); $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value); $this->initializedCollections[$oid . $fieldName] = $value; } else if ( isset($this->_hints[Query::HINT_REFRESH]) || isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) && ! $value->isInitialized() ) { // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! $value->setDirty(false); $value->setInitialized(true); $value->unwrap()->clear(); $this->initializedCollections[$oid . $fieldName] = $value; } else { // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN! $this->existingCollections[$oid . $fieldName] = $value; } return $value; } /** * Gets an entity instance. * * @param array $data The instance data. * @param string $dqlAlias The DQL alias of the entity's class. * * @return object The entity. * * @throws HydrationException */ private function getEntity(array $data, $dqlAlias) { $className = $this->_rsm->aliasMap[$dqlAlias]; if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { if ( ! isset($this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]])) { throw HydrationException::missingDiscriminatorMetaMappingColumn($className, $this->_rsm->discriminatorColumns[$dqlAlias], $dqlAlias); } $discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]]; if ( ! isset($data[$discrColumn])) { throw HydrationException::missingDiscriminatorColumn($className, $discrColumn, $dqlAlias); } if ($data[$discrColumn] === "") { throw HydrationException::emptyDiscriminatorValue($dqlAlias); } $discrMap = $this->ce[$className]->discriminatorMap; if ( ! isset($discrMap[$data[$discrColumn]])) { throw HydrationException::invalidDiscriminatorValue($data[$discrColumn], array_keys($discrMap)); } $className = $discrMap[$data[$discrColumn]]; unset($data[$discrColumn]); } if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->rootAliases[$dqlAlias])) { $this->registerManaged($this->ce[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } $this->_hints['fetchAlias'] = $dqlAlias; return $this->_uow->createEntity($className, $data, $this->_hints); } /** * @param string $className * @param array $data * * @return mixed */ 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) { 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); } } /** * Gets a ClassMetadata instance from the local cache. * If the instance is not yet in the local cache, it is loaded into the * local cache. * * @param string $className The name of the class. * * @return ClassMetadata */ private function getClassMetadata($className) { if ( ! isset($this->ce[$className])) { $this->ce[$className] = $this->_em->getClassMetadata($className); } return $this->ce[$className]; } /** * Hydrates a single row in an SQL result set. * * @internal * First, the data of the row is split into chunks where each chunk contains data * that belongs to a particular component/class. Afterwards, all these chunks * are processed, one after the other. For each chunk of class data only one of the * following code paths is executed: * * Path A: The data chunk belongs to a joined/associated object and the association * is collection-valued. * Path B: The data chunk belongs to a joined/associated object and the association * is single-valued. * Path C: The data chunk belongs to a root result element/object that appears in the topmost * level of the hydrated result. A typical example are the objects of the type * specified by the FROM clause in a DQL query. * * @param array $row The data of the row to process. * @param array $cache The cache to use. * @param array $result The result array to fill. * * @return void */ protected function hydrateRowData(array $row, array &$cache, array &$result) { // Initialize $id = $this->idTemplate; // initialize the id-memory $nonemptyComponents = array(); // Split the row data into chunks of class data. $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { $scalars = $rowData['scalars']; unset($rowData['scalars']); if (empty($rowData)) { ++$this->resultCounter; } } // Extract "new" object constructor arguments. They're appended at the end. if (isset($rowData['newObjects'])) { $newObjects = $rowData['newObjects']; unset($rowData['newObjects']); if (empty($rowData)) { ++$this->resultCounter; } } // Hydrate the data chunks foreach ($rowData as $dqlAlias => $data) { $entityName = $this->_rsm->aliasMap[$dqlAlias]; if (isset($this->_rsm->parentAliasMap[$dqlAlias])) { // It's a joined result $parentAlias = $this->_rsm->parentAliasMap[$dqlAlias]; // we need the $path to save into the identifier map which entities were already // seen for this parent-child relationship $path = $parentAlias . '.' . $dqlAlias; // We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs if ( ! isset($nonemptyComponents[$parentAlias])) { // TODO: Add special case code where we hydrate the right join objects into identity map at least continue; } // Get a reference to the parent object to which the joined element belongs. if ($this->_rsm->isMixed && isset($this->rootAliases[$parentAlias])) { $first = reset($this->resultPointers); $parentObject = $first[key($first)]; } else if (isset($this->resultPointers[$parentAlias])) { $parentObject = $this->resultPointers[$parentAlias]; } else { // Parent object of relation not found, so skip it. continue; } $parentClass = $this->ce[$this->_rsm->aliasMap[$parentAlias]]; $oid = spl_object_hash($parentObject); $relationField = $this->_rsm->relationMap[$dqlAlias]; $relation = $parentClass->associationMappings[$relationField]; $reflField = $parentClass->reflFields[$relationField]; // Check the type of the relation (many or single-valued) if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { $reflFieldValue = $reflField->getValue($parentObject); // PATH A: Collection-valued association if (isset($nonemptyComponents[$dqlAlias])) { $collKey = $oid . $relationField; if (isset($this->initializedCollections[$collKey])) { $reflFieldValue = $this->initializedCollections[$collKey]; } else if ( ! isset($this->existingCollections[$collKey])) { $reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } $indexExists = isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); $index = $indexExists ? $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false; $indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; if ( ! $indexExists || ! $indexIsValid) { if (isset($this->existingCollections[$collKey])) { // Collection exists, only look for the element in the identity map. if ($element = $this->getEntityFromIdentityMap($entityName, $data)) { $this->resultPointers[$dqlAlias] = $element; } else { unset($this->resultPointers[$dqlAlias]); } } else { $element = $this->getEntity($data, $dqlAlias); if (isset($this->_rsm->indexByMap[$dqlAlias])) { $indexValue = $row[$this->_rsm->indexByMap[$dqlAlias]]; $reflFieldValue->hydrateSet($indexValue, $element); $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue; } else { $reflFieldValue->hydrateAdd($element); $reflFieldValue->last(); $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key(); } // Update result pointer $this->resultPointers[$dqlAlias] = $element; } } else { // Update result pointer $this->resultPointers[$dqlAlias] = $reflFieldValue[$index]; } } else if ( ! $reflFieldValue) { $reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) { $reflFieldValue->setInitialized(true); } } else { // PATH B: Single-valued association $reflFieldValue = $reflField->getValue($parentObject); if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && !$reflFieldValue->__isInitialized__)) { // we only need to take action if this value is null, // we refresh the entity or its an unitialized proxy. if (isset($nonemptyComponents[$dqlAlias])) { $element = $this->getEntity($data, $dqlAlias); $reflField->setValue($parentObject, $element); $this->_uow->setOriginalEntityProperty($oid, $relationField, $element); $targetClass = $this->ce[$relation['targetEntity']]; if ($relation['isOwningSide']) { //TODO: Just check hints['fetched'] here? // If there is an inverse mapping on the target class its bidirectional if ($relation['inversedBy']) { $inverseAssoc = $targetClass->associationMappings[$relation['inversedBy']]; if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) { $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject); $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc['fieldName'], $parentObject); } } else if ($parentClass === $targetClass && $relation['mappedBy']) { // Special case: bi-directional self-referencing one-one on the same class $targetClass->reflFields[$relationField]->setValue($element, $parentObject); } } else { // For sure bidirectional, as there is no inverse side in unidirectional mappings $targetClass->reflFields[$relation['mappedBy']]->setValue($element, $parentObject); $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject); } // Update result pointer $this->resultPointers[$dqlAlias] = $element; } else { $this->_uow->setOriginalEntityProperty($oid, $relationField, null); $reflField->setValue($parentObject, null); } // else leave $reflFieldValue null for single-valued associations } else { // Update result pointer $this->resultPointers[$dqlAlias] = $reflFieldValue; } } } else { // PATH C: Its a root result element $this->rootAliases[$dqlAlias] = true; // Mark as root alias $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0; // if this row has a NULL value for the root result id then make it a null result. if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ($this->_rsm->isMixed) { $result[] = array($entityKey => null); } else { $result[] = null; } $resultKey = $this->resultCounter; ++$this->resultCounter; continue; } // check for existing result from the iterations before if ( ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $this->getEntity($rowData[$dqlAlias], $dqlAlias); if ($this->_rsm->isMixed) { $element = array($entityKey => $element); } if (isset($this->_rsm->indexByMap[$dqlAlias])) { $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]]; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateSet($resultKey, $element); } $result[$resultKey] = $element; } else { $resultKey = $this->resultCounter; ++$this->resultCounter; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateAdd($element); } $result[] = $element; } $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; // Update result pointer $this->resultPointers[$dqlAlias] = $element; } else { // Update result pointer $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]]; $this->resultPointers[$dqlAlias] = $result[$index]; $resultKey = $index; /*if ($this->_rsm->isMixed) { $result[] = $result[$index]; ++$this->_resultCounter; }*/ } } } // Append scalar values to mixed result sets if (isset($scalars)) { if ( ! isset($resultKey) ) { if (isset($this->_rsm->indexByMap['scalars'])) { $resultKey = $row[$this->_rsm->indexByMap['scalars']]; } else { $resultKey = $this->resultCounter - 1; } } foreach ($scalars as $name => $value) { $result[$resultKey][$name] = $value; } } // Append new object to mixed result sets if (isset($newObjects)) { if ( ! isset($resultKey) ) { $resultKey = $this->resultCounter - 1; } $count = count($newObjects); foreach ($newObjects as $objIndex => $newObject) { $class = $newObject['class']; $args = $newObject['args']; $obj = $class->newInstanceArgs($args); if ($count === 1) { $result[$resultKey] = $obj; continue; } $result[$resultKey][$objIndex] = $obj; } } } /** * When executed in a hydrate() loop we may have to clear internal state to * decrease memory consumption. * * @param mixed $eventArgs * * @return void */ public function onClear($eventArgs) { parent::onClear($eventArgs); $aliases = array_keys($this->identifierMap); $this->identifierMap = array(); foreach ($aliases as $alias) { $this->identifierMap[$alias] = array(); } } } doctrine2-2.4.8/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php000066400000000000000000000036321257105210500255420ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Internal\Hydration; /** * Hydrator that produces flat, rectangular results of scalar data. * The created result is almost the same as a regular SQL result set, except * that column names are mapped to field names and data type conversions take place. * * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco */ class ScalarHydrator extends AbstractHydrator { /** * {@inheritdoc} */ protected function hydrateAllData() { $result = array(); $cache = array(); while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) { $this->hydrateRowData($data, $cache, $result); } return $result; } /** * {@inheritdoc} */ protected function hydrateRowData(array $data, array &$cache, array &$result) { $result[] = $this->gatherScalarRowData($data, $cache); } } doctrine2-2.4.8/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php000066400000000000000000000145471257105210500267240ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Internal\Hydration; use PDO; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query; class SimpleObjectHydrator extends AbstractHydrator { /** * @var ClassMetadata */ private $class; /** * @var array */ private $declaringClasses = array(); /** * {@inheritdoc} */ protected function hydrateAllData() { $result = array(); $cache = array(); while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { $this->hydrateRowData($row, $cache, $result); } $this->_em->getUnitOfWork()->triggerEagerLoads(); return $result; } /** * {@inheritdoc} */ protected function prepare() { if (count($this->_rsm->aliasMap) !== 1) { throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result."); } if ($this->_rsm->scalarMappings) { throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings."); } $this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap)); // We only need to add declaring classes if we have inheritance. if ($this->class->inheritanceType === ClassMetadata::INHERITANCE_TYPE_NONE) { return; } foreach ($this->_rsm->declaringClasses as $column => $class) { $this->declaringClasses[$column] = $this->_em->getClassMetadata($class); } } /** * {@inheritdoc} */ protected function hydrateRowData(array $sqlResult, array &$cache, array &$result) { $entityName = $this->class->name; $data = array(); // We need to find the correct entity class name if we have inheritance in resultset if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { $discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']); if ( ! isset($sqlResult[$discrColumnName])) { throw HydrationException::missingDiscriminatorColumn($entityName, $discrColumnName, key($this->_rsm->aliasMap)); } if ($sqlResult[$discrColumnName] === '') { throw HydrationException::emptyDiscriminatorValue(key($this->_rsm->aliasMap)); } $discrMap = $this->class->discriminatorMap; if ( ! isset($discrMap[$sqlResult[$discrColumnName]])) { throw HydrationException::invalidDiscriminatorValue($sqlResult[$discrColumnName], array_keys($discrMap)); } $entityName = $discrMap[$sqlResult[$discrColumnName]]; unset($sqlResult[$discrColumnName]); } foreach ($sqlResult as $column => $value) { // Hydrate column information if not yet present if ( ! isset($cache[$column])) { if (($info = $this->hydrateColumnInfo($entityName, $column)) === null) { continue; } $cache[$column] = $info; } // Convert field to a valid PHP value if (isset($cache[$column]['type'])) { $value = Type::getType($cache[$column]['type'])->convertToPHPValue($value, $this->_platform); } // Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator) if (isset($cache[$column]) && ( ! isset($data[$cache[$column]['name']]) || $value !== null)) { $data[$cache[$column]['name']] = $value; } } if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) { $this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } $uow = $this->_em->getUnitOfWork(); $entity = $uow->createEntity($entityName, $data, $this->_hints); $result[] = $entity; } /** * Retrieve column information form ResultSetMapping. * * @param string $entityName * @param string $column * * @return array */ protected function hydrateColumnInfo($entityName, $column) { if (isset($this->_rsm->fieldMappings[$column])) { $name = $this->_rsm->fieldMappings[$column]; $class = isset($this->declaringClasses[$column]) ? $this->declaringClasses[$column] : $this->class; // If class is not part of the inheritance, ignore if ( ! ($class->name === $entityName || is_subclass_of($entityName, $class->name))) { return null; } return array( 'name' => $name, 'type' => $class->fieldMappings[$name]['type'] ); } if (isset($this->_rsm->metaMappings[$column])) { return array( 'name' => $this->_rsm->metaMappings[$column], 'type' => (isset($this->_rsm->typeMappings[$column]) ? $this->_rsm->typeMappings[$column] : null) ); } // An ObjectHydrator should be used instead of SimpleObjectHydrator if (isset($this->_rsm->relationMap[$column])) { throw new \Exception(sprintf('Unable to retrieve association information for column "%s"', $column)); } return null; } } doctrine2-2.4.8/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php000066400000000000000000000035551257105210500267100ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Internal\Hydration; use Doctrine\ORM\NoResultException; use Doctrine\ORM\NonUniqueResultException; /** * Hydrator that hydrates a single scalar value from the result set. * * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco */ class SingleScalarHydrator extends AbstractHydrator { /** * {@inheritdoc} */ protected function hydrateAllData() { $data = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC); $numRows = count($data); if ($numRows === 0) { throw new NoResultException(); } if ($numRows > 1 || count($data[key($data)]) > 1) { throw new NonUniqueResultException(); } $cache = array(); $result = $this->gatherScalarRowData($data[key($data)], $cache); return array_shift($result); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/000077500000000000000000000000001257105210500177615ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Annotation.php000066400000000000000000000020271257105210500226050ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; interface Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/AnsiQuoteStrategy.php000066400000000000000000000057041257105210500241330ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * ANSI compliant quote strategy, this strategy does not apply any quote. * To use this strategy all mapped tables and columns should be ANSI compliant. * * @since 2.5 * @author Fabio B. Silva */ class AnsiQuoteStrategy implements QuoteStrategy { /** * {@inheritdoc} */ public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform) { return $class->fieldMappings[$fieldName]['columnName']; } /** * {@inheritdoc} */ public function getTableName(ClassMetadata $class, AbstractPlatform $platform) { return $class->table['name']; } /** * {@inheritdoc} */ public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform) { return $definition['sequenceName']; } /** * {@inheritdoc} */ public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform) { return $joinColumn['name']; } /** * {@inheritdoc} */ public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform) { return $joinColumn['referencedColumnName']; } /** * {@inheritdoc} */ public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform) { return $association['joinTable']['name']; } /** * {@inheritdoc} */ public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform) { return $class->identifier; } /** * {@inheritdoc} */ public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ClassMetadata $class = null) { return $platform->getSQLResultCasing($columnName . $counter); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/AssociationOverride.php000066400000000000000000000033411257105210500244470ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * This annotation is used to override association mapping of property for an entity relationship. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class AssociationOverride implements Annotation { /** * The name of the relationship property whose mapping is being overridden. * * @var string */ public $name; /** * The join column that is being mapped to the persistent attribute. * * @var array<\Doctrine\ORM\Mapping\JoinColumn> */ public $joinColumns; /** * The join table that maps the relationship. * * @var \Doctrine\ORM\Mapping\JoinTable */ public $joinTable; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/AssociationOverrides.php000066400000000000000000000026461257105210500246410ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * This annotation is used to override association mappings of relationship properties. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("CLASS") */ final class AssociationOverrides implements Annotation { /** * Mapping overrides of relationship properties. * * @var array<\Doctrine\ORM\Mapping\AssociationOverride> */ public $value; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/AttributeOverride.php000066400000000000000000000027601257105210500241420ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * This annotation is used to override the mapping of a entity property. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class AttributeOverride implements Annotation { /** * The name of the property whose mapping is being overridden. * * @var string */ public $name; /** * The column definition. * * @var \Doctrine\ORM\Mapping\Column */ public $column; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/AttributeOverrides.php000066400000000000000000000026251257105210500243250ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * This annotation is used to override the mapping of a entity property. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("CLASS") */ final class AttributeOverrides implements Annotation { /** * One or more field or property mapping overrides. * * @var array<\Doctrine\ORM\Mapping\AttributeOverride> */ public $value; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Builder/000077500000000000000000000000001257105210500213475ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Builder/AssociationBuilder.php000066400000000000000000000120531257105210500256440ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Builder; use Doctrine\ORM\Mapping\ClassMetadata; class AssociationBuilder { /** * @var ClassMetadataBuilder */ protected $builder; /** * @var array */ protected $mapping; /** * @var array|null */ protected $joinColumns; /** * @var int */ protected $type; /** * @param ClassMetadataBuilder $builder * @param array $mapping * @param int $type */ public function __construct(ClassMetadataBuilder $builder, array $mapping, $type) { $this->builder = $builder; $this->mapping = $mapping; $this->type = $type; } /** * @param string $fieldName * * @return AssociationBuilder */ public function mappedBy($fieldName) { $this->mapping['mappedBy'] = $fieldName; return $this; } /** * @param string $fieldName * * @return AssociationBuilder */ public function inversedBy($fieldName) { $this->mapping['inversedBy'] = $fieldName; return $this; } /** * @return AssociationBuilder */ public function cascadeAll() { $this->mapping['cascade'] = array("ALL"); return $this; } /** * @return AssociationBuilder */ public function cascadePersist() { $this->mapping['cascade'][] = "persist"; return $this; } /** * @return AssociationBuilder */ public function cascadeRemove() { $this->mapping['cascade'][] = "remove"; return $this; } /** * @return AssociationBuilder */ public function cascadeMerge() { $this->mapping['cascade'][] = "merge"; return $this; } /** * @return AssociationBuilder */ public function cascadeDetach() { $this->mapping['cascade'][] = "detach"; return $this; } /** * @return AssociationBuilder */ public function cascadeRefresh() { $this->mapping['cascade'][] = "refresh"; return $this; } /** * @return AssociationBuilder */ public function fetchExtraLazy() { $this->mapping['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY; return $this; } /** * @return AssociationBuilder */ public function fetchEager() { $this->mapping['fetch'] = ClassMetadata::FETCH_EAGER; return $this; } /** * @return AssociationBuilder */ 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|null $onDelete * @param string|null $columnDef * * @return AssociationBuilder */ 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 * * @throws \InvalidArgumentException */ 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 Association here"); } return $this->builder; } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php000066400000000000000000000273241257105210500261050ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Builder; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * 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 * @author Guilherme Blanco */ class ClassMetadataBuilder { /** * @var \Doctrine\ORM\Mapping\ClassMetadataInfo */ private $cm; /** * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $cm */ public function __construct(ClassMetadataInfo $cm) { $this->cm = $cm; } /** * @return ClassMetadata */ public function getClassMetadata() { return $this->cm; } /** * Marks the class as mapped superclass. * * @return ClassMetadataBuilder */ public function setMappedSuperClass() { $this->cm->isMappedSuperclass = true; return $this; } /** * Sets custom Repository class name. * * @param string $repositoryClassName * * @return ClassMetadataBuilder */ public function setCustomRepositoryClass($repositoryClassName) { $this->cm->setCustomRepositoryClass($repositoryClassName); return $this; } /** * Marks class read only. * * @return ClassMetadataBuilder */ public function setReadOnly() { $this->cm->markReadOnly(); return $this; } /** * Sets the table name. * * @param string $name * * @return ClassMetadataBuilder */ public function setTable($name) { $this->cm->setPrimaryTable(array('name' => $name)); return $this; } /** * Adds 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; } /** * Adds 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; } /** * Adds 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; } /** * Sets class as root of a joined table inheritance hierarchy. * * @return ClassMetadataBuilder */ public function setJoinedTableInheritance() { $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_JOINED); return $this; } /** * Sets class as root of a single table inheritance hierarchy. * * @return ClassMetadataBuilder */ public function setSingleTableInheritance() { $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE); return $this; } /** * Sets the discriminator column details. * * @param string $name * @param string $type * @param int $length * * @return ClassMetadataBuilder */ public function setDiscriminatorColumn($name, $type = 'string', $length = 255) { $this->cm->setDiscriminatorColumn(array( 'name' => $name, 'type' => $type, 'length' => $length, )); return $this; } /** * Adds a subclass to this inheritance hierarchy. * * @param string $name * @param string $class * * @return ClassMetadataBuilder */ public function addDiscriminatorMapClass($name, $class) { $this->cm->addDiscriminatorMapClass($name, $class); return $this; } /** * Sets deferred explicit change tracking policy. * * @return ClassMetadataBuilder */ public function setChangeTrackingPolicyDeferredExplicit() { $this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT); return $this; } /** * Sets notify change tracking policy. * * @return ClassMetadataBuilder */ public function setChangeTrackingPolicyNotify() { $this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_NOTIFY); return $this; } /** * Adds lifecycle event. * * @param string $methodName * @param string $event * * @return ClassMetadataBuilder */ public function addLifecycleEvent($methodName, $event) { $this->cm->addLifecycleCallback($methodName, $event); return $this; } /** * Adds Field. * * @param string $name * @param string $type * @param array $mapping * * @return ClassMetadataBuilder */ public function addField($name, $type, array $mapping = array()) { $mapping['fieldName'] = $name; $mapping['type'] = $type; $this->cm->mapField($mapping); return $this; } /** * Creates 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 ) ); } /** * Adds 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->inversedBy($inversedBy); } return $builder->build(); } /** * Creates a ManyToOne Association 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 ); } /** * Creates a OneToOne Association 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 ); } /** * Adds simple inverse one-to-one association. * * @param string $name * @param string $targetEntity * @param string $mappedBy * * @return ClassMetadataBuilder */ public function addInverseOneToOne($name, $targetEntity, $mappedBy) { $builder = $this->createOneToOne($name, $targetEntity); $builder->mappedBy($mappedBy); return $builder->build(); } /** * Adds simple owning one-to-one association. * * @param string $name * @param string $targetEntity * @param string|null $inversedBy * * @return ClassMetadataBuilder */ public function addOwningOneToOne($name, $targetEntity, $inversedBy = null) { $builder = $this->createOneToOne($name, $targetEntity); if ($inversedBy) { $builder->inversedBy($inversedBy); } return $builder->build(); } /** * Creates a ManyToMany Association 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 ); } /** * Adds a simple owning many to many association. * * @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->inversedBy($inversedBy); } return $builder->build(); } /** * Adds a simple inverse many to many association. * * @param string $name * @param string $targetEntity * @param string $mappedBy * * @return ClassMetadataBuilder */ public function addInverseManyToMany($name, $targetEntity, $mappedBy) { $builder = $this->createManyToMany($name, $targetEntity); $builder->mappedBy($mappedBy); return $builder->build(); } /** * Creates a one to many association 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 ); } /** * Adds simple OneToMany association. * * @param string $name * @param string $targetEntity * @param string $mappedBy * * @return ClassMetadataBuilder */ public function addOneToMany($name, $targetEntity, $mappedBy) { $builder = $this->createOneToMany($name, $targetEntity); $builder->mappedBy($mappedBy); return $builder->build(); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Builder/EntityListenerBuilder.php000066400000000000000000000050601257105210500263520ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Builder; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Events; /** * Builder for entity listeners. * * @since 2.4 * @author Fabio B. Silva */ class EntityListenerBuilder { /** * @var array Hash-map to handle event names. */ static private $events = array( Events::preRemove => true, Events::postRemove => true, Events::prePersist => true, Events::postPersist => true, Events::preUpdate => true, Events::postUpdate => true, Events::postLoad => true, Events::preFlush => true ); /** * Lookup the entity class to find methods that match to event lifecycle names * * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. * @param string $className The listener class name. * * @throws \Doctrine\ORM\Mapping\MappingException When the listener class not found. */ static public function bindEntityListener(ClassMetadata $metadata, $className) { $class = $metadata->fullyQualifiedClassName($className); if ( ! class_exists($class)) { throw MappingException::entityListenerClassNotFound($class, $className); } foreach (get_class_methods($class) as $method) { if ( ! isset(self::$events[$method])) { continue; } $metadata->addEntityListener($method, $class, $method); } } }doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php000066400000000000000000000122151257105210500244130ustar00rootroot00000000000000. */ 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 */ 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; } /** * Sets length. * * @param int $length * * @return FieldBuilder */ public function length($length) { $this->mapping['length'] = $length; return $this; } /** * Sets nullable. * * @param bool $flag * * @return FieldBuilder */ public function nullable($flag = true) { $this->mapping['nullable'] = (bool)$flag; return $this; } /** * Sets Unique. * * @param bool $flag * * @return FieldBuilder */ public function unique($flag = true) { $this->mapping['unique'] = (bool)$flag; return $this; } /** * Sets column name. * * @param string $name * * @return FieldBuilder */ public function columnName($name) { $this->mapping['columnName'] = $name; return $this; } /** * Sets Precision. * * @param int $p * * @return FieldBuilder */ public function precision($p) { $this->mapping['precision'] = $p; return $this; } /** * Sets scale. * * @param int $s * * @return FieldBuilder */ public function scale($s) { $this->mapping['scale'] = $s; return $this; } /** * Sets field as primary key. * * @return FieldBuilder */ public function isPrimaryKey() { $this->mapping['id'] = true; return $this; } /** * @param string $strategy * * @return FieldBuilder */ public function generatedValue($strategy = 'AUTO') { $this->generatedValue = $strategy; return $this; } /** * Sets field versioned. * * @return FieldBuilder */ public function isVersionField() { $this->version = true; return $this; } /** * Sets 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; } /** * Sets column definition. * * @param string $def * * @return FieldBuilder */ public function columnDefinition($def) { $this->mapping['columnDefinition'] = $def; return $this; } /** * Finalizes 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; } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Builder/ManyToManyAssociationBuilder.php000066400000000000000000000061411257105210500276220ustar00rootroot00000000000000. */ 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 */ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder { /** * @var string|null */ private $joinTableName; /** * @var array */ private $inverseJoinColumns = array(); /** * @param string $name * * @return ManyToManyAssociationBuilder */ public function setJoinTable($name) { $this->joinTableName = $name; return $this; } /** * Adds Inverse Join Columns. * * @param string $columnName * @param string $referencedColumnName * @param bool $nullable * @param bool $unique * @param string|null $onDelete * @param string|null $columnDef * * @return ManyToManyAssociationBuilder */ 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; } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Builder/OneToManyAssociationBuilder.php000066400000000000000000000041231257105210500274350ustar00rootroot00000000000000. */ 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 */ class OneToManyAssociationBuilder extends AssociationBuilder { /** * @param array $fieldNames * * @return OneToManyAssociationBuilder */ public function setOrderBy(array $fieldNames) { $this->mapping['orderBy'] = $fieldNames; return $this; } /** * @param string $fieldName * * @return OneToManyAssociationBuilder */ 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; } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php000066400000000000000000000024161257105210500245250ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class ChangeTrackingPolicy implements Annotation { /** * The change tracking policy. * * @var string * * @Enum({"DEFERRED_IMPLICIT", "DEFERRED_EXPLICIT", "NOTIFY"}) */ public $value; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/ClassMetadata.php000066400000000000000000000022111257105210500231740ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * {@inheritDoc} * * @todo remove or rename ClassMetadataInfo to ClassMetadata */ class ClassMetadata extends ClassMetadataInfo { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php000066400000000000000000000527111257105210500245360ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; use ReflectionException; use Doctrine\ORM\ORMException; use Doctrine\ORM\EntityManager; use Doctrine\DBAL\Platforms; use Doctrine\ORM\Events; use Doctrine\Common\Persistence\Mapping\ReflectionService; use Doctrine\Common\Persistence\Mapping\ClassMetadata as ClassMetadataInterface; use Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory; use Doctrine\ORM\Id\IdentityGenerator; use Doctrine\ORM\Id\BigIntegerIdentityGenerator; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; /** * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the * metadata mapping information of a class which describes how a class should be mapped * to a relational database. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ClassMetadataFactory extends AbstractClassMetadataFactory { /** * @var EntityManager */ private $em; /** * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ private $targetPlatform; /** * @var \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver */ private $driver; /** * @var \Doctrine\Common\EventManager */ private $evm; /** * @param EntityManager $em */ public function setEntityManager(EntityManager $em) { $this->em = $em; } /** * {@inheritDoc}. */ protected function initialize() { $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl(); $this->evm = $this->em->getEventManager(); $this->initialized = true; } /** * {@inheritDoc} */ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents) { /* @var $class ClassMetadata */ /* @var $parent ClassMetadata */ if ($parent) { $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); $class->setDiscriminatorMap($parent->discriminatorMap); $class->setLifecycleCallbacks($parent->lifecycleCallbacks); $class->setChangeTrackingPolicy($parent->changeTrackingPolicy); if ( ! empty($parent->customGeneratorDefinition)) { $class->setCustomGeneratorDefinition($parent->customGeneratorDefinition); } if ($parent->isMappedSuperclass) { $class->setCustomRepositoryClass($parent->customRepositoryClassName); } } // Invoke driver try { $this->driver->loadMetadataForClass($class->getName(), $class); } catch (ReflectionException $e) { throw MappingException::reflectionFailure($class->getName(), $e); } // If this class has a parent the id generator strategy is inherited. // However this is only true if the hierarchy of parents contains the root entity, // if it consists 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()) { $class->tableGeneratorDefinition = $parent->tableGeneratorDefinition; } if ($parent->generatorType) { $class->setIdGeneratorType($parent->generatorType); } if ($parent->idGenerator) { $class->setIdGenerator($parent->idGenerator); } } else { $this->completeIdGeneratorMapping($class); } if ($parent && $parent->isInheritanceTypeSingleTable()) { $class->setPrimaryTable($parent->table); } if ($parent && $parent->containsForeignIdentifier) { $class->containsForeignIdentifier = true; } if ($parent && !empty($parent->namedQueries)) { $this->addInheritedNamedQueries($class, $parent); } if ($parent && !empty($parent->namedNativeQueries)) { $this->addInheritedNamedNativeQueries($class, $parent); } if ($parent && !empty($parent->sqlResultSetMappings)) { $this->addInheritedSqlResultSetMappings($class, $parent); } if ($parent && !empty($parent->entityListeners) && empty($class->entityListeners)) { $class->entityListeners = $parent->entityListeners; } $class->setParentClasses($nonSuperclassParents); if ( $class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) { $this->addDefaultDiscriminatorMap($class); } if ($this->evm->hasListeners(Events::loadClassMetadata)) { $eventArgs = new LoadClassMetadataEventArgs($class, $this->em); $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); } $this->validateRuntimeMetadata($class, $parent); } /** * Validate runtime metadata is correctly defined. * * @param ClassMetadata $class * @param ClassMetadataInterface|null $parent * * @return void * * @throws MappingException */ protected function validateRuntimeMetadata($class, $parent) { if ( ! $class->reflClass ) { // only validate if there is a reflection class instance return; } $class->validateIdentifier(); $class->validateAssociations(); $class->validateLifecycleCallbacks($this->getReflectionService()); // 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 hierarchy, 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 hierarchy throw MappingException::noInheritanceOnMappedSuperClass($class->name); } } /** * {@inheritDoc} */ protected function newClassMetadataInstance($className) { return new ClassMetadata($className, $this->em->getConfiguration()->getNamingStrategy()); } /** * Adds a default discriminator map if no one is given * * If an entity is of any inheritance type and does not contain a * discriminator map, then the map is generated automatically. This process * is expensive computation wise. * * The automatically generated discriminator map contains the lowercase short name of * each class as key. * * @param \Doctrine\ORM\Mapping\ClassMetadata $class * * @throws MappingException */ private function addDefaultDiscriminatorMap(ClassMetadata $class) { $allClasses = $this->driver->getAllClassNames(); $fqcn = $class->getName(); $map = array($this->getShortName($class->name) => $fqcn); $duplicates = array(); foreach ($allClasses as $subClassCandidate) { if (is_subclass_of($subClassCandidate, $fqcn)) { $shortName = $this->getShortName($subClassCandidate); if (isset($map[$shortName])) { $duplicates[] = $shortName; } $map[$shortName] = $subClassCandidate; } } if ($duplicates) { throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map); } $class->setDiscriminatorMap($map); } /** * Gets the lower-case short name of a class. * * @param string $className * * @return string */ private function getShortName($className) { if (strpos($className, "\\") === false) { return strtolower($className); } $parts = explode("\\", $className); return strtolower(end($parts)); } /** * Adds inherited fields to the subclass mapping. * * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass * * @return void */ private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->fieldMappings as $mapping) { if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { $mapping['inherited'] = $parentClass->name; } if ( ! isset($mapping['declared'])) { $mapping['declared'] = $parentClass->name; } $subClass->addInheritedFieldMapping($mapping); } foreach ($parentClass->reflFields as $name => $field) { $subClass->reflFields[$name] = $field; } } /** * Adds inherited association mappings to the subclass mapping. * * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass * * @return void * * @throws MappingException */ private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->associationMappings as $field => $mapping) { if ($parentClass->isMappedSuperclass) { if ($mapping['type'] & ClassMetadata::TO_MANY && !$mapping['isOwningSide']) { throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field); } $mapping['sourceEntity'] = $subClass->name; } //$subclassMapping = $mapping; if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { $mapping['inherited'] = $parentClass->name; } if ( ! isset($mapping['declared'])) { $mapping['declared'] = $parentClass->name; } $subClass->addInheritedAssociationMapping($mapping); } } /** * Adds inherited named queries to the subclass mapping. * * @since 2.2 * * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass * * @return void */ private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->namedQueries as $name => $query) { if ( ! isset ($subClass->namedQueries[$name])) { $subClass->addNamedQuery(array( 'name' => $query['name'], 'query' => $query['query'] )); } } } /** * Adds inherited named native queries to the subclass mapping. * * @since 2.3 * * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass * * @return void */ private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->namedNativeQueries as $name => $query) { if ( ! isset ($subClass->namedNativeQueries[$name])) { $subClass->addNamedNativeQuery(array( 'name' => $query['name'], 'query' => $query['query'], 'isSelfClass' => $query['isSelfClass'], 'resultSetMapping' => $query['resultSetMapping'], 'resultClass' => $query['isSelfClass'] ? $subClass->name : $query['resultClass'], )); } } } /** * Adds inherited sql result set mappings to the subclass mapping. * * @since 2.3 * * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass * * @return void */ private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->sqlResultSetMappings as $name => $mapping) { if ( ! isset ($subClass->sqlResultSetMappings[$name])) { $entities = array(); foreach ($mapping['entities'] as $entity) { $entities[] = array( 'fields' => $entity['fields'], 'isSelfClass' => $entity['isSelfClass'], 'discriminatorColumn' => $entity['discriminatorColumn'], 'entityClass' => $entity['isSelfClass'] ? $subClass->name : $entity['entityClass'], ); } $subClass->addSqlResultSetMapping(array( 'name' => $mapping['name'], 'columns' => $mapping['columns'], 'entities' => $entities, )); } } } /** * Completes the ID generator mapping. If "auto" is specified we choose the generator * most appropriate for the targeted database platform. * * @param ClassMetadataInfo $class * * @return void * * @throws ORMException */ private function completeIdGeneratorMapping(ClassMetadataInfo $class) { $idGenType = $class->generatorType; if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) { if ($this->getTargetPlatform()->prefersSequences()) { $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_SEQUENCE); } else if ($this->getTargetPlatform()->prefersIdentityColumns()) { $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY); } else { $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_TABLE); } } // Create & assign an appropriate ID generator instance switch ($class->generatorType) { case ClassMetadata::GENERATOR_TYPE_IDENTITY: // For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to // __seq in PostgreSQL for SERIAL columns. // Not pretty but necessary and the simplest solution that currently works. $sequenceName = null; $fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null; if ($this->getTargetPlatform() instanceof Platforms\PostgreSQLPlatform) { $columnName = $class->getSingleIdentifierColumnName(); $quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']); $sequenceName = $class->getTableName() . '_' . $columnName . '_seq'; $definition = array( 'sequenceName' => $this->getTargetPlatform()->fixSchemaElementName($sequenceName) ); if ($quoted) { $definition['quoted'] = true; } $sequenceName = $this ->em ->getConfiguration() ->getQuoteStrategy() ->getSequenceName($definition, $class, $this->getTargetPlatform()); } $generator = ($fieldName && $class->fieldMappings[$fieldName]['type'] === 'bigint') ? new BigIntegerIdentityGenerator($sequenceName) : new IdentityGenerator($sequenceName); $class->setIdGenerator($generator); break; case ClassMetadata::GENERATOR_TYPE_SEQUENCE: // If there is no sequence definition yet, create a default definition $definition = $class->sequenceGeneratorDefinition; if ( ! $definition) { $fieldName = $class->getSingleIdentifierFieldName(); $columnName = $class->getSingleIdentifierColumnName(); $quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']); $sequenceName = $class->getTableName() . '_' . $columnName . '_seq'; $definition = array( 'sequenceName' => $this->getTargetPlatform()->fixSchemaElementName($sequenceName), 'allocationSize' => 1, 'initialValue' => 1, ); if ($quoted) { $definition['quoted'] = true; } $class->setSequenceGeneratorDefinition($definition); } $sequenceGenerator = new \Doctrine\ORM\Id\SequenceGenerator( $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->getTargetPlatform()), $definition['allocationSize'] ); $class->setIdGenerator($sequenceGenerator); break; case ClassMetadata::GENERATOR_TYPE_NONE: $class->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator()); break; case ClassMetadata::GENERATOR_TYPE_UUID: $class->setIdGenerator(new \Doctrine\ORM\Id\UuidGenerator()); break; case ClassMetadata::GENERATOR_TYPE_TABLE: throw new ORMException("TableGenerator not yet implemented."); break; case ClassMetadata::GENERATOR_TYPE_CUSTOM: $definition = $class->customGeneratorDefinition; if ( ! class_exists($definition['class'])) { throw new ORMException("Can't instantiate custom generator : " . $definition['class']); } $class->setIdGenerator(new $definition['class']); break; default: throw new ORMException("Unknown generator type: " . $class->generatorType); } } /** * {@inheritDoc} */ protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService) { /* @var $class ClassMetadata */ $class->wakeupReflection($reflService); } /** * {@inheritDoc} */ protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService) { /* @var $class ClassMetadata */ $class->initializeReflection($reflService); } /** * {@inheritDoc} */ protected function getFqcnFromAlias($namespaceAlias, $simpleClassName) { return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; } /** * {@inheritDoc} */ protected function getDriver() { return $this->driver; } /** * {@inheritDoc} */ protected function isEntity(ClassMetadataInterface $class) { return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false; } /** * @return Platforms\AbstractPlatform */ private function getTargetPlatform() { if (!$this->targetPlatform) { $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform(); } return $this->targetPlatform; } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php000066400000000000000000002745071257105210500240330ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; use BadMethodCallException; use InvalidArgumentException; use RuntimeException; use Doctrine\DBAL\Types\Type; use ReflectionClass; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\ClassLoader; use Doctrine\Common\EventArgs; /** * A ClassMetadata instance holds all the object-relational mapping metadata * of an entity and its associations. * * Once populated, ClassMetadata instances are usually cached in a serialized form. * * IMPORTANT NOTE: * * The fields of this class are only public for 2 reasons: * 1) To allow fast READ access. * 2) To drastically reduce the size of a serialized instance (private/protected members * get the whole class name, namespace inclusive, prepended to every property in * the serialized representation). * * @author Roman Borschel * @author Jonathan H. Wage * @since 2.0 */ class ClassMetadataInfo implements ClassMetadata { /* The inheritance mapping types */ /** * NONE means the class does not participate in an inheritance hierarchy * and therefore does not need an inheritance mapping type. */ const INHERITANCE_TYPE_NONE = 1; /** * JOINED means the class will be persisted according to the rules of * Class Table Inheritance. */ const INHERITANCE_TYPE_JOINED = 2; /** * SINGLE_TABLE means the class will be persisted according to the rules of * Single Table Inheritance. */ const INHERITANCE_TYPE_SINGLE_TABLE = 3; /** * TABLE_PER_CLASS means the class will be persisted according to the rules * of Concrete Table Inheritance. */ const INHERITANCE_TYPE_TABLE_PER_CLASS = 4; /* The Id generator types. */ /** * AUTO means the generator type will depend on what the used platform prefers. * Offers full portability. */ const GENERATOR_TYPE_AUTO = 1; /** * SEQUENCE means a separate sequence object will be used. Platforms that do * not have native sequence support may emulate it. Full portability is currently * not guaranteed. */ const GENERATOR_TYPE_SEQUENCE = 2; /** * TABLE means a separate table is used for id generation. * Offers full portability. */ const GENERATOR_TYPE_TABLE = 3; /** * IDENTITY means an identity column is used for id generation. The database * will fill in the id column on insertion. Platforms that do not support * native identity columns may emulate them. Full portability is currently * not guaranteed. */ const GENERATOR_TYPE_IDENTITY = 4; /** * NONE means the class does not have a generated id. That means the class * must have a natural, manually assigned id. */ const GENERATOR_TYPE_NONE = 5; /** * UUID means that a UUID/GUID expression is used for id generation. Full * portability is currently not guaranteed. */ const GENERATOR_TYPE_UUID = 6; /** * CUSTOM means that customer will use own ID generator that supposedly work */ const GENERATOR_TYPE_CUSTOM = 7; /** * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time * by doing a property-by-property comparison with the original data. This will * be done for all entities that are in MANAGED state at commit-time. * * This is the default change tracking policy. */ const CHANGETRACKING_DEFERRED_IMPLICIT = 1; /** * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time * by doing a property-by-property comparison with the original data. This will * be done only for entities that were explicitly saved (through persist() or a cascade). */ const CHANGETRACKING_DEFERRED_EXPLICIT = 2; /** * NOTIFY means that Doctrine relies on the entities sending out notifications * when their properties change. Such entity classes must implement * the NotifyPropertyChanged interface. */ const CHANGETRACKING_NOTIFY = 3; /** * Specifies that an association is to be fetched when it is first accessed. */ const FETCH_LAZY = 2; /** * Specifies that an association is to be fetched when the owner of the * association is fetched. */ const FETCH_EAGER = 3; /** * Specifies that an association is to be fetched lazy (on first access) and that * commands such as Collection#count, Collection#slice are issued directly against * the database if the collection is not yet initialized. */ const FETCH_EXTRA_LAZY = 4; /** * Identifies a one-to-one association. */ const ONE_TO_ONE = 1; /** * Identifies a many-to-one association. */ const MANY_TO_ONE = 2; /** * Identifies a one-to-many association. */ const ONE_TO_MANY = 4; /** * Identifies a many-to-many association. */ const MANY_TO_MANY = 8; /** * Combined bitmask for to-one (single-valued) associations. */ const TO_ONE = 3; /** * Combined bitmask for to-many (collection-valued) associations. */ const TO_MANY = 12; /** * READ-ONLY: The name of the entity class. * * @var string */ public $name; /** * READ-ONLY: The namespace the entity class is contained in. * * @var string * * @todo Not really needed. Usage could be localized. */ public $namespace; /** * READ-ONLY: The name of the entity class that is at the root of the mapped entity inheritance * hierarchy. If the entity is not part of a mapped inheritance hierarchy this is the same * as {@link $entityName}. * * @var string */ public $rootEntityName; /** * READ-ONLY: The definition of custom generator. Only used for CUSTOM * generator type * * The definition has the following structure: * * array( * 'class' => 'ClassName', * ) * * * @var array * * @todo Merge with tableGeneratorDefinition into generic generatorDefinition */ public $customGeneratorDefinition; /** * The name of the custom repository class used for the entity class. * (Optional). * * @var string */ public $customRepositoryClassName; /** * READ-ONLY: Whether this class describes the mapping of a mapped superclass. * * @var boolean */ public $isMappedSuperclass = false; /** * READ-ONLY: The names of the parent classes (ancestors). * * @var array */ public $parentClasses = array(); /** * READ-ONLY: The names of all subclasses (descendants). * * @var array */ public $subClasses = array(); /** * READ-ONLY: The named queries allowed to be called directly from Repository. * * @var array */ public $namedQueries = array(); /** * READ-ONLY: The named native queries allowed to be called directly from Repository. * * A native SQL named query definition has the following structure: *
     * array(
     *     'name'               => ,
     *     'query'              => ,
     *     'resultClass'        => ,
     *     'resultSetMapping'   => 
     * )
     * 
* * @var array */ public $namedNativeQueries = array(); /** * READ-ONLY: The mappings of the results of native SQL queries. * * A native result mapping definition has the following structure: *
     * array(
     *     'name'               => ,
     *     'entities'           => array(),
     *     'columns'            => array()
     * )
     * 
* * @var array */ public $sqlResultSetMappings = array(); /** * READ-ONLY: The field names of all fields that are part of the identifier/primary key * of the mapped entity class. * * @var array */ public $identifier = array(); /** * READ-ONLY: The inheritance mapping type used by the class. * * @var integer */ public $inheritanceType = self::INHERITANCE_TYPE_NONE; /** * READ-ONLY: The Id generator type used by the class. * * @var int */ public $generatorType = self::GENERATOR_TYPE_NONE; /** * READ-ONLY: The field mappings of the class. * Keys are field names and values are mapping definitions. * * The mapping definition array has the following values: * * - fieldName (string) * The name of the field in the Entity. * * - type (string) * The type name of the mapped field. Can be one of Doctrine's mapping types * or a custom mapping type. * * - columnName (string, optional) * The column name. Optional. Defaults to the field name. * * - length (integer, optional) * The database length of the column. Optional. Default value taken from * the type. * * - id (boolean, optional) * Marks the field as the primary key of the entity. Multiple fields of an * entity can have the id attribute, forming a composite key. * * - nullable (boolean, optional) * Whether the column is nullable. Defaults to FALSE. * * - columnDefinition (string, optional, schema-only) * The SQL fragment that is used when generating the DDL for the column. * * - precision (integer, optional, schema-only) * The precision of a decimal column. Only valid if the column type is decimal. * * - scale (integer, optional, schema-only) * The scale of a decimal column. Only valid if the column type is decimal. * * - 'unique' (string, optional, schema-only) * Whether a unique constraint should be generated for the column. * * @var array */ public $fieldMappings = array(); /** * READ-ONLY: An array of field names. Used to look up field names from column names. * Keys are column names and values are field names. * This is the reverse lookup map of $_columnNames. * * @var array */ public $fieldNames = array(); /** * READ-ONLY: A map of field names to column names. Keys are field names and values column names. * Used to look up column names from field names. * This is the reverse lookup map of $_fieldNames. * * @var array * * @todo We could get rid of this array by just using $fieldMappings[$fieldName]['columnName']. */ public $columnNames = array(); /** * READ-ONLY: The discriminator value of this class. * * This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies * where a discriminator column is used. * * @var mixed * * @see discriminatorColumn */ public $discriminatorValue; /** * READ-ONLY: The discriminator map of all mapped classes in the hierarchy. * * This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies * where a discriminator column is used. * * @var mixed * * @see discriminatorColumn */ public $discriminatorMap = array(); /** * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE * inheritance mappings. * * @var array */ public $discriminatorColumn; /** * READ-ONLY: The primary table definition. The definition is an array with the * following entries: * * name => * schema => * indexes => array * uniqueConstraints => array * * @var array */ public $table; /** * READ-ONLY: The registered lifecycle callbacks for entities of this class. * * @var array */ public $lifecycleCallbacks = array(); /** * READ-ONLY: The registered entity listeners. * * @var array */ public $entityListeners = array(); /** * READ-ONLY: The association mappings of this class. * * The mapping definition array supports the following keys: * * - fieldName (string) * The name of the field in the entity the association is mapped to. * * - targetEntity (string) * The class name of the target entity. If it is fully-qualified it is used as is. * If it is a simple, unqualified class name the namespace is assumed to be the same * as the namespace of the source entity. * * - mappedBy (string, required for bidirectional associations) * The name of the field that completes the bidirectional association on the owning side. * This key must be specified on the inverse side of a bidirectional association. * * - inversedBy (string, required for bidirectional associations) * The name of the field that completes the bidirectional association on the inverse side. * This key must be specified on the owning side of a bidirectional association. * * - cascade (array, optional) * The names of persistence operations to cascade on the association. The set of possible * values are: "persist", "remove", "detach", "merge", "refresh", "all" (implies all others). * * - orderBy (array, one-to-many/many-to-many only) * A map of field names (of the target entity) to sorting directions (ASC/DESC). * Example: array('priority' => 'desc') * * - fetch (integer, optional) * The fetching strategy to use for the association, usually defaults to FETCH_LAZY. * Possible values are: ClassMetadata::FETCH_EAGER, ClassMetadata::FETCH_LAZY. * * - joinTable (array, optional, many-to-many only) * Specification of the join table and its join columns (foreign keys). * Only valid for many-to-many mappings. Note that one-to-many associations can be mapped * through a join table by simply mapping the association as many-to-many with a unique * constraint on the join table. * * - indexBy (string, optional, to-many only) * Specification of a field on target-entity that is used to index the collection by. * This field HAS to be either the primary key or a unique column. Otherwise the collection * does not contain all the entities that are actually related. * * A join table definition has the following structure: *
     * array(
     *     'name' => ,
     *      'joinColumns' => array(),
     *      'inverseJoinColumns' => array()
     * )
     * 
* * @var array */ public $associationMappings = array(); /** * READ-ONLY: Flag indicating whether the identifier/primary key of the class is composite. * * @var boolean */ public $isIdentifierComposite = false; /** * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one foreign key association. * * This flag is necessary because some code blocks require special treatment of this cases. * * @var boolean */ public $containsForeignIdentifier = false; /** * READ-ONLY: The ID generator used for generating IDs for this class. * * @var \Doctrine\ORM\Id\AbstractIdGenerator * * @todo Remove! */ public $idGenerator; /** * READ-ONLY: The definition of the sequence generator of this class. Only used for the * SEQUENCE generation strategy. * * The definition has the following structure: * * array( * 'sequenceName' => 'name', * 'allocationSize' => 20, * 'initialValue' => 1 * ) * * * @var array * * @todo Merge with tableGeneratorDefinition into generic generatorDefinition */ public $sequenceGeneratorDefinition; /** * READ-ONLY: The definition of the table generator of this class. Only used for the * TABLE generation strategy. * * @var array * * @todo Merge with tableGeneratorDefinition into generic generatorDefinition */ public $tableGeneratorDefinition; /** * READ-ONLY: The policy used for change-tracking on entities of this class. * * @var integer */ public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT; /** * READ-ONLY: A flag for whether or not instances of this class are to be versioned * with optimistic locking. * * @var boolean */ public $isVersioned; /** * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any). * * @var mixed */ public $versionField; /** * The ReflectionClass instance of the mapped class. * * @var ReflectionClass */ public $reflClass; /** * Is this entity marked as "read-only"? * * That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance * optimization for entities that are immutable, either in your domain or through the relation database * (coming from a view, or a history table for example). * * @var bool */ public $isReadOnly = false; /** * NamingStrategy determining the default column and table names. * * @var \Doctrine\ORM\Mapping\NamingStrategy */ protected $namingStrategy; /** * The ReflectionProperty instances of the mapped class. * * @var \ReflectionProperty[] */ public $reflFields = array(); /** * The prototype from which new instances of the mapped class are created. * * @var object */ private $_prototype; /** * Initializes a new ClassMetadata instance that will hold the object-relational mapping * metadata of the class with the given name. * * @param string $entityName The name of the entity class the new instance is used for. * @param NamingStrategy|null $namingStrategy */ public function __construct($entityName, NamingStrategy $namingStrategy = null) { $this->name = $entityName; $this->rootEntityName = $entityName; $this->namingStrategy = $namingStrategy ?: new DefaultNamingStrategy(); } /** * Gets the ReflectionProperties of the mapped class. * * @return array An array of ReflectionProperty instances. */ public function getReflectionProperties() { return $this->reflFields; } /** * Gets a ReflectionProperty for a specific field of the mapped class. * * @param string $name * * @return \ReflectionProperty */ public function getReflectionProperty($name) { return $this->reflFields[$name]; } /** * Gets the ReflectionProperty for the single identifier field. * * @return \ReflectionProperty * * @throws BadMethodCallException If the class has a composite identifier. */ public function getSingleIdReflectionProperty() { if ($this->isIdentifierComposite) { throw new BadMethodCallException("Class " . $this->name . " has a composite identifier."); } return $this->reflFields[$this->identifier[0]]; } /** * Extracts the identifier values of an entity of this class. * * For composite identifiers, the identifier values are returned as an array * with the same order as the field order in {@link identifier}. * * @param object $entity * * @return array */ public function getIdentifierValues($entity) { if ($this->isIdentifierComposite) { $id = array(); foreach ($this->identifier as $idField) { $value = $this->reflFields[$idField]->getValue($entity); if ($value !== null) { $id[$idField] = $value; } } return $id; } $id = $this->identifier[0]; $value = $this->reflFields[$id]->getValue($entity); if (null === $value) { return array(); } return array($id => $value); } /** * Populates the entity identifier of an entity. * * @param object $entity * @param mixed $id * * @return void * * @todo Rename to assignIdentifier() */ public function setIdentifierValues($entity, array $id) { foreach ($id as $idField => $idValue) { $this->reflFields[$idField]->setValue($entity, $idValue); } } /** * Sets the specified field to the specified value on the given entity. * * @param object $entity * @param string $field * @param mixed $value * * @return void */ public function setFieldValue($entity, $field, $value) { $this->reflFields[$field]->setValue($entity, $value); } /** * Gets the specified field's value off the given entity. * * @param object $entity * @param string $field * * @return mixed */ public function getFieldValue($entity, $field) { return $this->reflFields[$field]->getValue($entity); } /** * Creates a string representation of this instance. * * @return string The string representation of this instance. * * @todo Construct meaningful string representation. */ public function __toString() { return __CLASS__ . '@' . spl_object_hash($this); } /** * Determines which fields get serialized. * * It is only serialized what is necessary for best unserialization performance. * That means any metadata properties that are not set or empty or simply have * their default value are NOT serialized. * * Parts that are also NOT serialized because they can not be properly unserialized: * - reflClass (ReflectionClass) * - reflFields (ReflectionProperty array) * * @return array The names of all the fields that should be serialized. */ public function __sleep() { // This metadata is always serialized/cached. $serialized = array( 'associationMappings', 'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName'] 'fieldMappings', 'fieldNames', 'identifier', 'isIdentifierComposite', // TODO: REMOVE 'name', 'namespace', // TODO: REMOVE 'table', 'rootEntityName', 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. ); // The rest of the metadata is only serialized if necessary. if ($this->changeTrackingPolicy != self::CHANGETRACKING_DEFERRED_IMPLICIT) { $serialized[] = 'changeTrackingPolicy'; } if ($this->customRepositoryClassName) { $serialized[] = 'customRepositoryClassName'; } if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE) { $serialized[] = 'inheritanceType'; $serialized[] = 'discriminatorColumn'; $serialized[] = 'discriminatorValue'; $serialized[] = 'discriminatorMap'; $serialized[] = 'parentClasses'; $serialized[] = 'subClasses'; } if ($this->generatorType != self::GENERATOR_TYPE_NONE) { $serialized[] = 'generatorType'; if ($this->generatorType == self::GENERATOR_TYPE_SEQUENCE) { $serialized[] = 'sequenceGeneratorDefinition'; } } if ($this->isMappedSuperclass) { $serialized[] = 'isMappedSuperclass'; } if ($this->containsForeignIdentifier) { $serialized[] = 'containsForeignIdentifier'; } if ($this->isVersioned) { $serialized[] = 'isVersioned'; $serialized[] = 'versionField'; } if ($this->lifecycleCallbacks) { $serialized[] = 'lifecycleCallbacks'; } if ($this->entityListeners) { $serialized[] = 'entityListeners'; } if ($this->namedQueries) { $serialized[] = 'namedQueries'; } if ($this->namedNativeQueries) { $serialized[] = 'namedNativeQueries'; } if ($this->sqlResultSetMappings) { $serialized[] = 'sqlResultSetMappings'; } if ($this->isReadOnly) { $serialized[] = 'isReadOnly'; } if ($this->customGeneratorDefinition) { $serialized[] = "customGeneratorDefinition"; } return $serialized; } /** * Creates a new instance of the mapped class, without invoking the constructor. * * @return object */ public function newInstance() { if ($this->_prototype === null) { if (PHP_VERSION_ID === 50429 || PHP_VERSION_ID === 50513 || PHP_VERSION_ID >= 50600) { $this->_prototype = $this->reflClass->newInstanceWithoutConstructor(); } else { $this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name)); } } return clone $this->_prototype; } /** * Restores some state that can not be serialized/unserialized. * * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService * * @return void */ public function wakeupReflection($reflService) { // Restore ReflectionClass and properties $this->reflClass = $reflService->getClass($this->name); foreach ($this->fieldMappings as $field => $mapping) { $this->reflFields[$field] = isset($mapping['declared']) ? $reflService->getAccessibleProperty($mapping['declared'], $field) : $reflService->getAccessibleProperty($this->name, $field); } foreach ($this->associationMappings as $field => $mapping) { $this->reflFields[$field] = isset($mapping['declared']) ? $reflService->getAccessibleProperty($mapping['declared'], $field) : $reflService->getAccessibleProperty($this->name, $field); } } /** * Initializes a new ClassMetadata instance that will hold the object-relational mapping * metadata of the class with the given name. * * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService The reflection service. * * @return void */ public function initializeReflection($reflService) { $this->reflClass = $reflService->getClass($this->name); $this->namespace = $reflService->getClassNamespace($this->name); if ($this->reflClass) { $this->name = $this->rootEntityName = $this->reflClass->getName(); } $this->table['name'] = $this->namingStrategy->classToTableName($this->name); } /** * Validates Identifier. * * @return void * * @throws MappingException */ public function validateIdentifier() { // Verify & complete identifier mapping if ( ! $this->identifier && ! $this->isMappedSuperclass) { throw MappingException::identifierRequired($this->name); } if ($this->usesIdGenerator() && $this->isIdentifierComposite) { throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name); } } /** * Validates association targets actually exist. * * @return void * * @throws MappingException */ public function validateAssociations() { foreach ($this->associationMappings as $mapping) { if ( ! ClassLoader::classExists($mapping['targetEntity']) ) { throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']); } } } /** * Validates lifecycle callbacks. * * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService * * @return void * * @throws MappingException */ public function validateLifecycleCallbacks($reflService) { foreach ($this->lifecycleCallbacks as $callbacks) { foreach ($callbacks as $callbackFuncName) { if ( ! $reflService->hasPublicMethod($this->name, $callbackFuncName)) { throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName); } } } } /** * {@inheritDoc} */ public function getReflectionClass() { return $this->reflClass; } /** * Sets the change tracking policy used by this class. * * @param integer $policy * * @return void */ public function setChangeTrackingPolicy($policy) { $this->changeTrackingPolicy = $policy; } /** * Whether the change tracking policy of this class is "deferred explicit". * * @return boolean */ public function isChangeTrackingDeferredExplicit() { return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_EXPLICIT; } /** * Whether the change tracking policy of this class is "deferred implicit". * * @return boolean */ public function isChangeTrackingDeferredImplicit() { return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_IMPLICIT; } /** * Whether the change tracking policy of this class is "notify". * * @return boolean */ public function isChangeTrackingNotify() { return $this->changeTrackingPolicy == self::CHANGETRACKING_NOTIFY; } /** * Checks whether a field is part of the identifier/primary key field(s). * * @param string $fieldName The field name. * * @return boolean TRUE if the field is part of the table identifier/primary key field(s), * FALSE otherwise. */ public function isIdentifier($fieldName) { if ( ! $this->identifier) { return false; } if ( ! $this->isIdentifierComposite) { return $fieldName === $this->identifier[0]; } return in_array($fieldName, $this->identifier); } /** * Checks if the field is unique. * * @param string $fieldName The field name. * * @return boolean TRUE if the field is unique, FALSE otherwise. */ public function isUniqueField($fieldName) { $mapping = $this->getFieldMapping($fieldName); if ($mapping !== false) { return isset($mapping['unique']) && $mapping['unique'] == true; } return false; } /** * Checks if the field is not null. * * @param string $fieldName The field name. * * @return boolean TRUE if the field is not null, FALSE otherwise. */ public function isNullable($fieldName) { $mapping = $this->getFieldMapping($fieldName); if ($mapping !== false) { return isset($mapping['nullable']) && $mapping['nullable'] == true; } return false; } /** * Gets a column name for a field name. * If the column name for the field cannot be found, the given field name * is returned. * * @param string $fieldName The field name. * * @return string The column name. */ public function getColumnName($fieldName) { return isset($this->columnNames[$fieldName]) ? $this->columnNames[$fieldName] : $fieldName; } /** * Gets the mapping of a (regular) field that holds some data but not a * reference to another object. * * @param string $fieldName The field name. * * @return array The field mapping. * * @throws MappingException */ public function getFieldMapping($fieldName) { if ( ! isset($this->fieldMappings[$fieldName])) { throw MappingException::mappingNotFound($this->name, $fieldName); } return $this->fieldMappings[$fieldName]; } /** * Gets the mapping of an association. * * @see ClassMetadataInfo::$associationMappings * * @param string $fieldName The field name that represents the association in * the object model. * * @return array The mapping. * * @throws MappingException */ public function getAssociationMapping($fieldName) { if ( ! isset($this->associationMappings[$fieldName])) { throw MappingException::mappingNotFound($this->name, $fieldName); } return $this->associationMappings[$fieldName]; } /** * Gets all association mappings of the class. * * @return array */ public function getAssociationMappings() { return $this->associationMappings; } /** * Gets the field name for a column name. * If no field name can be found the column name is returned. * * @param string $columnName The column name. * * @return string The column alias. */ public function getFieldName($columnName) { return isset($this->fieldNames[$columnName]) ? $this->fieldNames[$columnName] : $columnName; } /** * Gets the named query. * * @see ClassMetadataInfo::$namedQueries * * @param string $queryName The query name. * * @return string * * @throws MappingException */ public function getNamedQuery($queryName) { if ( ! isset($this->namedQueries[$queryName])) { throw MappingException::queryNotFound($this->name, $queryName); } return $this->namedQueries[$queryName]['dql']; } /** * Gets all named queries of the class. * * @return array */ public function getNamedQueries() { return $this->namedQueries; } /** * Gets the named native query. * * @see ClassMetadataInfo::$namedNativeQueries * * @param string $queryName The query name. * * @return array * * @throws MappingException */ public function getNamedNativeQuery($queryName) { if ( ! isset($this->namedNativeQueries[$queryName])) { throw MappingException::queryNotFound($this->name, $queryName); } return $this->namedNativeQueries[$queryName]; } /** * Gets all named native queries of the class. * * @return array */ public function getNamedNativeQueries() { return $this->namedNativeQueries; } /** * Gets the result set mapping. * * @see ClassMetadataInfo::$sqlResultSetMappings * * @param string $name The result set mapping name. * * @return array * * @throws MappingException */ public function getSqlResultSetMapping($name) { if ( ! isset($this->sqlResultSetMappings[$name])) { throw MappingException::resultMappingNotFound($this->name, $name); } return $this->sqlResultSetMappings[$name]; } /** * Gets all sql result set mappings of the class. * * @return array */ public function getSqlResultSetMappings() { return $this->sqlResultSetMappings; } /** * Validates & completes the given field mapping. * * @param array $mapping The field mapping to validate & complete. * * @return array The validated and completed field mapping. * * @throws MappingException */ protected function _validateAndCompleteFieldMapping(array &$mapping) { // Check mandatory fields if ( ! isset($mapping['fieldName']) || strlen($mapping['fieldName']) == 0) { throw MappingException::missingFieldName($this->name); } if ( ! isset($mapping['type'])) { // Default to string $mapping['type'] = 'string'; } // Complete fieldName and columnName mapping if ( ! isset($mapping['columnName'])) { $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name); } if ($mapping['columnName'][0] === '`') { $mapping['columnName'] = trim($mapping['columnName'], '`'); $mapping['quoted'] = true; } $this->columnNames[$mapping['fieldName']] = $mapping['columnName']; if (isset($this->fieldNames[$mapping['columnName']]) || ($this->discriminatorColumn != null && $this->discriminatorColumn['name'] == $mapping['columnName'])) { throw MappingException::duplicateColumnName($this->name, $mapping['columnName']); } $this->fieldNames[$mapping['columnName']] = $mapping['fieldName']; // Complete id mapping if (isset($mapping['id']) && $mapping['id'] === true) { if ($this->versionField == $mapping['fieldName']) { throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']); } if ( ! in_array($mapping['fieldName'], $this->identifier)) { $this->identifier[] = $mapping['fieldName']; } // Check for composite key if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) { $this->isIdentifierComposite = true; } } if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) { if (isset($mapping['id']) && $mapping['id'] === true) { throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']); } $mapping['requireSQLConversion'] = true; } } /** * Validates & completes the basic mapping information that is common to all * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many). * * @param array $mapping The mapping. * * @return array The updated mapping. * * @throws MappingException If something is wrong with the mapping. */ protected function _validateAndCompleteAssociationMapping(array $mapping) { if ( ! isset($mapping['mappedBy'])) { $mapping['mappedBy'] = null; } if ( ! isset($mapping['inversedBy'])) { $mapping['inversedBy'] = null; } $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy // unset optional indexBy attribute if its empty if ( ! isset($mapping['indexBy']) || !$mapping['indexBy']) { unset($mapping['indexBy']); } // If targetEntity is unqualified, assume it is in the same namespace as // the sourceEntity. $mapping['sourceEntity'] = $this->name; if (isset($mapping['targetEntity'])) { $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']); $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\'); } if ( ($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) { throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']); } // Complete id mapping if (isset($mapping['id']) && $mapping['id'] === true) { if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) { throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']); } if ( ! in_array($mapping['fieldName'], $this->identifier)) { if (count($mapping['joinColumns']) >= 2) { throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId( $mapping['targetEntity'], $this->name, $mapping['fieldName'] ); } $this->identifier[] = $mapping['fieldName']; $this->containsForeignIdentifier = true; } // Check for composite key if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) { $this->isIdentifierComposite = true; } } // Mandatory attributes for both sides // Mandatory: fieldName, targetEntity if ( ! isset($mapping['fieldName']) || strlen($mapping['fieldName']) == 0) { throw MappingException::missingFieldName($this->name); } if ( ! isset($mapping['targetEntity'])) { throw MappingException::missingTargetEntity($mapping['fieldName']); } // Mandatory and optional attributes for either side if ( ! $mapping['mappedBy']) { if (isset($mapping['joinTable']) && $mapping['joinTable']) { if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') { $mapping['joinTable']['name'] = trim($mapping['joinTable']['name'], '`'); $mapping['joinTable']['quoted'] = true; } } } else { $mapping['isOwningSide'] = false; } if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) { throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']); } // Fetch mode. Default fetch mode to LAZY, if not set. if ( ! isset($mapping['fetch'])) { $mapping['fetch'] = self::FETCH_LAZY; } // Cascades $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : array(); if (in_array('all', $cascades)) { $cascades = array('remove', 'persist', 'refresh', 'merge', 'detach'); } if (count($cascades) !== count(array_intersect($cascades, array('remove', 'persist', 'refresh', 'merge', 'detach')))) { throw MappingException::invalidCascadeOption( array_diff($cascades, array_intersect($cascades, array('remove', 'persist', 'refresh', 'merge', 'detach'))), $this->name, $mapping['fieldName'] ); } $mapping['cascade'] = $cascades; $mapping['isCascadeRemove'] = in_array('remove', $cascades); $mapping['isCascadePersist'] = in_array('persist', $cascades); $mapping['isCascadeRefresh'] = in_array('refresh', $cascades); $mapping['isCascadeMerge'] = in_array('merge', $cascades); $mapping['isCascadeDetach'] = in_array('detach', $cascades); return $mapping; } /** * Validates & completes a one-to-one association mapping. * * @param array $mapping The mapping to validate & complete. * * @return array The validated & completed mapping. * * @throws RuntimeException * @throws MappingException */ protected function _validateAndCompleteOneToOneMapping(array $mapping) { $mapping = $this->_validateAndCompleteAssociationMapping($mapping); if (isset($mapping['joinColumns']) && $mapping['joinColumns']) { $mapping['isOwningSide'] = true; } if ($mapping['isOwningSide']) { if ( ! isset($mapping['joinColumns']) || ! $mapping['joinColumns']) { // Apply default join column $mapping['joinColumns'] = array(array( 'name' => $this->namingStrategy->joinColumnName($mapping['fieldName']), 'referencedColumnName' => $this->namingStrategy->referenceColumnName() )); } $uniqueConstraintColumns = array(); foreach ($mapping['joinColumns'] as &$joinColumn) { if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) { if (count($mapping['joinColumns']) == 1) { if ( ! isset($mapping['id']) || ! $mapping['id']) { $joinColumn['unique'] = true; } } else { $uniqueConstraintColumns[] = $joinColumn['name']; } } if (empty($joinColumn['name'])) { $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName']); } if (empty($joinColumn['referencedColumnName'])) { $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName(); } if ($joinColumn['name'][0] === '`') { $joinColumn['name'] = trim($joinColumn['name'], '`'); $joinColumn['quoted'] = true; } if ($joinColumn['referencedColumnName'][0] === '`') { $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`'); $joinColumn['quoted'] = true; } $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName']; $mapping['joinColumnFieldNames'][$joinColumn['name']] = isset($joinColumn['fieldName']) ? $joinColumn['fieldName'] : $joinColumn['name']; } if ($uniqueConstraintColumns) { if ( ! $this->table) { throw new RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship."); } $this->table['uniqueConstraints'][$mapping['fieldName']."_uniq"] = array( 'columns' => $uniqueConstraintColumns ); } $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']); } $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false; $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove']; if ($mapping['orphanRemoval']) { unset($mapping['unique']); } if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) { throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']); } return $mapping; } /** * Validates & completes a one-to-many association mapping. * * @param array $mapping The mapping to validate and complete. * * @return array The validated and completed mapping. * * @throws MappingException * @throws InvalidArgumentException */ protected function _validateAndCompleteOneToManyMapping(array $mapping) { $mapping = $this->_validateAndCompleteAssociationMapping($mapping); // OneToMany-side MUST be inverse (must have mappedBy) if ( ! isset($mapping['mappedBy'])) { throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']); } $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'])) { throw new InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy'])); } } return $mapping; } /** * Validates & completes a many-to-many association mapping. * * @param array $mapping The mapping to validate & complete. * * @return array The validated & completed mapping. * * @throws \InvalidArgumentException */ protected function _validateAndCompleteManyToManyMapping(array $mapping) { $mapping = $this->_validateAndCompleteAssociationMapping($mapping); if ($mapping['isOwningSide']) { // owning side MUST have a join table if ( ! isset($mapping['joinTable']['name'])) { $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']); } $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity'] && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns']))); if ( ! isset($mapping['joinTable']['joinColumns'])) { $mapping['joinTable']['joinColumns'] = array(array( 'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null), 'referencedColumnName' => $this->namingStrategy->referenceColumnName(), 'onDelete' => 'CASCADE')); } if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) { $mapping['joinTable']['inverseJoinColumns'] = array(array( 'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null), 'referencedColumnName' => $this->namingStrategy->referenceColumnName(), 'onDelete' => 'CASCADE')); } $mapping['joinTableColumns'] = array(); foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) { if (empty($joinColumn['name'])) { $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']); } if (empty($joinColumn['referencedColumnName'])) { $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName(); } if ($joinColumn['name'][0] === '`') { $joinColumn['name'] = trim($joinColumn['name'], '`'); $joinColumn['quoted'] = true; } if ($joinColumn['referencedColumnName'][0] === '`') { $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`'); $joinColumn['quoted'] = true; } if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') { $mapping['isOnDeleteCascade'] = true; } $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName']; $mapping['joinTableColumns'][] = $joinColumn['name']; } foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) { if (empty($inverseJoinColumn['name'])) { $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']); } if (empty($inverseJoinColumn['referencedColumnName'])) { $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName(); } if ($inverseJoinColumn['name'][0] === '`') { $inverseJoinColumn['name'] = trim($inverseJoinColumn['name'], '`'); $inverseJoinColumn['quoted'] = true; } if ($inverseJoinColumn['referencedColumnName'][0] === '`') { $inverseJoinColumn['referencedColumnName'] = trim($inverseJoinColumn['referencedColumnName'], '`'); $inverseJoinColumn['quoted'] = true; } if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') { $mapping['isOnDeleteCascade'] = true; } $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName']; $mapping['joinTableColumns'][] = $inverseJoinColumn['name']; } } $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false; if (isset($mapping['orderBy'])) { if ( ! is_array($mapping['orderBy'])) { throw new InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy'])); } } return $mapping; } /** * {@inheritDoc} */ public function getIdentifierFieldNames() { return $this->identifier; } /** * Gets the name of the single id field. Note that this only works on * entity classes that have a single-field pk. * * @return string * * @throws MappingException If the class has a composite primary key. */ public function getSingleIdentifierFieldName() { if ($this->isIdentifierComposite) { throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name); } return $this->identifier[0]; } /** * Gets the column name of the single id column. Note that this only works on * entity classes that have a single-field pk. * * @return string * * @throws MappingException If the class has a composite primary key. */ public function getSingleIdentifierColumnName() { return $this->getColumnName($this->getSingleIdentifierFieldName()); } /** * INTERNAL: * Sets the mapped identifier/primary key fields of this class. * Mainly used by the ClassMetadataFactory to assign inherited identifiers. * * @param array $identifier * * @return void */ public function setIdentifier(array $identifier) { $this->identifier = $identifier; $this->isIdentifierComposite = (count($this->identifier) > 1); } /** * {@inheritDoc} */ public function getIdentifier() { return $this->identifier; } /** * {@inheritDoc} */ public function hasField($fieldName) { return isset($this->fieldMappings[$fieldName]); } /** * Gets an array containing all the column names. * * @param array|null $fieldNames * * @return array */ public function getColumnNames(array $fieldNames = null) { if ($fieldNames === null) { return array_keys($this->fieldNames); } else { $columnNames = array(); foreach ($fieldNames as $fieldName) { $columnNames[] = $this->getColumnName($fieldName); } return $columnNames; } } /** * Returns an array with all the identifier column names. * * @return array */ public function getIdentifierColumnNames() { $columnNames = array(); foreach ($this->identifier as $idProperty) { if (isset($this->fieldMappings[$idProperty])) { $columnNames[] = $this->fieldMappings[$idProperty]['columnName']; continue; } // Association defined as Id field $joinColumns = $this->associationMappings[$idProperty]['joinColumns']; $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns); $columnNames = array_merge($columnNames, $assocColumnNames); } return $columnNames; } /** * Sets the type of Id generator to use for the mapped class. * * @param int $generatorType * * @return void */ public function setIdGeneratorType($generatorType) { $this->generatorType = $generatorType; } /** * Checks whether the mapped class uses an Id generator. * * @return boolean TRUE if the mapped class uses an Id generator, FALSE otherwise. */ public function usesIdGenerator() { return $this->generatorType != self::GENERATOR_TYPE_NONE; } /** * @return boolean */ public function isInheritanceTypeNone() { return $this->inheritanceType == self::INHERITANCE_TYPE_NONE; } /** * Checks whether the mapped class uses the JOINED inheritance mapping strategy. * * @return boolean TRUE if the class participates in a JOINED inheritance mapping, * FALSE otherwise. */ public function isInheritanceTypeJoined() { return $this->inheritanceType == self::INHERITANCE_TYPE_JOINED; } /** * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy. * * @return boolean TRUE if the class participates in a SINGLE_TABLE inheritance mapping, * FALSE otherwise. */ public function isInheritanceTypeSingleTable() { return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE; } /** * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy. * * @return boolean TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping, * FALSE otherwise. */ public function isInheritanceTypeTablePerClass() { return $this->inheritanceType == self::INHERITANCE_TYPE_TABLE_PER_CLASS; } /** * Checks whether the class uses an identity column for the Id generation. * * @return boolean TRUE if the class uses the IDENTITY generator, FALSE otherwise. */ public function isIdGeneratorIdentity() { return $this->generatorType == self::GENERATOR_TYPE_IDENTITY; } /** * Checks whether the class uses a sequence for id generation. * * @return boolean TRUE if the class uses the SEQUENCE generator, FALSE otherwise. */ public function isIdGeneratorSequence() { return $this->generatorType == self::GENERATOR_TYPE_SEQUENCE; } /** * Checks whether the class uses a table for id generation. * * @return boolean TRUE if the class uses the TABLE generator, FALSE otherwise. */ public function isIdGeneratorTable() { return $this->generatorType == self::GENERATOR_TYPE_TABLE; } /** * Checks whether the class has a natural identifier/pk (which means it does * not use any Id generator. * * @return boolean */ public function isIdentifierNatural() { return $this->generatorType == self::GENERATOR_TYPE_NONE; } /** * Checks whether the class use a UUID for id generation. * * @return boolean */ public function isIdentifierUuid() { return $this->generatorType == self::GENERATOR_TYPE_UUID; } /** * Gets the type of a field. * * @param string $fieldName * * @return \Doctrine\DBAL\Types\Type|string|null */ public function getTypeOfField($fieldName) { return isset($this->fieldMappings[$fieldName]) ? $this->fieldMappings[$fieldName]['type'] : null; } /** * Gets the type of a column. * * @param string $columnName * * @return \Doctrine\DBAL\Types\Type */ public function getTypeOfColumn($columnName) { return $this->getTypeOfField($this->getFieldName($columnName)); } /** * Gets the name of the primary table. * * @return string */ public function getTableName() { return $this->table['name']; } /** * Gets the table name to use for temporary identifier tables of this class. * * @return string */ public function getTemporaryIdTableName() { // replace dots with underscores because PostgreSQL creates temporary tables in a special schema return str_replace('.', '_', $this->getTableName() . '_id_tmp'); } /** * Sets the mapped subclasses of this class. * * @param array $subclasses The names of all mapped subclasses. * * @return void */ public function setSubclasses(array $subclasses) { foreach ($subclasses as $subclass) { $this->subClasses[] = $this->fullyQualifiedClassName($subclass); } } /** * Sets the parent class names. * Assumes that the class names in the passed array are in the order: * directParent -> directParentParent -> directParentParentParent ... -> root. * * @param array $classNames * * @return void */ public function setParentClasses(array $classNames) { $this->parentClasses = $classNames; if (count($classNames) > 0) { $this->rootEntityName = array_pop($classNames); } } /** * Sets the inheritance type used by the class and its subclasses. * * @param integer $type * * @return void * * @throws MappingException */ public function setInheritanceType($type) { if ( ! $this->_isInheritanceType($type)) { throw MappingException::invalidInheritanceType($this->name, $type); } $this->inheritanceType = $type; } /** * Sets the association to override association mapping of property for an entity relationship. * * @param string $fieldName * @param array $overrideMapping * * @return void * * @throws MappingException */ public function setAssociationOverride($fieldName, array $overrideMapping) { if ( ! isset($this->associationMappings[$fieldName])) { throw MappingException::invalidOverrideFieldName($this->name, $fieldName); } $mapping = $this->associationMappings[$fieldName]; if (isset($overrideMapping['joinColumns'])) { $mapping['joinColumns'] = $overrideMapping['joinColumns']; } if (isset($overrideMapping['joinTable'])) { $mapping['joinTable'] = $overrideMapping['joinTable']; } $mapping['joinColumnFieldNames'] = null; $mapping['joinTableColumns'] = null; $mapping['sourceToTargetKeyColumns'] = null; $mapping['relationToSourceKeyColumns'] = null; $mapping['relationToTargetKeyColumns'] = null; switch ($mapping['type']) { case self::ONE_TO_ONE: $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); break; case self::ONE_TO_MANY: $mapping = $this->_validateAndCompleteOneToManyMapping($mapping); break; case self::MANY_TO_ONE: $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); break; case self::MANY_TO_MANY: $mapping = $this->_validateAndCompleteManyToManyMapping($mapping); break; } $this->associationMappings[$fieldName] = $mapping; } /** * Sets the override for a mapped field. * * @param string $fieldName * @param array $overrideMapping * * @return void * * @throws MappingException */ public function setAttributeOverride($fieldName, array $overrideMapping) { if ( ! isset($this->fieldMappings[$fieldName])) { throw MappingException::invalidOverrideFieldName($this->name, $fieldName); } $mapping = $this->fieldMappings[$fieldName]; if (isset($mapping['id'])) { $overrideMapping['id'] = $mapping['id']; } if ( ! isset($overrideMapping['type']) || $overrideMapping['type'] === null) { $overrideMapping['type'] = $mapping['type']; } if ( ! isset($overrideMapping['fieldName']) || $overrideMapping['fieldName'] === null) { $overrideMapping['fieldName'] = $mapping['fieldName']; } if ($overrideMapping['type'] !== $mapping['type']) { throw MappingException::invalidOverrideFieldType($this->name, $fieldName); } unset($this->fieldMappings[$fieldName]); unset($this->fieldNames[$mapping['columnName']]); unset($this->columnNames[$mapping['fieldName']]); $this->_validateAndCompleteFieldMapping($overrideMapping); $this->fieldMappings[$fieldName] = $overrideMapping; } /** * Checks whether a mapped field is inherited from an entity superclass. * * @param string $fieldName * * @return bool TRUE if the field is inherited, FALSE otherwise. */ public function isInheritedField($fieldName) { return isset($this->fieldMappings[$fieldName]['inherited']); } /** * Checks if this entity is the root in any entity-inheritance-hierarchy. * * @return bool */ public function isRootEntity() { return $this->name == $this->rootEntityName; } /** * Checks whether a mapped association field is inherited from a superclass. * * @param string $fieldName * * @return boolean TRUE if the field is inherited, FALSE otherwise. */ public function isInheritedAssociation($fieldName) { return isset($this->associationMappings[$fieldName]['inherited']); } /** * Sets the name of the primary table the class is mapped to. * * @param string $tableName The table name. * * @return void * * @deprecated Use {@link setPrimaryTable}. */ public function setTableName($tableName) { $this->table['name'] = $tableName; } /** * Sets the primary table definition. The provided array supports the * following structure: * * name => (optional, defaults to class name) * indexes => array of indexes (optional) * uniqueConstraints => array of constraints (optional) * * If a key is omitted, the current value is kept. * * @param array $table The table description. * * @return void */ public function setPrimaryTable(array $table) { if (isset($table['name'])) { if ($table['name'][0] === '`') { $table['name'] = trim($table['name'], '`'); $this->table['quoted'] = true; } $this->table['name'] = $table['name']; } if (isset($table['indexes'])) { $this->table['indexes'] = $table['indexes']; } if (isset($table['uniqueConstraints'])) { $this->table['uniqueConstraints'] = $table['uniqueConstraints']; } if (isset($table['options'])) { $this->table['options'] = $table['options']; } } /** * Checks whether the given type identifies an inheritance type. * * @param integer $type * * @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise. */ private function _isInheritanceType($type) { return $type == self::INHERITANCE_TYPE_NONE || $type == self::INHERITANCE_TYPE_SINGLE_TABLE || $type == self::INHERITANCE_TYPE_JOINED || $type == self::INHERITANCE_TYPE_TABLE_PER_CLASS; } /** * Adds a mapped field to the class. * * @param array $mapping The field mapping. * * @return void * * @throws MappingException */ public function mapField(array $mapping) { $this->_validateAndCompleteFieldMapping($mapping); if (isset($this->fieldMappings[$mapping['fieldName']]) || isset($this->associationMappings[$mapping['fieldName']])) { throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']); } $this->fieldMappings[$mapping['fieldName']] = $mapping; } /** * INTERNAL: * Adds an association mapping without completing/validating it. * This is mainly used to add inherited association mappings to derived classes. * * @param array $mapping * * @return void * * @throws MappingException */ public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/) { if (isset($this->associationMappings[$mapping['fieldName']])) { throw MappingException::duplicateAssociationMapping($this->name, $mapping['fieldName']); } $this->associationMappings[$mapping['fieldName']] = $mapping; } /** * INTERNAL: * Adds a field mapping without completing/validating it. * This is mainly used to add inherited field mappings to derived classes. * * @param array $fieldMapping * * @return void */ public function addInheritedFieldMapping(array $fieldMapping) { $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping; $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName']; $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName']; } /** * INTERNAL: * Adds a named query to this class. * * @param array $queryMapping * * @return void * * @throws MappingException */ public function addNamedQuery(array $queryMapping) { if (!isset($queryMapping['name'])) { throw MappingException::nameIsMandatoryForQueryMapping($this->name); } if (isset($this->namedQueries[$queryMapping['name']])) { throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); } if (!isset($queryMapping['query'])) { throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']); } $name = $queryMapping['name']; $query = $queryMapping['query']; $dql = str_replace('__CLASS__', $this->name, $query); $this->namedQueries[$name] = array( 'name' => $name, 'query' => $query, 'dql' => $dql ); } /** * INTERNAL: * Adds a named native query to this class. * * @param array $queryMapping * * @return void * * @throws MappingException */ public function addNamedNativeQuery(array $queryMapping) { if (!isset($queryMapping['name'])) { throw MappingException::nameIsMandatoryForQueryMapping($this->name); } if (isset($this->namedNativeQueries[$queryMapping['name']])) { throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); } if (!isset($queryMapping['query'])) { throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']); } if (!isset($queryMapping['resultClass']) && !isset($queryMapping['resultSetMapping'])) { throw MappingException::missingQueryMapping($this->name, $queryMapping['name']); } $queryMapping['isSelfClass'] = false; if (isset($queryMapping['resultClass'])) { if($queryMapping['resultClass'] === '__CLASS__') { $queryMapping['isSelfClass'] = true; $queryMapping['resultClass'] = $this->name; } $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']); $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\'); } $this->namedNativeQueries[$queryMapping['name']] = $queryMapping; } /** * INTERNAL: * Adds a sql result set mapping to this class. * * @param array $resultMapping * * @return void * * @throws MappingException */ public function addSqlResultSetMapping(array $resultMapping) { if (!isset($resultMapping['name'])) { throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name); } if (isset($this->sqlResultSetMappings[$resultMapping['name']])) { throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']); } if (isset($resultMapping['entities'])) { foreach ($resultMapping['entities'] as $key => $entityResult) { if (!isset($entityResult['entityClass'])) { throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']); } $entityResult['isSelfClass'] = false; if($entityResult['entityClass'] === '__CLASS__') { $entityResult['isSelfClass'] = true; $entityResult['entityClass'] = $this->name; } $entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']); $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\'); $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass']; if (isset($entityResult['fields'])) { foreach ($entityResult['fields'] as $k => $field) { if (!isset($field['name'])) { throw MappingException::missingResultSetMappingFieldName($this->name, $resultMapping['name']); } if (!isset($field['column'])) { $fieldName = $field['name']; if(strpos($fieldName, '.')){ list(, $fieldName) = explode('.', $fieldName); } $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName; } } } } } $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping; } /** * Adds a one-to-one mapping. * * @param array $mapping The mapping. * * @return void */ public function mapOneToOne(array $mapping) { $mapping['type'] = self::ONE_TO_ONE; $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Adds a one-to-many mapping. * * @param array $mapping The mapping. * * @return void */ public function mapOneToMany(array $mapping) { $mapping['type'] = self::ONE_TO_MANY; $mapping = $this->_validateAndCompleteOneToManyMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Adds a many-to-one mapping. * * @param array $mapping The mapping. * * @return void */ public function mapManyToOne(array $mapping) { $mapping['type'] = self::MANY_TO_ONE; // A many-to-one mapping is essentially a one-one backreference $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Adds a many-to-many mapping. * * @param array $mapping The mapping. * * @return void */ public function mapManyToMany(array $mapping) { $mapping['type'] = self::MANY_TO_MANY; $mapping = $this->_validateAndCompleteManyToManyMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Stores the association mapping. * * @param array $assocMapping * * @return void * * @throws MappingException */ protected function _storeAssociationMapping(array $assocMapping) { $sourceFieldName = $assocMapping['fieldName']; if (isset($this->fieldMappings[$sourceFieldName]) || isset($this->associationMappings[$sourceFieldName])) { throw MappingException::duplicateFieldMapping($this->name, $sourceFieldName); } $this->associationMappings[$sourceFieldName] = $assocMapping; } /** * Registers a custom repository class for the entity class. * * @param string $repositoryClassName The class name of the custom mapper. * * @return void */ public function setCustomRepositoryClass($repositoryClassName) { $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName); } /** * Dispatches the lifecycle event of the given entity to the registered * lifecycle callbacks and lifecycle listeners. * * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker * * @param string $lifecycleEvent The lifecycle event. * @param object $entity The Entity on which the event occurred. * * @return void */ public function invokeLifecycleCallbacks($lifecycleEvent, $entity) { foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) { $entity->$callback(); } } /** * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event. * * @param string $lifecycleEvent * * @return boolean */ public function hasLifecycleCallbacks($lifecycleEvent) { return isset($this->lifecycleCallbacks[$lifecycleEvent]); } /** * Gets the registered lifecycle callbacks for an event. * * @param string $event * * @return array */ public function getLifecycleCallbacks($event) { return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : array(); } /** * Adds a lifecycle callback for entities of this class. * * @param string $callback * @param string $event * * @return void */ public function addLifecycleCallback($callback, $event) { if(isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) { return; } $this->lifecycleCallbacks[$event][] = $callback; } /** * Sets the lifecycle callbacks for entities of this class. * Any previously registered callbacks are overwritten. * * @param array $callbacks * * @return void */ public function setLifecycleCallbacks(array $callbacks) { $this->lifecycleCallbacks = $callbacks; } /** * Adds a entity listener for entities of this class. * * @param string $eventName The entity lifecycle event. * @param string $class The listener class. * @param string $method The listener callback method. * * @throws \Doctrine\ORM\Mapping\MappingException */ public function addEntityListener($eventName, $class, $method) { $class = $this->fullyQualifiedClassName($class); if ( ! class_exists($class)) { throw MappingException::entityListenerClassNotFound($class, $this->name); } if ( ! method_exists($class, $method)) { throw MappingException::entityListenerMethodNotFound($class, $method, $this->name); } $this->entityListeners[$eventName][] = array( 'class' => $class, 'method' => $method ); } /** * Sets the discriminator column definition. * * @param array $columnDef * * @return void * * @throws MappingException * * @see getDiscriminatorColumn() */ public function setDiscriminatorColumn($columnDef) { if ($columnDef !== null) { if ( ! isset($columnDef['name'])) { throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name); } if (isset($this->fieldNames[$columnDef['name']])) { throw MappingException::duplicateColumnName($this->name, $columnDef['name']); } if ( ! isset($columnDef['fieldName'])) { $columnDef['fieldName'] = $columnDef['name']; } if ( ! isset($columnDef['type'])) { $columnDef['type'] = "string"; } if (in_array($columnDef['type'], array("boolean", "array", "object", "datetime", "time", "date"))) { throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']); } $this->discriminatorColumn = $columnDef; } } /** * Sets the discriminator values used by this class. * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. * * @param array $map * * @return void */ public function setDiscriminatorMap(array $map) { foreach ($map as $value => $className) { $this->addDiscriminatorMapClass($value, $className); } } /** * Adds one entry of the discriminator map with a new class and corresponding name. * * @param string $name * @param string $className * * @return void * * @throws MappingException */ public function addDiscriminatorMapClass($name, $className) { $className = $this->fullyQualifiedClassName($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); } if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses)) { $this->subClasses[] = $className; } } } /** * Checks whether the class has a named query with the given query name. * * @param string $queryName * * @return boolean */ public function hasNamedQuery($queryName) { return isset($this->namedQueries[$queryName]); } /** * Checks whether the class has a named native query with the given query name. * * @param string $queryName * * @return boolean */ public function hasNamedNativeQuery($queryName) { return isset($this->namedNativeQueries[$queryName]); } /** * Checks whether the class has a named native query with the given query name. * * @param string $name * * @return boolean */ public function hasSqlResultSetMapping($name) { return isset($this->sqlResultSetMappings[$name]); } /** * {@inheritDoc} */ public function hasAssociation($fieldName) { return isset($this->associationMappings[$fieldName]); } /** * {@inheritDoc} */ public function isSingleValuedAssociation($fieldName) { return isset($this->associationMappings[$fieldName]) && ($this->associationMappings[$fieldName]['type'] & self::TO_ONE); } /** * {@inheritDoc} */ public function isCollectionValuedAssociation($fieldName) { return isset($this->associationMappings[$fieldName]) && ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE); } /** * Is this an association that only has a single join column? * * @param string $fieldName * * @return bool */ public function isAssociationWithSingleJoinColumn($fieldName) { return ( isset($this->associationMappings[$fieldName]) && isset($this->associationMappings[$fieldName]['joinColumns'][0]) && !isset($this->associationMappings[$fieldName]['joinColumns'][1]) ); } /** * Returns the single association join column (if any). * * @param string $fieldName * * @return string * * @throws MappingException */ public function getSingleAssociationJoinColumnName($fieldName) { if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) { throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); } return $this->associationMappings[$fieldName]['joinColumns'][0]['name']; } /** * Returns the single association referenced join column name (if any). * * @param string $fieldName * * @return string * * @throws MappingException */ public function getSingleAssociationReferencedJoinColumnName($fieldName) { if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) { throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); } return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName']; } /** * Used to retrieve a fieldname for either field or association from a given column. * * This method is used in foreign-key as primary-key contexts. * * @param string $columnName * * @return string * * @throws MappingException */ public function getFieldForColumn($columnName) { if (isset($this->fieldNames[$columnName])) { return $this->fieldNames[$columnName]; } else { foreach ($this->associationMappings as $assocName => $mapping) { if ($this->isAssociationWithSingleJoinColumn($assocName) && $this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) { return $assocName; } } throw MappingException::noFieldNameFoundForColumn($this->name, $columnName); } } /** * Sets the ID generator used to generate IDs for instances of this class. * * @param \Doctrine\ORM\Id\AbstractIdGenerator $generator * * @return void */ public function setIdGenerator($generator) { $this->idGenerator = $generator; } /** * Sets definition. * * @param array $definition * * @return void */ public function setCustomGeneratorDefinition(array $definition) { $this->customGeneratorDefinition = $definition; } /** * Sets the definition of the sequence ID generator for this class. * * The definition must have the following structure: * * array( * 'sequenceName' => 'name', * 'allocationSize' => 20, * 'initialValue' => 1 * 'quoted' => 1 * ) * * * @param array $definition * * @return void */ public function setSequenceGeneratorDefinition(array $definition) { if ( ! isset($definition['sequenceName'])) { throw MappingException::missingSequenceName($this->name); } if ($definition['sequenceName'][0] == '`') { $definition['sequenceName'] = trim($definition['sequenceName'], '`'); $definition['quoted'] = true; } $this->sequenceGeneratorDefinition = $definition; } /** * Sets the version field mapping used for versioning. Sets the default * value to use depending on the column type. * * @param array $mapping The version field mapping array. * * @return void * * @throws MappingException */ public function setVersionMapping(array &$mapping) { $this->isVersioned = true; $this->versionField = $mapping['fieldName']; if ( ! isset($mapping['default'])) { if (in_array($mapping['type'], array('integer', 'bigint', 'smallint'))) { $mapping['default'] = 1; } else if ($mapping['type'] == 'datetime') { $mapping['default'] = 'CURRENT_TIMESTAMP'; } else { throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']); } } } /** * Sets whether this class is to be versioned for optimistic locking. * * @param boolean $bool * * @return void */ public function setVersioned($bool) { $this->isVersioned = $bool; } /** * Sets the name of the field that is to be used for versioning if this class is * versioned for optimistic locking. * * @param string $versionField * * @return void */ public function setVersionField($versionField) { $this->versionField = $versionField; } /** * Marks this class as read only, no change tracking is applied to it. * * @return void */ public function markReadOnly() { $this->isReadOnly = true; } /** * {@inheritDoc} */ public function getFieldNames() { return array_keys($this->fieldMappings); } /** * {@inheritDoc} */ public function getAssociationNames() { return array_keys($this->associationMappings); } /** * {@inheritDoc} * * @throws InvalidArgumentException */ public function getAssociationTargetClass($assocName) { if ( ! isset($this->associationMappings[$assocName])) { throw new InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association."); } return $this->associationMappings[$assocName]['targetEntity']; } /** * {@inheritDoc} */ public function getName() { return $this->name; } /** * Gets the (possibly quoted) identifier column names for safe use in an SQL statement. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform * * @return array */ public function getQuotedIdentifierColumnNames($platform) { $quotedColumnNames = array(); foreach ($this->identifier as $idProperty) { if (isset($this->fieldMappings[$idProperty])) { $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName']) : $this->fieldMappings[$idProperty]['columnName']; continue; } // Association defined as Id field $joinColumns = $this->associationMappings[$idProperty]['joinColumns']; $assocQuotedColumnNames = array_map( function ($joinColumn) use ($platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['name']) : $joinColumn['name']; }, $joinColumns ); $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); } return $quotedColumnNames; } /** * Gets the (possibly quoted) column name of a mapped field for safe use in an SQL statement. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param string $field * @param \Doctrine\DBAL\Platforms\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. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param \Doctrine\DBAL\Platforms\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. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param array $assoc * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform * * @return string */ public function getQuotedJoinTableName(array $assoc, $platform) { return isset($assoc['joinTable']['quoted']) ? $platform->quoteIdentifier($assoc['joinTable']['name']) : $assoc['joinTable']['name']; } /** * {@inheritDoc} */ public function isAssociationInverseSide($fieldName) { return isset($this->associationMappings[$fieldName]) && ! $this->associationMappings[$fieldName]['isOwningSide']; } /** * {@inheritDoc} */ public function getAssociationMappedByTargetField($fieldName) { return $this->associationMappings[$fieldName]['mappedBy']; } /** * @param string $targetClass * * @return array */ public function getAssociationsByTargetClass($targetClass) { $relations = array(); foreach ($this->associationMappings as $mapping) { if ($mapping['targetEntity'] == $targetClass) { $relations[$mapping['fieldName']] = $mapping; } } return $relations; } /** * @param string $className * @return string */ public function fullyQualifiedClassName($className) { if ($className !== null && strpos($className, '\\') === false && strlen($this->namespace) > 0) { return $this->namespace . '\\' . $className; } return $className; } /** * @param string $name * * @return mixed */ public function getMetadataValue($name) { if (isset($this->$name)) { return $this->$name; } return null; } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Column.php000066400000000000000000000035361257105210500217360ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target({"PROPERTY","ANNOTATION"}) */ final class Column implements Annotation { /** * @var string */ public $name; /** * @var mixed */ public $type = 'string'; /** * @var integer */ public $length; /** * The precision for a decimal (exact numeric) column (Applies only for decimal column). * * @var integer */ public $precision = 0; /** * The scale for a decimal (exact numeric) column (Applies only for decimal column). * * @var integer */ public $scale = 0; /** * @var boolean */ public $unique = false; /** * @var boolean */ public $nullable = false; /** * @var array */ public $options = array(); /** * @var string */ public $columnDefinition; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/ColumnResult.php000066400000000000000000000027311257105210500231310ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * References name of a column in the SELECT clause of a SQL query. * Scalar result types can be included in the query result by specifying this annotation in the metadata. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class ColumnResult implements Annotation { /** * The name of a column in the SELECT clause of a SQL query. * * @var string */ public $name; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/CustomIdGenerator.php000066400000000000000000000022321257105210500240670ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class CustomIdGenerator implements Annotation { /** * @var string */ public $class; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/DefaultEntityListenerResolver.php000066400000000000000000000043221257105210500265040ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * The default DefaultEntityListener * * @since 2.4 * @author Fabio B. Silva */ class DefaultEntityListenerResolver implements EntityListenerResolver { /** * @var array Map to store entity listener instances. */ private $instances = array(); /** * {@inheritdoc} */ public function clear($className = null) { if ($className === null) { $this->instances = array(); return; } if (isset($this->instances[$className = trim($className, '\\')])) { unset($this->instances[$className]); } } /** * {@inheritdoc} */ public function register($object) { if ( ! is_object($object)) { throw new \InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object))); } $this->instances[get_class($object)] = $object; } /** * {@inheritdoc} */ public function resolve($className) { if (isset($this->instances[$className = trim($className, '\\')])) { return $this->instances[$className]; } return $this->instances[$className] = new $className(); } }doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php000066400000000000000000000046471257105210500247460ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * The default NamingStrategy * * * @link www.doctrine-project.org * @since 2.3 * @author Fabio B. Silva */ class DefaultNamingStrategy implements NamingStrategy { /** * {@inheritdoc} */ public function classToTableName($className) { if (strpos($className, '\\') !== false) { return substr($className, strrpos($className, '\\') + 1); } return $className; } /** * {@inheritdoc} */ public function propertyToColumnName($propertyName, $className = null) { return $propertyName; } /** * {@inheritdoc} */ public function referenceColumnName() { return 'id'; } /** * {@inheritdoc} */ public function joinColumnName($propertyName) { return $propertyName . '_' . $this->referenceColumnName(); } /** * {@inheritdoc} */ public function joinTableName($sourceEntity, $targetEntity, $propertyName = null) { return strtolower($this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity)); } /** * {@inheritdoc} */ public function joinKeyColumnName($entityName, $referencedColumnName = null) { return strtolower($this->classToTableName($entityName) . '_' . ($referencedColumnName ?: $this->referenceColumnName())); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/DefaultQuoteStrategy.php000066400000000000000000000120541257105210500246210ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * A set of rules for determining the physical column, alias and table quotes * * @since 2.3 * @author Fabio B. Silva */ class DefaultQuoteStrategy implements QuoteStrategy { /** * {@inheritdoc} */ public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform) { return isset($class->fieldMappings[$fieldName]['quoted']) ? $platform->quoteIdentifier($class->fieldMappings[$fieldName]['columnName']) : $class->fieldMappings[$fieldName]['columnName']; } /** * {@inheritdoc} */ public function getTableName(ClassMetadata $class, AbstractPlatform $platform) { return isset($class->table['quoted']) ? $platform->quoteIdentifier($class->table['name']) : $class->table['name']; } /** * {@inheritdoc} */ public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform) { return isset($definition['quoted']) ? $platform->quoteIdentifier($definition['sequenceName']) : $definition['sequenceName']; } /** * {@inheritdoc} */ public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['name']) : $joinColumn['name']; } /** * {@inheritdoc} */ public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['referencedColumnName']) : $joinColumn['referencedColumnName']; } /** * {@inheritdoc} */ public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform) { return isset($association['joinTable']['quoted']) ? $platform->quoteIdentifier($association['joinTable']['name']) : $association['joinTable']['name']; } /** * {@inheritdoc} */ public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform) { $quotedColumnNames = array(); foreach ($class->identifier as $fieldName) { if (isset($class->fieldMappings[$fieldName])) { $quotedColumnNames[] = $this->getColumnName($fieldName, $class, $platform); continue; } // Association defined as Id field $joinColumns = $class->associationMappings[$fieldName]['joinColumns']; $assocQuotedColumnNames = array_map( function ($joinColumn) use ($platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['name']) : $joinColumn['name']; }, $joinColumns ); $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); } return $quotedColumnNames; } /** * {@inheritdoc} */ public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ClassMetadata $class = null) { // 1 ) Concatenate column name and counter // 2 ) Trim the column alias to the maximum identifier length of the platform. // If the alias is to long, characters are cut off from the beginning. // 3 ) Strip non alphanumeric characters // 4 ) Prefix with "_" if the result its numeric $columnName = $columnName . $counter; $columnName = substr($columnName, -$platform->getMaxIdentifierLength()); $columnName = preg_replace('/[^A-Za-z0-9_]/', '', $columnName); $columnName = is_numeric($columnName) ? '_' . $columnName : $columnName; return $platform->getSQLResultCasing($columnName); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php000066400000000000000000000027111257105210500244600ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class DiscriminatorColumn implements Annotation { /** * @var string */ public $name; /** * @var string */ public $type; /** * @var integer */ public $length; /** * Field name used in non-object hydration (array/scalar). * * @var mixed */ public $fieldName; /** * @var string */ public $columnDefinition; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php000066400000000000000000000022351257105210500237410ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class DiscriminatorMap implements Annotation { /** * @var array */ public $value; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/000077500000000000000000000000001257105210500212145ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php000066400000000000000000000634761257105210500252330ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Mapping\JoinColumn; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\Persistence\Mapping\Driver\AnnotationDriver as AbstractAnnotationDriver; use Doctrine\ORM\Events; /** * The AnnotationDriver reads the mapping metadata from docblock annotations. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan H. Wage * @author Roman Borschel */ class AnnotationDriver extends AbstractAnnotationDriver { /** * {@inheritDoc} */ protected $entityAnnotationClasses = array( 'Doctrine\ORM\Mapping\Entity' => 1, 'Doctrine\ORM\Mapping\MappedSuperclass' => 2, ); /** * {@inheritDoc} */ public function loadMetadataForClass($className, ClassMetadata $metadata) { /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadataInfo */ $class = $metadata->getReflectionClass(); if ( ! $class) { // this happens when running annotation driver in combination with // static reflection services. This is not the nicest fix $class = new \ReflectionClass($metadata->name); } $classAnnotations = $this->reader->getClassAnnotations($class); if ($classAnnotations) { foreach ($classAnnotations as $key => $annot) { if ( ! is_numeric($key)) { continue; } $classAnnotations[get_class($annot)] = $annot; } } // Evaluate Entity annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) { $entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity']; 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); } // Evaluate Table annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\Table'])) { $tableAnnot = $classAnnotations['Doctrine\ORM\Mapping\Table']; $primaryTable = array( 'name' => $tableAnnot->name, 'schema' => $tableAnnot->schema ); if ($tableAnnot->indexes !== null) { foreach ($tableAnnot->indexes as $indexAnnot) { $index = array('columns' => $indexAnnot->columns); if ( ! empty($indexAnnot->name)) { $primaryTable['indexes'][$indexAnnot->name] = $index; } else { $primaryTable['indexes'][] = $index; } } } if ($tableAnnot->uniqueConstraints !== null) { foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) { $uniqueConstraint = array('columns' => $uniqueConstraintAnnot->columns); if ( ! empty($uniqueConstraintAnnot->name)) { $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint; } else { $primaryTable['uniqueConstraints'][] = $uniqueConstraint; } } } if ($tableAnnot->options !== null) { $primaryTable['options'] = $tableAnnot->options; } $metadata->setPrimaryTable($primaryTable); } // Evaluate NamedNativeQueries annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries'])) { $namedNativeQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries']; foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) { $metadata->addNamedNativeQuery(array( 'name' => $namedNativeQuery->name, 'query' => $namedNativeQuery->query, 'resultClass' => $namedNativeQuery->resultClass, 'resultSetMapping' => $namedNativeQuery->resultSetMapping, )); } } // Evaluate SqlResultSetMappings annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\SqlResultSetMappings'])) { $sqlResultSetMappingsAnnot = $classAnnotations['Doctrine\ORM\Mapping\SqlResultSetMappings']; foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) { $entities = array(); $columns = array(); foreach ($resultSetMapping->entities as $entityResultAnnot) { $entityResult = array( 'fields' => array(), 'entityClass' => $entityResultAnnot->entityClass, 'discriminatorColumn' => $entityResultAnnot->discriminatorColumn, ); foreach ($entityResultAnnot->fields as $fieldResultAnnot) { $entityResult['fields'][] = array( 'name' => $fieldResultAnnot->name, 'column' => $fieldResultAnnot->column ); } $entities[] = $entityResult; } foreach ($resultSetMapping->columns as $columnResultAnnot) { $columns[] = array( 'name' => $columnResultAnnot->name, ); } $metadata->addSqlResultSetMapping(array( 'name' => $resultSetMapping->name, 'entities' => $entities, 'columns' => $columns )); } } // Evaluate NamedQueries annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedQueries'])) { $namedQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries']; if ( ! is_array($namedQueriesAnnot->value)) { throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations."); } foreach ($namedQueriesAnnot->value as $namedQuery) { if ( ! ($namedQuery instanceof \Doctrine\ORM\Mapping\NamedQuery)) { throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations."); } $metadata->addNamedQuery(array( 'name' => $namedQuery->name, 'query' => $namedQuery->query )); } } // Evaluate InheritanceType annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\InheritanceType'])) { $inheritanceTypeAnnot = $classAnnotations['Doctrine\ORM\Mapping\InheritanceType']; $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value)); if ($metadata->inheritanceType != \Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate DiscriminatorColumn annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn'])) { $discrColumnAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn']; $metadata->setDiscriminatorColumn(array( 'name' => $discrColumnAnnot->name, 'type' => $discrColumnAnnot->type, 'length' => $discrColumnAnnot->length, 'columnDefinition' => $discrColumnAnnot->columnDefinition )); } else { $metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255)); } // Evaluate DiscriminatorMap annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'])) { $discrMapAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap']; $metadata->setDiscriminatorMap($discrMapAnnot->value); } } } // Evaluate DoctrineChangeTrackingPolicy annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\ChangeTrackingPolicy'])) { $changeTrackingAnnot = $classAnnotations['Doctrine\ORM\Mapping\ChangeTrackingPolicy']; $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAnnot->value)); } // Evaluate annotations on properties/fields /* @var $property \ReflectionProperty */ foreach ($class->getProperties() as $property) { if ($metadata->isMappedSuperclass && ! $property->isPrivate() || $metadata->isInheritedField($property->name) || $metadata->isInheritedAssociation($property->name)) { continue; } $mapping = array(); $mapping['fieldName'] = $property->getName(); // Check for JoinColumn/JoinColumns annotations $joinColumns = array(); if ($joinColumnAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumn')) { $joinColumns[] = $this->joinColumnToArray($joinColumnAnnot); } else if ($joinColumnsAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumns')) { foreach ($joinColumnsAnnot->value as $joinColumn) { $joinColumns[] = $this->joinColumnToArray($joinColumn); } } // Field can only be annotated with one of: // @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany if ($columnAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Column')) { if ($columnAnnot->type == null) { throw MappingException::propertyTypeIsRequired($className, $property->getName()); } $mapping = $this->columnToArray($property->getName(), $columnAnnot); if ($idAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { $mapping['id'] = true; } if ($generatedValueAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\GeneratedValue')) { $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAnnot->strategy)); } if ($this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Version')) { $metadata->setVersionMapping($mapping); } $metadata->mapField($mapping); // Check for SequenceGenerator/TableGenerator definition if ($seqGeneratorAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\SequenceGenerator')) { $metadata->setSequenceGeneratorDefinition(array( 'sequenceName' => $seqGeneratorAnnot->sequenceName, 'allocationSize' => $seqGeneratorAnnot->allocationSize, 'initialValue' => $seqGeneratorAnnot->initialValue )); } else if ($this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\TableGenerator')) { throw MappingException::tableIdGeneratorNotImplemented($className); } else if ($customGeneratorAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\CustomIdGenerator')) { $metadata->setCustomGeneratorDefinition(array( 'class' => $customGeneratorAnnot->class )); } } else if ($oneToOneAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) { if ($idAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { $mapping['id'] = true; } $mapping['targetEntity'] = $oneToOneAnnot->targetEntity; $mapping['joinColumns'] = $joinColumns; $mapping['mappedBy'] = $oneToOneAnnot->mappedBy; $mapping['inversedBy'] = $oneToOneAnnot->inversedBy; $mapping['cascade'] = $oneToOneAnnot->cascade; $mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAnnot->fetch); $metadata->mapOneToOne($mapping); } else if ($oneToManyAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToMany')) { $mapping['mappedBy'] = $oneToManyAnnot->mappedBy; $mapping['targetEntity'] = $oneToManyAnnot->targetEntity; $mapping['cascade'] = $oneToManyAnnot->cascade; $mapping['indexBy'] = $oneToManyAnnot->indexBy; $mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAnnot->fetch); if ($orderByAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { $mapping['orderBy'] = $orderByAnnot->value; } $metadata->mapOneToMany($mapping); } else if ($manyToOneAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) { if ($idAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { $mapping['id'] = true; } $mapping['joinColumns'] = $joinColumns; $mapping['cascade'] = $manyToOneAnnot->cascade; $mapping['inversedBy'] = $manyToOneAnnot->inversedBy; $mapping['targetEntity'] = $manyToOneAnnot->targetEntity; $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAnnot->fetch); $metadata->mapManyToOne($mapping); } else if ($manyToManyAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToMany')) { $joinTable = array(); if ($joinTableAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinTable')) { $joinTable = array( 'name' => $joinTableAnnot->name, 'schema' => $joinTableAnnot->schema ); foreach ($joinTableAnnot->joinColumns as $joinColumn) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); } foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); } } $mapping['joinTable'] = $joinTable; $mapping['targetEntity'] = $manyToManyAnnot->targetEntity; $mapping['mappedBy'] = $manyToManyAnnot->mappedBy; $mapping['inversedBy'] = $manyToManyAnnot->inversedBy; $mapping['cascade'] = $manyToManyAnnot->cascade; $mapping['indexBy'] = $manyToManyAnnot->indexBy; $mapping['orphanRemoval'] = $manyToManyAnnot->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnot->fetch); if ($orderByAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { $mapping['orderBy'] = $orderByAnnot->value; } $metadata->mapManyToMany($mapping); } } // Evaluate AssociationOverrides annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\AssociationOverrides'])) { $associationOverridesAnnot = $classAnnotations['Doctrine\ORM\Mapping\AssociationOverrides']; foreach ($associationOverridesAnnot->value as $associationOverride) { $override = array(); $fieldName = $associationOverride->name; // Check for JoinColumn/JoinColumns annotations if ($associationOverride->joinColumns) { $joinColumns = array(); foreach ($associationOverride->joinColumns as $joinColumn) { $joinColumns[] = $this->joinColumnToArray($joinColumn); } $override['joinColumns'] = $joinColumns; } // Check for JoinTable annotations if ($associationOverride->joinTable) { $joinTableAnnot = $associationOverride->joinTable; $joinTable = array( 'name' => $joinTableAnnot->name, 'schema' => $joinTableAnnot->schema ); foreach ($joinTableAnnot->joinColumns as $joinColumn) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); } foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); } $override['joinTable'] = $joinTable; } $metadata->setAssociationOverride($fieldName, $override); } } // Evaluate AttributeOverrides annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\AttributeOverrides'])) { $attributeOverridesAnnot = $classAnnotations['Doctrine\ORM\Mapping\AttributeOverrides']; foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) { $attributeOverride = $this->columnToArray($attributeOverrideAnnot->name, $attributeOverrideAnnot->column); $metadata->setAttributeOverride($attributeOverrideAnnot->name, $attributeOverride); } } // Evaluate EntityListeners annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\EntityListeners'])) { $entityListenersAnnot = $classAnnotations['Doctrine\ORM\Mapping\EntityListeners']; foreach ($entityListenersAnnot->value as $item) { $listenerClassName = $metadata->fullyQualifiedClassName($item); if ( ! class_exists($listenerClassName)) { throw MappingException::entityListenerClassNotFound($listenerClassName, $className); } $hasMapping = false; $listenerClass = new \ReflectionClass($listenerClassName); /* @var $method \ReflectionMethod */ foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { // find method callbacks. $callbacks = $this->getMethodCallbacks($method); $hasMapping = $hasMapping ?: ( ! empty($callbacks)); foreach ($callbacks as $value) { $metadata->addEntityListener($value[1], $listenerClassName, $value[0]); } } // Evaluate the listener using naming convention. if ( ! $hasMapping ) { EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName); } } } // Evaluate @HasLifecycleCallbacks annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\HasLifecycleCallbacks'])) { /* @var $method \ReflectionMethod */ foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { foreach ($this->getMethodCallbacks($method) as $value) { $metadata->addLifecycleCallback($value[0], $value[1]); } } } } /** * Attempts to resolve the fetch mode. * * @param string $className The class name. * @param string $fetchMode The fetch mode. * * @return integer The fetch mode as defined in ClassMetadata. * * @throws MappingException If the fetch mode is not valid. */ private function getFetchMode($className, $fetchMode) { if( ! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) { throw MappingException::invalidFetchMode($className, $fetchMode); } return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode); } /** * Parses the given method. * * @param \ReflectionMethod $method * * @return array */ private function getMethodCallbacks(\ReflectionMethod $method) { $callbacks = array(); $annotations = $this->reader->getMethodAnnotations($method); foreach ($annotations as $annot) { if ($annot instanceof \Doctrine\ORM\Mapping\PrePersist) { $callbacks[] = array($method->name, Events::prePersist); } if ($annot instanceof \Doctrine\ORM\Mapping\PostPersist) { $callbacks[] = array($method->name, Events::postPersist); } if ($annot instanceof \Doctrine\ORM\Mapping\PreUpdate) { $callbacks[] = array($method->name, Events::preUpdate); } if ($annot instanceof \Doctrine\ORM\Mapping\PostUpdate) { $callbacks[] = array($method->name, Events::postUpdate); } if ($annot instanceof \Doctrine\ORM\Mapping\PreRemove) { $callbacks[] = array($method->name, Events::preRemove); } if ($annot instanceof \Doctrine\ORM\Mapping\PostRemove) { $callbacks[] = array($method->name, Events::postRemove); } if ($annot instanceof \Doctrine\ORM\Mapping\PostLoad) { $callbacks[] = array($method->name, Events::postLoad); } if ($annot instanceof \Doctrine\ORM\Mapping\PreFlush) { $callbacks[] = array($method->name, Events::preFlush); } } return $callbacks; } /** * Parse the given JoinColumn as array * * @param JoinColumn $joinColumn * @return array */ private function joinColumnToArray(JoinColumn $joinColumn) { return array( 'name' => $joinColumn->name, 'unique' => $joinColumn->unique, 'nullable' => $joinColumn->nullable, 'onDelete' => $joinColumn->onDelete, 'columnDefinition' => $joinColumn->columnDefinition, 'referencedColumnName' => $joinColumn->referencedColumnName, ); } /** * Parse the given Column as array * * @param string $fieldName * @param Column $column * * @return array */ private function columnToArray($fieldName, Column $column) { $mapping = array( 'fieldName' => $fieldName, 'type' => $column->type, 'scale' => $column->scale, 'length' => $column->length, 'unique' => $column->unique, 'nullable' => $column->nullable, 'precision' => $column->precision ); if ($column->options) { $mapping['options'] = $column->options; } if (isset($column->name)) { $mapping['columnName'] = $column->name; } if (isset($column->columnDefinition)) { $mapping['columnDefinition'] = $column->columnDefinition; } return $mapping; } /** * Factory method for the Annotation Driver. * * @param array|string $paths * @param AnnotationReader|null $reader * * @return AnnotationDriver */ static public function create($paths = array(), AnnotationReader $reader = null) { if ($reader == null) { $reader = new AnnotationReader(); } return new self($reader, $paths); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php000066400000000000000000000433631257105210500246160ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\Util\Inflector; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException; /** * The DatabaseDriver reverse engineers the mapping metadata from a database. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Benjamin Eberlei */ class DatabaseDriver implements MappingDriver { /** * @var AbstractSchemaManager */ private $_sm; /** * @var array|null */ private $tables = null; /** * @var array */ private $classToTableNames = array(); /** * @var array */ private $manyToManyTables = array(); /** * @var array */ private $classNamesForTables = array(); /** * @var array */ private $fieldNamesForColumns = array(); /** * The namespace for the generated entities. * * @var string|null */ private $namespace; /** * @param AbstractSchemaManager $schemaManager */ public function __construct(AbstractSchemaManager $schemaManager) { $this->_sm = $schemaManager; } /** * Set the namespace for the generated entities. * * @param string $namespace * * @return void */ public function setNamespace($namespace) { $this->namespace = $namespace; } /** * {@inheritDoc} */ public function isTransient($className) { return true; } /** * {@inheritDoc} */ public function getAllClassNames() { $this->reverseEngineerMappingFromDatabase(); return array_keys($this->classToTableNames); } /** * Sets class name for a table. * * @param string $tableName * @param string $className * * @return void */ public function setClassNameForTable($tableName, $className) { $this->classNamesForTables[$tableName] = $className; } /** * Sets 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; } /** * Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager. * * @param array $entityTables * @param array $manyToManyTables * * @return void */ public function setTables($entityTables, $manyToManyTables) { $this->tables = $this->manyToManyTables = $this->classToTableNames = array(); foreach ($entityTables as $table) { $className = $this->getClassNameForTable($table->getName()); $this->classToTableNames[$className] = $table->getName(); $this->tables[$table->getName()] = $table; } foreach ($manyToManyTables as $table) { $this->manyToManyTables[$table->getName()] = $table; } } /** * {@inheritDoc} */ public function loadMetadataForClass($className, ClassMetadata $metadata) { $this->reverseEngineerMappingFromDatabase(); if ( ! isset($this->classToTableNames[$className])) { throw new \InvalidArgumentException("Unknown class " . $className); } $tableName = $this->classToTableNames[$className]; $metadata->name = $className; $metadata->table['name'] = $tableName; $this->buildIndexes($metadata); $this->buildFieldMappings($metadata); $this->buildToOneAssociationMappings($metadata); foreach ($this->manyToManyTables as $manyTable) { foreach ($manyTable->getForeignKeys() as $foreignKey) { // foreign key maps to the table of the current entity, many to many association probably exists if ( ! (strtolower($tableName) === strtolower($foreignKey->getForeignTableName()))) { continue; } $myFk = $foreignKey; $otherFk = null; foreach ($manyTable->getForeignKeys() as $foreignKey) { if ($foreignKey != $myFk) { $otherFk = $foreignKey; break; } } if ( ! $otherFk) { // the definition of this many to many table does not contain // enough foreign key information to continue reverse engineering. continue; } $localColumn = current($myFk->getColumns()); $associationMapping = array(); $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true); $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName()); if (current($manyTable->getColumns())->getName() == $localColumn) { $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true); $associationMapping['joinTable'] = array( 'name' => strtolower($manyTable->getName()), 'joinColumns' => array(), 'inverseJoinColumns' => array(), ); $fkCols = $myFk->getForeignColumns(); $cols = $myFk->getColumns(); for ($i = 0; $i < count($cols); $i++) { $associationMapping['joinTable']['joinColumns'][] = array( 'name' => $cols[$i], 'referencedColumnName' => $fkCols[$i], ); } $fkCols = $otherFk->getForeignColumns(); $cols = $otherFk->getColumns(); for ($i = 0; $i < count($cols); $i++) { $associationMapping['joinTable']['inverseJoinColumns'][] = array( 'name' => $cols[$i], 'referencedColumnName' => $fkCols[$i], ); } } else { $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true); } $metadata->mapManyToMany($associationMapping); break; } } } /** * @return void * * @throws \Doctrine\ORM\Mapping\MappingException */ private function reverseEngineerMappingFromDatabase() { if ($this->tables !== null) { return; } $tables = array(); foreach ($this->_sm->listTableNames() as $tableName) { $tables[$tableName] = $this->_sm->listTableDetails($tableName); } $this->tables = $this->manyToManyTables = $this->classToTableNames = array(); foreach ($tables as $tableName => $table) { $foreignKeys = ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) ? $table->getForeignKeys() : array(); $allForeignKeyColumns = array(); foreach ($foreignKeys as $foreignKey) { $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns()); } if ( ! $table->hasPrimaryKey()) { throw new MappingException( "Table " . $table->getName() . " has no primary key. Doctrine does not ". "support reverse engineering from tables that don't have a primary key." ); } $pkColumns = $table->getPrimaryKey()->getColumns(); sort($pkColumns); sort($allForeignKeyColumns); if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) { $this->manyToManyTables[$tableName] = $table; } else { // lower-casing is necessary because of Oracle Uppercase Tablenames, // assumption is lower-case + underscore separated. $className = $this->getClassNameForTable($tableName); $this->tables[$tableName] = $table; $this->classToTableNames[$className] = $tableName; } } } /** * Build indexes from a class metadata. * * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata */ private function buildIndexes(ClassMetadataInfo $metadata) { $tableName = $metadata->table['name']; $indexes = $this->tables[$tableName]->getIndexes(); foreach($indexes as $index){ if ($index->isPrimary()) { continue; } $indexName = $index->getName(); $indexColumns = $index->getColumns(); $constraintType = $index->isUnique() ? 'uniqueConstraints' : 'indexes'; $metadata->table[$constraintType][$indexName]['columns'] = $indexColumns; } } /** * Build field mapping from class metadata. * * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata */ private function buildFieldMappings(ClassMetadataInfo $metadata) { $tableName = $metadata->table['name']; $columns = $this->tables[$tableName]->getColumns(); $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); $foreignKeys = $this->getTableForeignKeys($this->tables[$tableName]); $allForeignKeys = array(); foreach ($foreignKeys as $foreignKey) { $allForeignKeys = array_merge($allForeignKeys, $foreignKey->getLocalColumns()); } $ids = array(); $fieldMappings = array(); foreach ($columns as $column) { if (in_array($column->getName(), $allForeignKeys)) { continue; } $fieldMapping = $this->buildFieldMapping($tableName, $column); if ($primaryKeys && in_array($column->getName(), $primaryKeys)) { $fieldMapping['id'] = true; $ids[] = $fieldMapping; } $fieldMappings[] = $fieldMapping; } // We need to check for the columns here, because we might have associations as id as well. if ($ids && count($primaryKeys) == 1) { $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); } foreach ($fieldMappings as $fieldMapping) { $metadata->mapField($fieldMapping); } } /** * Build field mapping from a schema column definition * * @param string $tableName * @param \Doctrine\DBAL\Schema\Column $column * * @return array */ private function buildFieldMapping($tableName, Column $column) { $fieldMapping = array( 'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false), 'columnName' => $column->getName(), 'type' => strtolower((string) $column->getType()), 'nullable' => ( ! $column->getNotNull()), ); // Type specific elements switch ($fieldMapping['type']) { case Type::TARRAY: case Type::BLOB: case Type::GUID: case Type::JSON_ARRAY: case Type::OBJECT: case Type::SIMPLE_ARRAY: case Type::STRING: case Type::TEXT: $fieldMapping['length'] = $column->getLength(); $fieldMapping['fixed'] = $column->getFixed(); break; case Type::DECIMAL: case Type::FLOAT: $fieldMapping['precision'] = $column->getPrecision(); $fieldMapping['scale'] = $column->getScale(); break; case Type::INTEGER: case Type::BIGINT: case Type::SMALLINT: $fieldMapping['unsigned'] = $column->getUnsigned(); break; } // Comment if (($comment = $column->getComment()) !== null) { $fieldMapping['comment'] = $comment; } // Default if (($default = $column->getDefault()) !== null) { $fieldMapping['default'] = $default; } return $fieldMapping; } /** * Build to one (one to one, many to one) association mapping from class metadata. * * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata */ private function buildToOneAssociationMappings(ClassMetadataInfo $metadata) { $tableName = $metadata->table['name']; $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); $foreignKeys = $this->getTableForeignKeys($this->tables[$tableName]); foreach ($foreignKeys as $foreignKey) { $foreignTableName = $foreignKey->getForeignTableName(); $fkColumns = $foreignKey->getColumns(); $fkForeignColumns = $foreignKey->getForeignColumns(); $localColumn = current($fkColumns); $associationMapping = array( 'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true), 'targetEntity' => $this->getClassNameForTable($foreignTableName), ); if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) { $associationMapping['fieldName'] .= '2'; // "foo" => "foo2" } if ($primaryKeys && in_array($localColumn, $primaryKeys)) { $associationMapping['id'] = true; } for ($i = 0; $i < count($fkColumns); $i++) { $associationMapping['joinColumns'][] = array( 'name' => $fkColumns[$i], 'referencedColumnName' => $fkForeignColumns[$i], ); } // Here we need to check if $fkColumns are the same as $primaryKeys if ( ! array_diff($fkColumns, $primaryKeys)) { $metadata->mapOneToOne($associationMapping); } else { $metadata->mapManyToOne($associationMapping); } } } /** * Retreive schema table definition foreign keys. * * @param \Doctrine\DBAL\Schema\Table $table * * @return array */ private function getTableForeignKeys(Table $table) { return ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) ? $table->getForeignKeys() : array(); } /** * Retreive schema table definition primary keys. * * @param \Doctrine\DBAL\Schema\Table $table * * @return array */ private function getTablePrimaryKeys(Table $table) { try { return $table->getPrimaryKey()->getColumns(); } catch(SchemaException $e) { // Do nothing } return array(); } /** * Returns the mapped class name for a table if it exists. Otherwise return "classified" version. * * @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); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php000066400000000000000000000061401257105210500257130ustar00rootroot00000000000000. */ require_once __DIR__.'/../Annotation.php'; require_once __DIR__.'/../Entity.php'; require_once __DIR__.'/../MappedSuperclass.php'; require_once __DIR__.'/../InheritanceType.php'; require_once __DIR__.'/../DiscriminatorColumn.php'; require_once __DIR__.'/../DiscriminatorMap.php'; require_once __DIR__.'/../Id.php'; require_once __DIR__.'/../GeneratedValue.php'; require_once __DIR__.'/../Version.php'; require_once __DIR__.'/../JoinColumn.php'; require_once __DIR__.'/../JoinColumns.php'; require_once __DIR__.'/../Column.php'; require_once __DIR__.'/../OneToOne.php'; require_once __DIR__.'/../OneToMany.php'; require_once __DIR__.'/../ManyToOne.php'; require_once __DIR__.'/../ManyToMany.php'; require_once __DIR__.'/../ElementCollection.php'; require_once __DIR__.'/../Table.php'; require_once __DIR__.'/../UniqueConstraint.php'; require_once __DIR__.'/../Index.php'; require_once __DIR__.'/../JoinTable.php'; require_once __DIR__.'/../SequenceGenerator.php'; require_once __DIR__.'/../CustomIdGenerator.php'; require_once __DIR__.'/../ChangeTrackingPolicy.php'; require_once __DIR__.'/../OrderBy.php'; require_once __DIR__.'/../NamedQueries.php'; require_once __DIR__.'/../NamedQuery.php'; require_once __DIR__.'/../HasLifecycleCallbacks.php'; require_once __DIR__.'/../PrePersist.php'; require_once __DIR__.'/../PostPersist.php'; require_once __DIR__.'/../PreUpdate.php'; require_once __DIR__.'/../PostUpdate.php'; require_once __DIR__.'/../PreRemove.php'; require_once __DIR__.'/../PostRemove.php'; require_once __DIR__.'/../PostLoad.php'; require_once __DIR__.'/../PreFlush.php'; require_once __DIR__.'/../FieldResult.php'; require_once __DIR__.'/../ColumnResult.php'; require_once __DIR__.'/../EntityResult.php'; require_once __DIR__.'/../NamedNativeQuery.php'; require_once __DIR__.'/../NamedNativeQueries.php'; require_once __DIR__.'/../SqlResultSetMapping.php'; require_once __DIR__.'/../SqlResultSetMappings.php'; require_once __DIR__.'/../AssociationOverride.php'; require_once __DIR__.'/../AssociationOverrides.php'; require_once __DIR__.'/../AttributeOverride.php'; require_once __DIR__.'/../AttributeOverrides.php'; require_once __DIR__.'/../EntityListeners.php';doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php000066400000000000000000000024141257105210500241240ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain; /** * {@inheritDoc} * * @deprecated this driver will be removed. Use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain instead */ class DriverChain extends MappingDriverChain { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php000066400000000000000000000024101257105210500235250ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\PHPDriver as CommonPHPDriver; /** * {@inheritDoc} * * @deprecated this driver will be removed. Use Doctrine\Common\Persistence\Mapping\Driver\PHPDriver instead */ class PHPDriver extends CommonPHPDriver { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php000066400000000000000000000032051257105210500256470ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\SymfonyFileLocator; /** * XmlDriver that additionally looks for mapping information in a global file. * * @author Fabien Potencier * @author Benjamin Eberlei * @license MIT */ class SimplifiedXmlDriver extends XmlDriver { const DEFAULT_FILE_EXTENSION = '.orm.xml'; /** * {@inheritDoc} */ public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION) { $locator = new SymfonyFileLocator((array) $prefixes, $fileExtension); parent::__construct($locator, $fileExtension); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php000066400000000000000000000032101257105210500260050ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\SymfonyFileLocator; /** * YamlDriver that additionally looks for mapping information in a global file. * * @author Fabien Potencier * @author Benjamin Eberlei * @license MIT */ class SimplifiedYamlDriver extends YamlDriver { const DEFAULT_FILE_EXTENSION = '.orm.yml'; /** * {@inheritDoc} */ public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION) { $locator = new SymfonyFileLocator((array) $prefixes, $fileExtension); parent::__construct($locator, $fileExtension); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/StaticPHPDriver.php000066400000000000000000000024461257105210500247060ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver as CommonStaticPHPDriver; /** * {@inheritDoc} * * @deprecated this driver will be removed. Use Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver instead */ class StaticPHPDriver extends CommonStaticPHPDriver { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php000066400000000000000000000756701257105210500236600ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Driver; use SimpleXMLElement; use Doctrine\Common\Persistence\Mapping\Driver\FileDriver; use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; /** * XmlDriver is a metadata driver that enables mapping through XML files. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan H. Wage * @author Roman Borschel */ class XmlDriver extends FileDriver { const DEFAULT_FILE_EXTENSION = '.dcm.xml'; /** * {@inheritDoc} */ public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION) { parent::__construct($locator, $fileExtension); } /** * {@inheritDoc} */ public function loadMetadataForClass($className, ClassMetadata $metadata) { /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadataInfo */ /* @var $xmlRoot SimpleXMLElement */ $xmlRoot = $this->getElement($className); if ($xmlRoot->getName() == 'entity') { if (isset($xmlRoot['repository-class'])) { $metadata->setCustomRepositoryClass((string)$xmlRoot['repository-class']); } if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) { $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); } // Evaluate attributes $table = array(); if (isset($xmlRoot['table'])) { $table['name'] = (string)$xmlRoot['table']; } $metadata->setPrimaryTable($table); // Evaluate named queries if (isset($xmlRoot->{'named-queries'})) { foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) { $metadata->addNamedQuery(array( 'name' => (string)$namedQueryElement['name'], 'query' => (string)$namedQueryElement['query'] )); } } // Evaluate native named queries if (isset($xmlRoot->{'named-native-queries'})) { foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) { $metadata->addNamedNativeQuery(array( 'name' => isset($nativeQueryElement['name']) ? (string)$nativeQueryElement['name'] : null, 'query' => isset($nativeQueryElement->query) ? (string)$nativeQueryElement->query : null, 'resultClass' => isset($nativeQueryElement['result-class']) ? (string)$nativeQueryElement['result-class'] : null, 'resultSetMapping' => isset($nativeQueryElement['result-set-mapping']) ? (string)$nativeQueryElement['result-set-mapping'] : null, )); } } // Evaluate sql result set mapping if (isset($xmlRoot->{'sql-result-set-mappings'})) { foreach ($xmlRoot->{'sql-result-set-mappings'}->{'sql-result-set-mapping'} as $rsmElement) { $entities = array(); $columns = array(); foreach ($rsmElement as $entityElement) { // if (isset($entityElement['entity-class'])) { $entityResult = array( 'fields' => array(), 'entityClass' => (string)$entityElement['entity-class'], 'discriminatorColumn' => isset($entityElement['discriminator-column']) ? (string)$entityElement['discriminator-column'] : null, ); foreach ($entityElement as $fieldElement) { $entityResult['fields'][] = array( 'name' => isset($fieldElement['name']) ? (string)$fieldElement['name'] : null, 'column' => isset($fieldElement['column']) ? (string)$fieldElement['column'] : null, ); } $entities[] = $entityResult; } // if (isset($entityElement['name'])) { $columns[] = array( 'name' => (string)$entityElement['name'], ); } } $metadata->addSqlResultSetMapping(array( 'name' => (string)$rsmElement['name'], 'entities' => $entities, 'columns' => $columns )); } } /* not implemented specially anyway. use table = schema.table if (isset($xmlRoot['schema'])) { $metadata->table['schema'] = (string)$xmlRoot['schema']; }*/ if (isset($xmlRoot['inheritance-type'])) { $inheritanceType = (string)$xmlRoot['inheritance-type']; $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType)); if ($metadata->inheritanceType != \Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate if (isset($xmlRoot->{'discriminator-column'})) { $discrColumn = $xmlRoot->{'discriminator-column'}; $metadata->setDiscriminatorColumn(array( 'name' => isset($discrColumn['name']) ? (string)$discrColumn['name'] : null, 'type' => isset($discrColumn['type']) ? (string)$discrColumn['type'] : null, 'length' => isset($discrColumn['length']) ? (string)$discrColumn['length'] : null, 'columnDefinition' => isset($discrColumn['column-definition']) ? (string)$discrColumn['column-definition'] : null )); } else { $metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255)); } // Evaluate if (isset($xmlRoot->{'discriminator-map'})) { $map = array(); foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) { $map[(string)$discrMapElement['value']] = (string)$discrMapElement['class']; } $metadata->setDiscriminatorMap($map); } } } // Evaluate if (isset($xmlRoot['change-tracking-policy'])) { $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . strtoupper((string)$xmlRoot['change-tracking-policy']))); } // Evaluate if (isset($xmlRoot->indexes)) { $metadata->table['indexes'] = array(); foreach ($xmlRoot->indexes->index as $index) { $columns = explode(',', (string)$index['columns']); if (isset($index['name'])) { $metadata->table['indexes'][(string)$index['name']] = array( 'columns' => $columns ); } else { $metadata->table['indexes'][] = array( 'columns' => $columns ); } } } // Evaluate if (isset($xmlRoot->{'unique-constraints'})) { $metadata->table['uniqueConstraints'] = array(); foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $unique) { $columns = explode(',', (string)$unique['columns']); if (isset($unique['name'])) { $metadata->table['uniqueConstraints'][(string)$unique['name']] = array( 'columns' => $columns ); } else { $metadata->table['uniqueConstraints'][] = array( 'columns' => $columns ); } } } if (isset($xmlRoot->options)) { $metadata->table['options'] = $this->_parseOptions($xmlRoot->options->children()); } // The mapping assignment is done in 2 times as a bug might occurs on some php/xml lib versions // The internal SimpleXmlIterator get resetted, to this generate a duplicate field exception $mappings = array(); // Evaluate mappings if (isset($xmlRoot->field)) { foreach ($xmlRoot->field as $fieldMapping) { $mapping = $this->columnToArray($fieldMapping); if (isset($mapping['version'])) { $metadata->setVersionMapping($mapping); unset($mapping['version']); } $metadata->mapField($mapping); } } foreach ($mappings as $mapping) { if (isset($mapping['version'])) { $metadata->setVersionMapping($mapping); } $metadata->mapField($mapping); } // Evaluate mappings $associationIds = array(); foreach ($xmlRoot->id as $idElement) { if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) { $associationIds[(string)$idElement['name']] = true; continue; } $mapping = array( 'id' => true, 'fieldName' => (string)$idElement['name'] ); if (isset($idElement['type'])) { $mapping['type'] = (string)$idElement['type']; } if (isset($idElement['length'])) { $mapping['length'] = (string)$idElement['length']; } if (isset($idElement['column'])) { $mapping['columnName'] = (string)$idElement['column']; } if (isset($idElement['column-definition'])) { $mapping['columnDefinition'] = (string)$idElement['column-definition']; } if (isset($idElement->options)) { $mapping['options'] = $this->_parseOptions($idElement->options->children()); } $metadata->mapField($mapping); if (isset($idElement->generator)) { $strategy = isset($idElement->generator['strategy']) ? (string)$idElement->generator['strategy'] : 'AUTO'; $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $strategy)); } // Check for SequenceGenerator/TableGenerator definition if (isset($idElement->{'sequence-generator'})) { $seqGenerator = $idElement->{'sequence-generator'}; $metadata->setSequenceGeneratorDefinition(array( 'sequenceName' => (string)$seqGenerator['sequence-name'], 'allocationSize' => (string)$seqGenerator['allocation-size'], 'initialValue' => (string)$seqGenerator['initial-value'] )); } else if (isset($idElement->{'custom-id-generator'})) { $customGenerator = $idElement->{'custom-id-generator'}; $metadata->setCustomGeneratorDefinition(array( 'class' => (string) $customGenerator['class'] )); } else if (isset($idElement->{'table-generator'})) { throw MappingException::tableIdGeneratorNotImplemented($className); } } // Evaluate mappings if (isset($xmlRoot->{'one-to-one'})) { foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) { $mapping = array( 'fieldName' => (string)$oneToOneElement['field'], 'targetEntity' => (string)$oneToOneElement['target-entity'] ); if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($oneToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$oneToOneElement['fetch']); } if (isset($oneToOneElement['mapped-by'])) { $mapping['mappedBy'] = (string)$oneToOneElement['mapped-by']; } else { if (isset($oneToOneElement['inversed-by'])) { $mapping['inversedBy'] = (string)$oneToOneElement['inversed-by']; } $joinColumns = array(); if (isset($oneToOneElement->{'join-column'})) { $joinColumns[] = $this->joinColumnToArray($oneToOneElement->{'join-column'}); } else if (isset($oneToOneElement->{'join-columns'})) { foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; } if (isset($oneToOneElement->cascade)) { $mapping['cascade'] = $this->_getCascadeMappings($oneToOneElement->cascade); } if (isset($oneToOneElement['orphan-removal'])) { $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToOneElement['orphan-removal']); } $metadata->mapOneToOne($mapping); } } // Evaluate mappings if (isset($xmlRoot->{'one-to-many'})) { foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) { $mapping = array( 'fieldName' => (string)$oneToManyElement['field'], 'targetEntity' => (string)$oneToManyElement['target-entity'], 'mappedBy' => (string)$oneToManyElement['mapped-by'] ); if (isset($oneToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$oneToManyElement['fetch']); } if (isset($oneToManyElement->cascade)) { $mapping['cascade'] = $this->_getCascadeMappings($oneToManyElement->cascade); } if (isset($oneToManyElement['orphan-removal'])) { $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToManyElement['orphan-removal']); } if (isset($oneToManyElement->{'order-by'})) { $orderBy = array(); foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) { $orderBy[(string)$orderByField['name']] = (string)$orderByField['direction']; } $mapping['orderBy'] = $orderBy; } if (isset($oneToManyElement['index-by'])) { $mapping['indexBy'] = (string)$oneToManyElement['index-by']; } else if (isset($oneToManyElement->{'index-by'})) { throw new \InvalidArgumentException(" is not a valid tag"); } $metadata->mapOneToMany($mapping); } } // Evaluate mappings if (isset($xmlRoot->{'many-to-one'})) { foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) { $mapping = array( 'fieldName' => (string)$manyToOneElement['field'], 'targetEntity' => (string)$manyToOneElement['target-entity'] ); if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($manyToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToOneElement['fetch']); } if (isset($manyToOneElement['inversed-by'])) { $mapping['inversedBy'] = (string)$manyToOneElement['inversed-by']; } $joinColumns = array(); if (isset($manyToOneElement->{'join-column'})) { $joinColumns[] = $this->joinColumnToArray($manyToOneElement->{'join-column'}); } else if (isset($manyToOneElement->{'join-columns'})) { foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; if (isset($manyToOneElement->cascade)) { $mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade); } $metadata->mapManyToOne($mapping); } } // Evaluate mappings if (isset($xmlRoot->{'many-to-many'})) { foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) { $mapping = array( 'fieldName' => (string)$manyToManyElement['field'], 'targetEntity' => (string)$manyToManyElement['target-entity'] ); if (isset($manyToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToManyElement['fetch']); } if (isset($manyToManyElement['orphan-removal'])) { $mapping['orphanRemoval'] = $this->evaluateBoolean($manyToManyElement['orphan-removal']); } if (isset($manyToManyElement['mapped-by'])) { $mapping['mappedBy'] = (string)$manyToManyElement['mapped-by']; } else if (isset($manyToManyElement->{'join-table'})) { if (isset($manyToManyElement['inversed-by'])) { $mapping['inversedBy'] = (string)$manyToManyElement['inversed-by']; } $joinTableElement = $manyToManyElement->{'join-table'}; $joinTable = array( 'name' => (string)$joinTableElement['name'] ); if (isset($joinTableElement['schema'])) { $joinTable['schema'] = (string)$joinTableElement['schema']; } foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } $mapping['joinTable'] = $joinTable; } if (isset($manyToManyElement->cascade)) { $mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement->cascade); } if (isset($manyToManyElement->{'order-by'})) { $orderBy = array(); foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) { $orderBy[(string)$orderByField['name']] = (string)$orderByField['direction']; } $mapping['orderBy'] = $orderBy; } if (isset($manyToManyElement['index-by'])) { $mapping['indexBy'] = (string)$manyToManyElement['index-by']; } else if (isset($manyToManyElement->{'index-by'})) { throw new \InvalidArgumentException(" is not a valid tag"); } $metadata->mapManyToMany($mapping); } } // Evaluate association-overrides if (isset($xmlRoot->{'attribute-overrides'})) { foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) { $fieldName = (string) $overrideElement['name']; foreach ($overrideElement->field as $field) { $mapping = $this->columnToArray($field); $mapping['fieldName'] = $fieldName; $metadata->setAttributeOverride($fieldName, $mapping); } } } // Evaluate association-overrides if (isset($xmlRoot->{'association-overrides'})) { foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) { $fieldName = (string) $overrideElement['name']; $override = array(); // Check for join-columns if (isset($overrideElement->{'join-columns'})) { $joinColumns = array(); foreach ($overrideElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } $override['joinColumns'] = $joinColumns; } // Check for join-table if ($overrideElement->{'join-table'}) { $joinTable = null; $joinTableElement = $overrideElement->{'join-table'}; $joinTable = array( 'name' => (string) $joinTableElement['name'], 'schema' => (string) $joinTableElement['schema'] ); if (isset($joinTableElement->{'join-columns'})) { foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } } if (isset($joinTableElement->{'inverse-join-columns'})) { foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } } $override['joinTable'] = $joinTable; } $metadata->setAssociationOverride($fieldName, $override); } } // Evaluate if (isset($xmlRoot->{'lifecycle-callbacks'})) { foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) { $metadata->addLifecycleCallback((string)$lifecycleCallback['method'], constant('Doctrine\ORM\Events::' . (string)$lifecycleCallback['type'])); } } // Evaluate entity listener if (isset($xmlRoot->{'entity-listeners'})) { foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} as $listenerElement) { $className = (string) $listenerElement['class']; // Evaluate the listener using naming convention. if($listenerElement->count() === 0) { EntityListenerBuilder::bindEntityListener($metadata, $className); continue; } foreach ($listenerElement as $callbackElement) { $eventName = (string) $callbackElement['type']; $methodName = (string) $callbackElement['method']; $metadata->addEntityListener($eventName, $className, $methodName); } } } } /** * Parses (nested) option elements. * * @param SimpleXMLElement $options The XML element. * * @return array The options array. */ private function _parseOptions(SimpleXMLElement $options) { $array = array(); /* @var $option SimpleXMLElement */ foreach ($options as $option) { if ($option->count()) { $value = $this->_parseOptions($option->children()); } else { $value = (string) $option; } $attr = $option->attributes(); if (isset($attr->name)) { $array[(string) $attr->name] = $value; } else { $array[] = $value; } } return $array; } /** * Constructs a joinColumn mapping array based on the information * found in the given SimpleXMLElement. * * @param SimpleXMLElement $joinColumnElement The XML element. * * @return array The mapping array. */ private function joinColumnToArray(SimpleXMLElement $joinColumnElement) { $joinColumn = array( 'name' => (string)$joinColumnElement['name'], 'referencedColumnName' => (string)$joinColumnElement['referenced-column-name'] ); if (isset($joinColumnElement['unique'])) { $joinColumn['unique'] = $this->evaluateBoolean($joinColumnElement['unique']); } if (isset($joinColumnElement['nullable'])) { $joinColumn['nullable'] = $this->evaluateBoolean($joinColumnElement['nullable']); } if (isset($joinColumnElement['on-delete'])) { $joinColumn['onDelete'] = (string)$joinColumnElement['on-delete']; } if (isset($joinColumnElement['column-definition'])) { $joinColumn['columnDefinition'] = (string)$joinColumnElement['column-definition']; } return $joinColumn; } /** * Parses the given field as array. * * @param SimpleXMLElement $fieldMapping * * @return array */ private function columnToArray(SimpleXMLElement $fieldMapping) { $mapping = array( 'fieldName' => (string) $fieldMapping['name'], ); if (isset($fieldMapping['type'])) { $mapping['type'] = (string) $fieldMapping['type']; } if (isset($fieldMapping['column'])) { $mapping['columnName'] = (string) $fieldMapping['column']; } if (isset($fieldMapping['length'])) { $mapping['length'] = (int) $fieldMapping['length']; } if (isset($fieldMapping['precision'])) { $mapping['precision'] = (int) $fieldMapping['precision']; } if (isset($fieldMapping['scale'])) { $mapping['scale'] = (int) $fieldMapping['scale']; } if (isset($fieldMapping['unique'])) { $mapping['unique'] = $this->evaluateBoolean($fieldMapping['unique']); } if (isset($fieldMapping['nullable'])) { $mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']); } if (isset($fieldMapping['version']) && $fieldMapping['version']) { $mapping['version'] = $this->evaluateBoolean($fieldMapping['version']); } if (isset($fieldMapping['column-definition'])) { $mapping['columnDefinition'] = (string) $fieldMapping['column-definition']; } if (isset($fieldMapping->options)) { $mapping['options'] = $this->_parseOptions($fieldMapping->options->children()); } return $mapping; } /** * Gathers a list of cascade options found in the given cascade element. * * @param SimpleXMLElement $cascadeElement The cascade element. * * @return array The list of cascade options. */ private function _getCascadeMappings($cascadeElement) { $cascades = array(); /* @var $action SimpleXmlElement */ foreach ($cascadeElement->children() as $action) { // According to the JPA specifications, XML uses "cascade-persist" // instead of "persist". Here, both variations // are supported because both YAML and Annotation use "persist" // and we want to make sure that this driver doesn't need to know // anything about the supported cascading actions $cascades[] = str_replace('cascade-', '', $action->getName()); } return $cascades; } /** * {@inheritDoc} */ protected function loadMappingFile($file) { $result = array(); $xmlElement = simplexml_load_file($file); if (isset($xmlElement->entity)) { foreach ($xmlElement->entity as $entityElement) { $entityName = (string)$entityElement['name']; $result[$entityName] = $entityElement; } } else if (isset($xmlElement->{'mapped-superclass'})) { foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) { $className = (string)$mappedSuperClass['name']; $result[$className] = $mappedSuperClass; } } return $result; } /** * @param mixed $element * * @return bool */ protected function evaluateBoolean($element) { $flag = (string)$element; return ($flag === true || $flag == "true" || $flag == "1"); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php000066400000000000000000000672201257105210500240120ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; use Doctrine\Common\Persistence\Mapping\Driver\FileDriver; use Doctrine\ORM\Mapping\MappingException; use Symfony\Component\Yaml\Yaml; /** * The YamlDriver reads the mapping metadata from yaml schema files. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan H. Wage * @author Roman Borschel */ class YamlDriver extends FileDriver { const DEFAULT_FILE_EXTENSION = '.dcm.yml'; /** * {@inheritDoc} */ public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION) { parent::__construct($locator, $fileExtension); } /** * {@inheritDoc} */ public function loadMetadataForClass($className, ClassMetadata $metadata) { /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadataInfo */ $element = $this->getElement($className); if ($element['type'] == 'entity') { 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); } // Evaluate root level properties $table = array(); if (isset($element['table'])) { $table['name'] = $element['table']; } $metadata->setPrimaryTable($table); // Evaluate named queries if (isset($element['namedQueries'])) { foreach ($element['namedQueries'] as $name => $queryMapping) { if (is_string($queryMapping)) { $queryMapping = array('query' => $queryMapping); } if ( ! isset($queryMapping['name'])) { $queryMapping['name'] = $name; } $metadata->addNamedQuery($queryMapping); } } // Evaluate named native queries if (isset($element['namedNativeQueries'])) { foreach ($element['namedNativeQueries'] as $name => $mappingElement) { if (!isset($mappingElement['name'])) { $mappingElement['name'] = $name; } $metadata->addNamedNativeQuery(array( 'name' => $mappingElement['name'], 'query' => isset($mappingElement['query']) ? $mappingElement['query'] : null, 'resultClass' => isset($mappingElement['resultClass']) ? $mappingElement['resultClass'] : null, 'resultSetMapping' => isset($mappingElement['resultSetMapping']) ? $mappingElement['resultSetMapping'] : null, )); } } // Evaluate sql result set mappings if (isset($element['sqlResultSetMappings'])) { foreach ($element['sqlResultSetMappings'] as $name => $resultSetMapping) { if (!isset($resultSetMapping['name'])) { $resultSetMapping['name'] = $name; } $entities = array(); $columns = array(); if (isset($resultSetMapping['entityResult'])) { foreach ($resultSetMapping['entityResult'] as $entityResultElement) { $entityResult = array( 'fields' => array(), 'entityClass' => isset($entityResultElement['entityClass']) ? $entityResultElement['entityClass'] : null, 'discriminatorColumn' => isset($entityResultElement['discriminatorColumn']) ? $entityResultElement['discriminatorColumn'] : null, ); if (isset($entityResultElement['fieldResult'])) { foreach ($entityResultElement['fieldResult'] as $fieldResultElement) { $entityResult['fields'][] = array( 'name' => isset($fieldResultElement['name']) ? $fieldResultElement['name'] : null, 'column' => isset($fieldResultElement['column']) ? $fieldResultElement['column'] : null, ); } } $entities[] = $entityResult; } } if (isset($resultSetMapping['columnResult'])) { foreach ($resultSetMapping['columnResult'] as $columnResultAnnot) { $columns[] = array( 'name' => isset($columnResultAnnot['name']) ? $columnResultAnnot['name'] : null, ); } } $metadata->addSqlResultSetMapping(array( 'name' => $resultSetMapping['name'], 'entities' => $entities, 'columns' => $columns )); } } /* not implemented specially anyway. use table = schema.table if (isset($element['schema'])) { $metadata->table['schema'] = $element['schema']; }*/ if (isset($element['inheritanceType'])) { $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType']))); if ($metadata->inheritanceType != \Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate discriminatorColumn if (isset($element['discriminatorColumn'])) { $discrColumn = $element['discriminatorColumn']; $metadata->setDiscriminatorColumn(array( 'name' => isset($discrColumn['name']) ? (string)$discrColumn['name'] : null, 'type' => isset($discrColumn['type']) ? (string)$discrColumn['type'] : null, 'length' => isset($discrColumn['length']) ? (string)$discrColumn['length'] : null, 'columnDefinition' => isset($discrColumn['columnDefinition']) ? (string)$discrColumn['columnDefinition'] : null )); } else { $metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255)); } // Evaluate discriminatorMap if (isset($element['discriminatorMap'])) { $metadata->setDiscriminatorMap($element['discriminatorMap']); } } } // Evaluate changeTrackingPolicy if (isset($element['changeTrackingPolicy'])) { $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . strtoupper($element['changeTrackingPolicy']))); } // Evaluate indexes if (isset($element['indexes'])) { foreach ($element['indexes'] as $name => $index) { if ( ! isset($index['name'])) { $index['name'] = $name; } if (is_string($index['columns'])) { $columns = explode(',', $index['columns']); $columns = array_map('trim', $columns); } else { $columns = $index['columns']; } $metadata->table['indexes'][$index['name']] = array( 'columns' => $columns ); } } // Evaluate uniqueConstraints if (isset($element['uniqueConstraints'])) { foreach ($element['uniqueConstraints'] as $name => $unique) { if ( ! isset($unique['name'])) { $unique['name'] = $name; } if (is_string($unique['columns'])) { $columns = explode(',', $unique['columns']); $columns = array_map('trim', $columns); } else { $columns = $unique['columns']; } $metadata->table['uniqueConstraints'][$unique['name']] = array( 'columns' => $columns ); } } if (isset($element['options'])) { $metadata->table['options'] = $element['options']; } $associationIds = array(); if (isset($element['id'])) { // Evaluate identifier settings foreach ($element['id'] as $name => $idElement) { if (isset($idElement['associationKey']) && $idElement['associationKey'] == true) { $associationIds[$name] = true; continue; } $mapping = array( 'id' => true, 'fieldName' => $name ); if (isset($idElement['type'])) { $mapping['type'] = $idElement['type']; } if (isset($idElement['column'])) { $mapping['columnName'] = $idElement['column']; } if (isset($idElement['length'])) { $mapping['length'] = $idElement['length']; } if (isset($idElement['columnDefinition'])) { $mapping['columnDefinition'] = $idElement['columnDefinition']; } if (isset($idElement['options'])) { $mapping['options'] = $idElement['options']; } $metadata->mapField($mapping); if (isset($idElement['generator'])) { $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . strtoupper($idElement['generator']['strategy']))); } // Check for SequenceGenerator/TableGenerator definition if (isset($idElement['sequenceGenerator'])) { $metadata->setSequenceGeneratorDefinition($idElement['sequenceGenerator']); } else if (isset($idElement['customIdGenerator'])) { $customGenerator = $idElement['customIdGenerator']; $metadata->setCustomGeneratorDefinition(array( 'class' => (string) $customGenerator['class'] )); } else if (isset($idElement['tableGenerator'])) { throw MappingException::tableIdGeneratorNotImplemented($className); } } } // Evaluate fields if (isset($element['fields'])) { foreach ($element['fields'] as $name => $fieldMapping) { $mapping = $this->columnToArray($name, $fieldMapping); if (isset($fieldMapping['id'])) { $mapping['id'] = true; if (isset($fieldMapping['generator']['strategy'])) { $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . strtoupper($fieldMapping['generator']['strategy']))); } } if (isset($mapping['version'])) { $metadata->setVersionMapping($mapping); unset($mapping['version']); } $metadata->mapField($mapping); } } // Evaluate oneToOne relationships if (isset($element['oneToOne'])) { foreach ($element['oneToOne'] as $name => $oneToOneElement) { $mapping = array( 'fieldName' => $name, 'targetEntity' => $oneToOneElement['targetEntity'] ); if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($oneToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneElement['fetch']); } if (isset($oneToOneElement['mappedBy'])) { $mapping['mappedBy'] = $oneToOneElement['mappedBy']; } else { if (isset($oneToOneElement['inversedBy'])) { $mapping['inversedBy'] = $oneToOneElement['inversedBy']; } $joinColumns = array(); if (isset($oneToOneElement['joinColumn'])) { $joinColumns[] = $this->joinColumnToArray($oneToOneElement['joinColumn']); } else if (isset($oneToOneElement['joinColumns'])) { foreach ($oneToOneElement['joinColumns'] as $joinColumnName => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; } if (isset($oneToOneElement['cascade'])) { $mapping['cascade'] = $oneToOneElement['cascade']; } if (isset($oneToOneElement['orphanRemoval'])) { $mapping['orphanRemoval'] = (bool)$oneToOneElement['orphanRemoval']; } $metadata->mapOneToOne($mapping); } } // Evaluate oneToMany relationships if (isset($element['oneToMany'])) { foreach ($element['oneToMany'] as $name => $oneToManyElement) { $mapping = array( 'fieldName' => $name, 'targetEntity' => $oneToManyElement['targetEntity'], 'mappedBy' => $oneToManyElement['mappedBy'] ); if (isset($oneToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyElement['fetch']); } if (isset($oneToManyElement['cascade'])) { $mapping['cascade'] = $oneToManyElement['cascade']; } if (isset($oneToManyElement['orphanRemoval'])) { $mapping['orphanRemoval'] = (bool)$oneToManyElement['orphanRemoval']; } if (isset($oneToManyElement['orderBy'])) { $mapping['orderBy'] = $oneToManyElement['orderBy']; } if (isset($oneToManyElement['indexBy'])) { $mapping['indexBy'] = $oneToManyElement['indexBy']; } $metadata->mapOneToMany($mapping); } } // Evaluate manyToOne relationships if (isset($element['manyToOne'])) { foreach ($element['manyToOne'] as $name => $manyToOneElement) { $mapping = array( 'fieldName' => $name, 'targetEntity' => $manyToOneElement['targetEntity'] ); if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($manyToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneElement['fetch']); } if (isset($manyToOneElement['inversedBy'])) { $mapping['inversedBy'] = $manyToOneElement['inversedBy']; } $joinColumns = array(); if (isset($manyToOneElement['joinColumn'])) { $joinColumns[] = $this->joinColumnToArray($manyToOneElement['joinColumn']); } else if (isset($manyToOneElement['joinColumns'])) { foreach ($manyToOneElement['joinColumns'] as $joinColumnName => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; if (isset($manyToOneElement['cascade'])) { $mapping['cascade'] = $manyToOneElement['cascade']; } $metadata->mapManyToOne($mapping); } } // Evaluate manyToMany relationships if (isset($element['manyToMany'])) { foreach ($element['manyToMany'] as $name => $manyToManyElement) { $mapping = array( 'fieldName' => $name, 'targetEntity' => $manyToManyElement['targetEntity'] ); if (isset($manyToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyElement['fetch']); } if (isset($manyToManyElement['mappedBy'])) { $mapping['mappedBy'] = $manyToManyElement['mappedBy']; } else if (isset($manyToManyElement['joinTable'])) { $joinTableElement = $manyToManyElement['joinTable']; $joinTable = array( 'name' => $joinTableElement['name'] ); if (isset($joinTableElement['schema'])) { $joinTable['schema'] = $joinTableElement['schema']; } foreach ($joinTableElement['joinColumns'] as $joinColumnName => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } foreach ($joinTableElement['inverseJoinColumns'] as $joinColumnName => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } $mapping['joinTable'] = $joinTable; } if (isset($manyToManyElement['inversedBy'])) { $mapping['inversedBy'] = $manyToManyElement['inversedBy']; } if (isset($manyToManyElement['cascade'])) { $mapping['cascade'] = $manyToManyElement['cascade']; } if (isset($manyToManyElement['orderBy'])) { $mapping['orderBy'] = $manyToManyElement['orderBy']; } if (isset($manyToManyElement['indexBy'])) { $mapping['indexBy'] = $manyToManyElement['indexBy']; } if (isset($manyToManyElement['orphanRemoval'])) { $mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval']; } $metadata->mapManyToMany($mapping); } } // Evaluate associationOverride if (isset($element['associationOverride']) && is_array($element['associationOverride'])) { foreach ($element['associationOverride'] as $fieldName => $associationOverrideElement) { $override = array(); // Check for joinColumn if (isset($associationOverrideElement['joinColumn'])) { $joinColumns = array(); foreach ($associationOverrideElement['joinColumn'] as $name => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $name; } $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } $override['joinColumns'] = $joinColumns; } // Check for joinTable if (isset($associationOverrideElement['joinTable'])) { $joinTableElement = $associationOverrideElement['joinTable']; $joinTable = array( 'name' => $joinTableElement['name'] ); if (isset($joinTableElement['schema'])) { $joinTable['schema'] = $joinTableElement['schema']; } foreach ($joinTableElement['joinColumns'] as $name => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $name; } $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } foreach ($joinTableElement['inverseJoinColumns'] as $name => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $name; } $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } $override['joinTable'] = $joinTable; } $metadata->setAssociationOverride($fieldName, $override); } } // Evaluate associationOverride if (isset($element['attributeOverride']) && is_array($element['attributeOverride'])) { foreach ($element['attributeOverride'] as $fieldName => $attributeOverrideElement) { $mapping = $this->columnToArray($fieldName, $attributeOverrideElement); $metadata->setAttributeOverride($fieldName, $mapping); } } // Evaluate lifeCycleCallbacks if (isset($element['lifecycleCallbacks'])) { foreach ($element['lifecycleCallbacks'] as $type => $methods) { foreach ($methods as $method) { $metadata->addLifecycleCallback($method, constant('Doctrine\ORM\Events::' . $type)); } } } // Evaluate entityListeners if (isset($element['entityListeners'])) { foreach ($element['entityListeners'] as $className => $entityListener) { // Evaluate the listener using naming convention. if (empty($entityListener)) { EntityListenerBuilder::bindEntityListener($metadata, $className); continue; } foreach ($entityListener as $eventName => $callbackElement){ foreach ($callbackElement as $methodName){ $metadata->addEntityListener($eventName, $className, $methodName); } } } } } /** * Constructs a joinColumn mapping array based on the information * found in the given join column element. * * @param array $joinColumnElement The array join column element. * * @return array The mapping array. */ private function joinColumnToArray($joinColumnElement) { $joinColumn = array(); if (isset($joinColumnElement['referencedColumnName'])) { $joinColumn['referencedColumnName'] = (string) $joinColumnElement['referencedColumnName']; } if (isset($joinColumnElement['name'])) { $joinColumn['name'] = (string) $joinColumnElement['name']; } if (isset($joinColumnElement['fieldName'])) { $joinColumn['fieldName'] = (string) $joinColumnElement['fieldName']; } if (isset($joinColumnElement['unique'])) { $joinColumn['unique'] = (bool) $joinColumnElement['unique']; } if (isset($joinColumnElement['nullable'])) { $joinColumn['nullable'] = (bool) $joinColumnElement['nullable']; } if (isset($joinColumnElement['onDelete'])) { $joinColumn['onDelete'] = $joinColumnElement['onDelete']; } if (isset($joinColumnElement['columnDefinition'])) { $joinColumn['columnDefinition'] = $joinColumnElement['columnDefinition']; } return $joinColumn; } /** * Parses the given column as array. * * @param string $fieldName * @param array $column * * @return array */ private function columnToArray($fieldName, $column) { $mapping = array( 'fieldName' => $fieldName ); if (isset($column['type'])) { $params = explode('(', $column['type']); $column['type'] = $params[0]; $mapping['type'] = $column['type']; if (isset($params[1])) { $column['length'] = (integer) substr($params[1], 0, strlen($params[1]) - 1); } } if (isset($column['column'])) { $mapping['columnName'] = $column['column']; } if (isset($column['length'])) { $mapping['length'] = $column['length']; } if (isset($column['precision'])) { $mapping['precision'] = $column['precision']; } if (isset($column['scale'])) { $mapping['scale'] = $column['scale']; } if (isset($column['unique'])) { $mapping['unique'] = (bool)$column['unique']; } if (isset($column['options'])) { $mapping['options'] = $column['options']; } if (isset($column['nullable'])) { $mapping['nullable'] = $column['nullable']; } if (isset($column['version']) && $column['version']) { $mapping['version'] = $column['version']; } if (isset($column['columnDefinition'])) { $mapping['columnDefinition'] = $column['columnDefinition']; } return $mapping; } /** * {@inheritDoc} */ protected function loadMappingFile($file) { return Yaml::parse($file); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/ElementCollection.php000066400000000000000000000022721257105210500241020ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("ALL") * @todo check available targets */ final class ElementCollection implements Annotation { /** * @var string */ public $tableName; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Entity.php000066400000000000000000000023311257105210500217450ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class Entity implements Annotation { /** * @var string */ public $repositoryClass; /** * @var boolean */ public $readOnly = false; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/EntityListenerResolver.php000066400000000000000000000034301257105210500251760ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * A resolver is used to instantiate an entity listener. * * @since 2.4 * @author Fabio B. Silva */ interface EntityListenerResolver { /** * Clear all instances from the set, or a specific class when given. * * @param string $className The fully-qualified class name * * @return void */ function clear($className = null); /** * Returns a entity listener instance for the given class name. * * @param string $className The fully-qualified class name * * @return object An entity listener */ function resolve($className); /** * Register a entity listener instance. * * @param object $object An entity listener */ function register($object); }doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/EntityListeners.php000066400000000000000000000030011257105210500236310ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * The EntityListeners annotation specifies the callback listener classes to be used for an entity or mapped superclass. * The EntityListeners annotation may be applied to an entity class or mapped superclass. * * @author Fabio B. Silva * @since 2.4 * * @Annotation * @Target("CLASS") */ final class EntityListeners implements Annotation { /** * Specifies the names of the entity listeners. * * @var array */ public $value = array(); }doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/EntityResult.php000066400000000000000000000037701257105210500231540ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * References an entity in the SELECT clause of a SQL query. * If this annotation is used, the SQL statement should select all of the columns that are mapped to the entity object. * This should include foreign key columns to related entities. * The results obtained when insufficient data is available are undefined. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class EntityResult implements Annotation { /** * The class of the result. * * @var string */ public $entityClass; /** * Maps the columns specified in the SELECT list of the query to the properties or fields of the entity class. * * @var array<\Doctrine\ORM\Mapping\FieldResult> */ public $fields = array(); /** * Specifies the column name of the column in the SELECT list that is used to determine the type of the entity instance. * * @var string */ public $discriminatorColumn; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/FieldResult.php000066400000000000000000000030171257105210500227150ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * Is used to map the columns specified in the SELECT list of the query to the properties or fields of the entity class. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class FieldResult implements Annotation { /** * Name of the column in the SELECT clause. * * @var string */ public $name; /** * Name of the persistent field or property of the class. * * @var string */ public $column; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/GeneratedValue.php000066400000000000000000000024441257105210500233710ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class GeneratedValue implements Annotation { /** * The type of Id generator. * * @var string * * @Enum({"AUTO", "SEQUENCE", "TABLE", "IDENTITY", "NONE", "UUID", "CUSTOM"}) */ public $strategy = 'AUTO'; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php000066400000000000000000000021451257105210500246270ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class HasLifecycleCallbacks implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Id.php000066400000000000000000000021251257105210500210260ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class Id implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Index.php000066400000000000000000000023171257105210500215440ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("ANNOTATION") */ final class Index implements Annotation { /** * @var string */ public $name; /** * @var array */ public $columns; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/InheritanceType.php000066400000000000000000000024511257105210500235670ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class InheritanceType implements Annotation { /** * The inheritance type used by the class and its subclasses. * * @var string * * @Enum({"NONE", "JOINED", "SINGLE_TABLE", "TABLE_PER_CLASS"}) */ public $value; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/JoinColumn.php000066400000000000000000000031551257105210500225530ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target({"PROPERTY","ANNOTATION"}) */ final class JoinColumn implements Annotation { /** * @var string */ public $name; /** * @var string */ public $referencedColumnName = 'id'; /** * @var boolean */ public $unique = false; /** * @var boolean */ public $nullable = true; /** * @var mixed */ public $onDelete; /** * @var string */ public $columnDefinition; /** * Field name used in non-object hydration (array/scalar). * * @var string */ public $fieldName; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/JoinColumns.php000066400000000000000000000022651257105210500227370ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class JoinColumns implements Annotation { /** * @var array<\Doctrine\ORM\Mapping\JoinColumn> */ public $value; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/JoinTable.php000066400000000000000000000026571257105210500223530ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target({"PROPERTY","ANNOTATION"}) */ 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(); } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/ManyToMany.php000066400000000000000000000032061257105210500225270ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class ManyToMany implements Annotation { /** * @var string */ public $targetEntity; /** * @var string */ public $mappedBy; /** * @var string */ public $inversedBy; /** * @var array */ public $cascade; /** * The fetching strategy to use for the association. * * @var string * * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"}) */ public $fetch = 'LAZY'; /** * @var boolean */ public $orphanRemoval = false; /** * @var string */ public $indexBy; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/ManyToOne.php000066400000000000000000000027121257105210500223450ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class ManyToOne implements Annotation { /** * @var string */ public $targetEntity; /** * @var array */ public $cascade; /** * The fetching strategy to use for the association. * * @var string * * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"}) */ public $fetch = 'LAZY'; /** * @var string */ public $inversedBy; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/MappedSuperclass.php000066400000000000000000000022401257105210500237430ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class MappedSuperclass implements Annotation { /** * @var string */ public $repositoryClass; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/MappingException.php000066400000000000000000000570701257105210500237550ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * A MappingException indicates that something is wrong with the mapping setup. * * @since 2.0 */ class MappingException extends \Doctrine\ORM\ORMException { /** * @return MappingException */ public static function pathRequired() { return new self("Specifying the paths to your entities is required ". "in the AnnotationDriver to retrieve all class names."); } /** * @param string $entityName * * @return MappingException */ public static function identifierRequired($entityName) { if (false !== ($parent = get_parent_class($entityName))) { return new self(sprintf( 'No identifier/primary key specified for Entity "%s" sub class of "%s". Every Entity must have an identifier/primary key.', $entityName, $parent )); } return new self(sprintf( 'No identifier/primary key specified for Entity "%s". Every Entity must have an identifier/primary key.', $entityName )); } /** * @param string $entityName * @param string $type * * @return MappingException */ public static function invalidInheritanceType($entityName, $type) { return new self("The inheritance type '$type' specified for '$entityName' does not exist."); } /** * @return MappingException */ public static function generatorNotAllowedWithCompositeId() { return new self("Id generators can't be used with a composite id."); } /** * @param string $entity * * @return MappingException */ public static function missingFieldName($entity) { return new self("The field or association mapping misses the 'fieldName' attribute in entity '$entity'."); } /** * @param string $fieldName * * @return MappingException */ public static function missingTargetEntity($fieldName) { return new self("The association mapping '$fieldName' misses the 'targetEntity' attribute."); } /** * @param string $fieldName * * @return MappingException */ public static function missingSourceEntity($fieldName) { return new self("The association mapping '$fieldName' misses the 'sourceEntity' attribute."); } /** * @param string $entityName * @param string $fileName * * @return MappingException */ public static function mappingFileNotFound($entityName, $fileName) { return new self("No mapping file found named '$fileName' for class '$entityName'."); } /** * Exception for invalid property name override. * * @param string $className The entity's name. * @param string $fieldName * * @return MappingException */ public static function invalidOverrideFieldName($className, $fieldName) { return new self("Invalid field override named '$fieldName' for class '$className'."); } /** * Exception for invalid property type override. * * @param string $className The entity's name. * @param string $fieldName * * @return MappingException */ public static function invalidOverrideFieldType($className, $fieldName) { return new self("The column type of attribute '$fieldName' on class '$className' could not be changed."); } /** * @param string $className * @param string $fieldName * * @return MappingException */ public static function mappingNotFound($className, $fieldName) { return new self("No mapping found for field '$fieldName' on class '$className'."); } /** * @param string $className * @param string $queryName * * @return MappingException */ public static function queryNotFound($className, $queryName) { return new self("No query found named '$queryName' on class '$className'."); } /** * @param string $className * @param string $resultName * * @return MappingException */ public static function resultMappingNotFound($className, $resultName) { return new self("No result set mapping found named '$resultName' on class '$className'."); } /** * @param string $entity * @param string $queryName * * @return MappingException */ public static function emptyQueryMapping($entity, $queryName) { return new self('Query named "'.$queryName.'" in "'.$entity.'" could not be empty.'); } /** * @param string $className * * @return MappingException */ public static function nameIsMandatoryForQueryMapping($className) { return new self("Query name on entity class '$className' is not defined."); } /** * @param string $entity * @param string $queryName * * @return MappingException */ public static function missingQueryMapping($entity, $queryName) { return new self('Query named "'.$queryName.'" in "'.$entity.' requires a result class or result set mapping.'); } /** * @param string $entity * @param string $resultName * * @return MappingException */ public static function missingResultSetMappingEntity($entity, $resultName) { return new self('Result set mapping named "'.$resultName.'" in "'.$entity.' requires a entity class name.'); } /** * @param string $entity * @param string $resultName * * @return MappingException */ public static function missingResultSetMappingFieldName($entity, $resultName) { return new self('Result set mapping named "'.$resultName.'" in "'.$entity.' requires a field name.'); } /** * @param string $className * * @return MappingException */ public static function nameIsMandatoryForSqlResultSetMapping($className) { return new self("Result set mapping name on entity class '$className' is not defined."); } /** * @param string $fieldName * * @return MappingException */ public static function oneToManyRequiresMappedBy($fieldName) { return new self("OneToMany mapping on field '$fieldName' requires the 'mappedBy' attribute."); } /** * @param string $fieldName * * @return MappingException */ public static function joinTableRequired($fieldName) { return new self("The mapping of field '$fieldName' requires an the 'joinTable' attribute."); } /** * Called if a required option was not found but is required * * @param string $field Which field cannot be processed? * @param string $expectedOption Which option is required * @param string $hint Can optionally be used to supply a tip for common mistakes, * e.g. "Did you think of the plural s?" * * @return MappingException */ static function missingRequiredOption($field, $expectedOption, $hint = '') { $message = "The mapping of field '{$field}' is invalid: The option '{$expectedOption}' is required."; if ( ! empty($hint)) { $message .= ' (Hint: ' . $hint . ')'; } return new self($message); } /** * Generic exception for invalid mappings. * * @param string $fieldName * * @return MappingException */ public static function invalidMapping($fieldName) { return new self("The mapping of field '$fieldName' is invalid."); } /** * Exception for reflection exceptions - adds the entity name, * because there might be long classnames that will be shortened * within the stacktrace * * @param string $entity The entity's name * @param \ReflectionException $previousException * * @return MappingException */ public static function reflectionFailure($entity, \ReflectionException $previousException) { return new self('An error occurred in ' . $entity, 0, $previousException); } /** * @param string $className * @param string $joinColumn * * @return MappingException */ public static function joinColumnMustPointToMappedField($className, $joinColumn) { return new self('The column ' . $joinColumn . ' must be mapped to a field in class ' . $className . ' since it is referenced by a join column of another class.'); } /** * @param string $className * * @return MappingException */ public static function classIsNotAValidEntityOrMappedSuperClass($className) { if (false !== ($parent = get_parent_class($className))) { return new self(sprintf( 'Class "%s" sub class of "%s" is not a valid entity or mapped super class.', $className, $parent )); } return new self(sprintf( 'Class "%s" is not a valid entity or mapped super class.', $className )); } /** * @param string $className * @param string $propertyName * * @return MappingException */ public static function propertyTypeIsRequired($className, $propertyName) { return new self("The attribute 'type' is required for the column description of property ".$className."::\$".$propertyName."."); } /** * @param string $className * * @return MappingException */ public static function tableIdGeneratorNotImplemented($className) { return new self("TableIdGenerator is not yet implemented for use with class ".$className); } /** * @param string $entity The entity's name. * @param string $fieldName The name of the field that was already declared. * * @return MappingException */ public static function duplicateFieldMapping($entity, $fieldName) { return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } /** * @param string $entity * @param string $fieldName * * @return MappingException */ public static function duplicateAssociationMapping($entity, $fieldName) { return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } /** * @param string $entity * @param string $queryName * * @return MappingException */ public static function duplicateQueryMapping($entity, $queryName) { return new self('Query named "'.$queryName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } /** * @param string $entity * @param string $resultName * * @return MappingException */ public static function duplicateResultSetMapping($entity, $resultName) { return new self('Result set mapping named "'.$resultName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } /** * @param string $entity * * @return MappingException */ public static function singleIdNotAllowedOnCompositePrimaryKey($entity) { return new self('Single id is not allowed on composite primary key in entity '.$entity); } /** * @param string $entity * @param string $fieldName * @param string $unsupportedType * * @return MappingException */ public static function unsupportedOptimisticLockingType($entity, $fieldName, $unsupportedType) { return new self('Locking type "'.$unsupportedType.'" (specified in "'.$entity.'", field "'.$fieldName.'") ' .'is not supported by Doctrine.' ); } /** * @param string|null $path * * @return MappingException */ public static function fileMappingDriversRequireConfiguredDirectoryPath($path = null) { if ( ! empty($path)) { $path = '[' . $path . ']'; } return new self( 'File mapping drivers must have a valid directory path, ' . 'however the given path ' . $path . ' seems to be incorrect!' ); } /** * Returns an exception that indicates that a class used in a discriminator map does not exist. * An example would be an outdated (maybe renamed) classname. * * @param string $className The class that could not be found * @param string $owningClass The class that declares the discriminator map. * * @return MappingException */ public static function invalidClassInDiscriminatorMap($className, $owningClass) { return new self( "Entity class '$className' used in the discriminator map of class '$owningClass' ". "does not exist." ); } /** * @param string $className * @param array $entries * @param array $map * * @return MappingException */ public static function duplicateDiscriminatorEntry($className, array $entries, array $map) { return new self( "The entries " . implode(', ', $entries) . " in discriminator map of class '" . $className . "' is duplicated. " . "If the discriminator map is automatically generated you have to convert it to an explicit discriminator map now. " . "The entries of the current map are: @DiscriminatorMap({" . implode(', ', array_map( function($a, $b) { return "'$a': '$b'"; }, array_keys($map), array_values($map) )) . "})" ); } /** * @param string $className * * @return MappingException */ public static function missingDiscriminatorMap($className) { return new self("Entity class '$className' is using inheritance but no discriminator map was defined."); } /** * @param string $className * * @return MappingException */ public static function missingDiscriminatorColumn($className) { return new self("Entity class '$className' is using inheritance but no discriminator column was defined."); } /** * @param string $className * @param string $type * * @return MappingException */ public static function invalidDiscriminatorColumnType($className, $type) { return new self("Discriminator column type on entity class '$className' is not allowed to be '$type'. 'string' or 'integer' type variables are suggested!"); } /** * @param string $className * * @return MappingException */ public static function nameIsMandatoryForDiscriminatorColumns($className) { return new self("Discriminator column name on entity class '$className' is not defined."); } /** * @param string $className * @param string $fieldName * * @return MappingException */ public static function cannotVersionIdField($className, $fieldName) { return new self("Setting Id field '$fieldName' as versionable in entity class '$className' is not supported."); } /** * @param string $className * @param string $fieldName * @param string $type * * @return MappingException */ public static function sqlConversionNotAllowedForIdentifiers($className, $fieldName, $type) { return new self("It is not possible to set id field '$fieldName' to type '$type' in entity class '$className'. The type '$type' requires conversion SQL which is not allowed for identifiers."); } /** * @param string $className * @param string $columnName * * @return MappingException */ public static function duplicateColumnName($className, $columnName) { return new self("Duplicate definition of column '".$columnName."' on entity '".$className."' in a field or discriminator column mapping."); } /** * @param string $className * @param string $field * * @return MappingException */ public static function illegalToManyAssociationOnMappedSuperclass($className, $field) { return new self("It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass '".$className."#".$field."'."); } /** * @param string $className * @param string $targetEntity * @param string $targetField * * @return MappingException */ public static function cannotMapCompositePrimaryKeyEntitiesAsForeignId($className, $targetEntity, $targetField) { return new self("It is not possible to map entity '".$className."' with a composite primary key ". "as part of the primary key of another entity '".$targetEntity."#".$targetField."'."); } /** * @param string $className * @param string $field * * @return MappingException */ public static function noSingleAssociationJoinColumnFound($className, $field) { return new self("'$className#$field' is not an association with a single join column."); } /** * @param string $className * @param string $column * * @return MappingException */ public static function noFieldNameFoundForColumn($className, $column) { return new self("Cannot find a field on '$className' that is mapped to column '$column'. Either the ". "field does not exist or an association exists but it has multiple join columns."); } /** * @param string $className * @param string $field * * @return MappingException */ public static function illegalOrphanRemovalOnIdentifierAssociation($className, $field) { return new self("The orphan removal option is not allowed on an association that is ". "part of the identifier in '$className#$field'."); } /** * @param string $className * @param string $field * * @return MappingException */ public static function illegalOrphanRemoval($className, $field) { return new self("Orphan removal is only allowed on one-to-one and one-to-many ". "associations, but " . $className."#" .$field . " is not."); } /** * @param string $className * @param string $field * * @return MappingException */ public static function illegalInverseIdentifierAssociation($className, $field) { return new self("An inverse association is not allowed to be identifier in '$className#$field'."); } /** * @param string $className * @param string $field * * @return MappingException */ public static function illegalToManyIdentifierAssociation($className, $field) { return new self("Many-to-many or one-to-many associations are not allowed to be identifier in '$className#$field'."); } /** * @param string $className * * @return MappingException */ public static function noInheritanceOnMappedSuperClass($className) { return new self("Its not supported to define inheritance information on a mapped superclass '" . $className . "'."); } /** * @param string $className * @param string $rootClassName * * @return MappingException */ public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName) { return new self( "Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " . "to be properly mapped in the inheritance hierarchy. Alternatively you can make '".$className."' an abstract class " . "to avoid this exception from occurring." ); } /** * @param string $className * @param string $methodName * * @return MappingException */ public static function lifecycleCallbackMethodNotFound($className, $methodName) { return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback."); } /** * @param string $listenerName * @param string $className * * @return \Doctrine\ORM\Mapping\MappingException */ public static function entityListenerClassNotFound($listenerName, $className) { return new self(sprintf('Entity Listener "%s" declared on "%s" not found.', $listenerName, $className)); } /** * @param string $listenerName * @param string $methodName * @param string $className * * @return \Doctrine\ORM\Mapping\MappingException */ public static function entityListenerMethodNotFound($listenerName, $methodName, $className) { return new self(sprintf('Entity Listener "%s" declared on "%s" has no method "%s".', $listenerName, $className, $methodName)); } /** * @param string $className * @param string $annotation * * @return MappingException */ public static function invalidFetchMode($className, $annotation) { return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $annotation . "'"); } /** * @param string $className * * @return MappingException */ public static function compositeKeyAssignedIdGeneratorRequired($className) { return new self("Entity '". $className . "' has a composite identifier but uses an ID generator other than manually assigning (Identity, Sequence). This is not supported."); } /** * @param string $targetEntity * @param string $sourceEntity * @param string $associationName * * @return MappingException */ public static function invalidTargetEntityClass($targetEntity, $sourceEntity, $associationName) { return new self("The target-entity " . $targetEntity . " cannot be found in '" . $sourceEntity."#".$associationName."'."); } /** * @param array $cascades * @param string $className * @param string $propertyName * * @return MappingException */ public static function invalidCascadeOption(array $cascades, $className, $propertyName) { $cascades = implode(", ", array_map(function ($e) { return "'" . $e . "'"; }, $cascades)); return new self(sprintf( "You have specified invalid cascade options for %s::$%s: %s; available options: 'remove', 'persist', 'refresh', 'merge', and 'detach'", $className, $propertyName, $cascades )); } /** * @param string $className * * @return MappingException */ public static function missingSequenceName($className) { return new self( sprintf('Missing "sequenceName" attribute for sequence id generator definition on class "%s".', $className) ); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php000066400000000000000000000027421257105210500242300ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * Is used to specify an array of native SQL named queries. * The NamedNativeQueries annotation can be applied to an entity or mapped superclass. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("CLASS") */ final class NamedNativeQueries implements Annotation { /** * One or more NamedNativeQuery annotations. * * @var array<\Doctrine\ORM\Mapping\NamedNativeQuery> */ public $value = array(); } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php000066400000000000000000000034441257105210500237200ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * Is used to specify a native SQL named query. * The NamedNativeQuery annotation can be applied to an entity or mapped superclass. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class NamedNativeQuery implements Annotation { /** * The name used to refer to the query with the EntityManager methods that create query objects. * * @var string */ public $name; /** * The SQL query string. * * @var string */ public $query; /** * The class of the result. * * @var string */ public $resultClass; /** * The name of a SqlResultSetMapping, as defined in metadata. * * @var string */ public $resultSetMapping; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/NamedQueries.php000066400000000000000000000022631257105210500230570ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class NamedQueries implements Annotation { /** * @var array<\Doctrine\ORM\Mapping\NamedQuery> */ public $value; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/NamedQuery.php000066400000000000000000000023131257105210500225430ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("ANNOTATION") */ final class NamedQuery implements Annotation { /** * @var string */ public $name; /** * @var string */ public $query; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/NamingStrategy.php000066400000000000000000000054311257105210500234310ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * A set of rules for determining the physical column and table names * * * @link www.doctrine-project.org * @since 2.3 * @author Fabio B. Silva */ interface NamingStrategy { /** * Returns a table name for an entity class. * * @param string $className The fully-qualified class name. * * @return string A table name. */ function classToTableName($className); /** * Returns a column name for a property. * * @param string $propertyName A property name. * @param string|null $className The fully-qualified class name. * * @return string A column name. */ function propertyToColumnName($propertyName, $className = null); /** * Returns the default reference column name. * * @return string A column name. */ function referenceColumnName(); /** * Returns a join column name for a property. * * @param string $propertyName A property name. * * @return string A join column name. */ function joinColumnName($propertyName); /** * Returns a join table name. * * @param string $sourceEntity The source entity. * @param string $targetEntity The target entity. * @param string|null $propertyName A property name. * * @return string A join table name. */ function joinTableName($sourceEntity, $targetEntity, $propertyName = null); /** * Returns the foreign key column name for the given parameters. * * @param string $entityName An entity. * @param string|null $referencedColumnName A property. * * @return string A join column name. */ function joinKeyColumnName($entityName, $referencedColumnName = null); } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/OneToMany.php000066400000000000000000000031111257105210500223370ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class OneToMany implements Annotation { /** * @var string */ public $mappedBy; /** * @var string */ public $targetEntity; /** * @var array */ public $cascade; /** * The fetching strategy to use for the association. * * @var string * * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"}) */ public $fetch = 'LAZY'; /** * @var boolean */ public $orphanRemoval = false; /** * @var string */ public $indexBy; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/OneToOne.php000066400000000000000000000031131257105210500221560ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class OneToOne implements Annotation { /** * @var string */ public $targetEntity; /** * @var string */ public $mappedBy; /** * @var string */ public $inversedBy; /** * @var array */ public $cascade; /** * The fetching strategy to use for the association. * * @var string * * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"}) */ public $fetch = 'LAZY'; /** * @var boolean */ public $orphanRemoval = false; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/OrderBy.php000066400000000000000000000022271257105210500220430ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class OrderBy implements Annotation { /** * @var array */ public $value; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/PostLoad.php000066400000000000000000000021311257105210500222140ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PostLoad implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/PostPersist.php000066400000000000000000000021341257105210500227710ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PostPersist implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/PostRemove.php000066400000000000000000000021331257105210500225740ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PostRemove implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/PostUpdate.php000066400000000000000000000021331257105210500225610ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PostUpdate implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/PreFlush.php000066400000000000000000000021311257105210500222170ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PreFlush implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/PrePersist.php000066400000000000000000000021331257105210500225710ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PrePersist implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/PreRemove.php000066400000000000000000000021321257105210500223740ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PreRemove implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/PreUpdate.php000066400000000000000000000021321257105210500223610ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PreUpdate implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/QuoteStrategy.php000066400000000000000000000075701257105210500233230ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * A set of rules for determining the column, alias and table quotes. * * @since 2.3 * @author Fabio B. Silva */ interface QuoteStrategy { /** * Gets the (possibly quoted) column name for safe use in an SQL statement. * * @param string $fieldName * @param ClassMetadata $class * @param AbstractPlatform $platform * * @return string */ function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) primary table name for safe use in an SQL statement. * * @param ClassMetadata $class * @param AbstractPlatform $platform * * @return string */ function getTableName(ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) sequence name for safe use in an SQL statement. * * @param array $definition * @param ClassMetadata $class * @param AbstractPlatform $platform * * @return string */ function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) name of the join table. * * @param array $association * @param ClassMetadata $class * @param AbstractPlatform $platform * * @return string */ function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) join column name. * * @param array $joinColumn * @param ClassMetadata $class * @param AbstractPlatform $platform * * @return string */ function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) join column name. * * @param array $joinColumn * @param ClassMetadata $class * @param AbstractPlatform $platform * * @return string */ function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) identifier column names for safe use in an SQL statement. * * @param ClassMetadata $class * @param AbstractPlatform $platform * * @return array */ function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform); /** * Gets the column alias. * * @param string $columnName * @param integer $counter * @param AbstractPlatform $platform * @param ClassMetadata|null $class * * @return string */ function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ClassMetadata $class = null); } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/SequenceGenerator.php000066400000000000000000000024511257105210500241130ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class SequenceGenerator implements Annotation { /** * @var string */ public $sequenceName; /** * @var integer */ public $allocationSize = 1; /** * @var integer */ public $initialValue = 1; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php000066400000000000000000000035511257105210500244240ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * The SqlResultSetMapping annotation is used to specify the mapping of the result of a native SQL query. * The SqlResultSetMapping annotation can be applied to an entity or mapped superclass. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class SqlResultSetMapping implements Annotation { /** * The name given to the result set mapping, and used to refer to it in the methods of the Query API. * * @var string */ public $name; /** * Specifies the result set mapping to entities. * * @var array<\Doctrine\ORM\Mapping\EntityResult> */ public $entities = array(); /** * Specifies the result set mapping to scalar values. * * @var array<\Doctrine\ORM\Mapping\ColumnResult> */ public $columns = array(); } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php000066400000000000000000000027341257105210500246110ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * Is used to specify an array of mappings. * The SqlResultSetMappings annotation can be applied to an entity or mapped superclass. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("CLASS") */ final class SqlResultSetMappings implements Annotation { /** * One or more SqlResultSetMapping annotations. * * @var array<\Doctrine\ORM\Mapping\SqlResultSetMapping> */ public $value = array(); } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Table.php000066400000000000000000000027031257105210500215230ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @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; /** * @var array */ public $options = array(); } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php000066400000000000000000000070601257105210500254630ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * Naming strategy implementing the underscore naming convention. * Converts 'MyEntity' to 'my_entity' or 'MY_ENTITY'. * * * @link www.doctrine-project.org * @since 2.3 * @author Fabio B. Silva */ class UnderscoreNamingStrategy implements NamingStrategy { /** * @var integer */ private $case; /** * Underscore naming strategy construct. * * @param integer $case CASE_LOWER | CASE_UPPER */ public function __construct($case = CASE_LOWER) { $this->case = $case; } /** * @return integer CASE_LOWER | CASE_UPPER */ public function getCase() { return $this->case; } /** * Sets string case CASE_LOWER | CASE_UPPER. * Alphabetic characters converted to lowercase or uppercase. * * @param integer $case * * @return void */ public function setCase($case) { $this->case = $case; } /** * {@inheritdoc} */ public function classToTableName($className) { if (strpos($className, '\\') !== false) { $className = substr($className, strrpos($className, '\\') + 1); } return $this->underscore($className); } /** * {@inheritdoc} */ public function propertyToColumnName($propertyName, $className = null) { return $this->underscore($propertyName); } /** * {@inheritdoc} */ public function referenceColumnName() { return $this->case === CASE_UPPER ? 'ID' : 'id'; } /** * {@inheritdoc} */ public function joinColumnName($propertyName) { return $this->underscore($propertyName) . '_' . $this->referenceColumnName(); } /** * {@inheritdoc} */ public function joinTableName($sourceEntity, $targetEntity, $propertyName = null) { return $this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity); } /** * {@inheritdoc} */ public function joinKeyColumnName($entityName, $referencedColumnName = null) { return $this->classToTableName($entityName) . '_' . ($referencedColumnName ?: $this->referenceColumnName()); } /** * @param string $string * * @return string */ private function underscore($string) { $string = preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $string); if ($this->case === CASE_UPPER) { return strtoupper($string); } return strtolower($string); } } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/UniqueConstraint.php000066400000000000000000000023321257105210500240050ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("ANNOTATION") */ final class UniqueConstraint implements Annotation { /** * @var string */ public $name; /** * @var array */ public $columns; } doctrine2-2.4.8/lib/Doctrine/ORM/Mapping/Version.php000066400000000000000000000021321257105210500221150ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class Version implements Annotation { } doctrine2-2.4.8/lib/Doctrine/ORM/NativeQuery.php000066400000000000000000000051041257105210500213530ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * Represents a native SQL query. * * @author Roman Borschel * @since 2.0 */ final class NativeQuery extends AbstractQuery { /** * @var string */ private $_sql; /** * Sets the SQL of the query. * * @param string $sql * * @return NativeQuery This query instance. */ public function setSQL($sql) { $this->_sql = $sql; return $this; } /** * Gets the SQL query. * * @return mixed The built SQL query or an array of all SQL queries. * * @override */ public function getSQL() { return $this->_sql; } /** * {@inheritdoc} */ protected function _doExecute() { $parameters = array(); $types = array(); foreach ($this->getParameters() as $parameter) { $name = $parameter->getName(); $value = $this->processParameterValue($parameter->getValue()); $type = ($parameter->getValue() === $value) ? $parameter->getType() : Query\ParameterTypeInferer::inferType($value); $parameters[$name] = $value; $types[$name] = $type; } if ($parameters && is_int(key($parameters))) { ksort($parameters); ksort($types); $parameters = array_values($parameters); $types = array_values($types); } return $this->_em->getConnection()->executeQuery( $this->_sql, $parameters, $types, $this->_queryCacheProfile ); } } doctrine2-2.4.8/lib/Doctrine/ORM/NoResultException.php000066400000000000000000000025451257105210500225370ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * Exception thrown when an ORM query unexpectedly does not return any results. * * @author robo * @since 2.0 */ class NoResultException extends UnexpectedResultException { /** * Constructor. */ public function __construct() { parent::__construct('No result was found for query although at least one row was expected.'); } } doctrine2-2.4.8/lib/Doctrine/ORM/NonUniqueResultException.php000066400000000000000000000022651257105210500241030ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * Exception thrown when an ORM query unexpectedly returns more than one result. * * @author robo * @since 2.0 */ class NonUniqueResultException extends UnexpectedResultException { } doctrine2-2.4.8/lib/Doctrine/ORM/ORMException.php000066400000000000000000000175071257105210500214250ustar00rootroot00000000000000. */ namespace Doctrine\ORM; use Exception; /** * Base exception class for all ORM exceptions. * * @author Roman Borschel * @since 2.0 */ class ORMException extends Exception { /** * @return ORMException */ public static function missingMappingDriverImpl() { return new self("It's a requirement to specify a Metadata Driver and pass it ". "to Doctrine\\ORM\\Configuration::setMetadataDriverImpl()."); } /** * @param string $queryName * * @return ORMException */ public static function namedQueryNotFound($queryName) { return new self('Could not find a named query by the name "' . $queryName . '"'); } /** * @param string $nativeQueryName * * @return ORMException */ public static function namedNativeQueryNotFound($nativeQueryName) { return new self('Could not find a named native query by the name "' . $nativeQueryName . '"'); } /** * @param object $entity * @param object $relatedEntity * * @return ORMException */ 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." ); } /** * @param object $entity * @param string $field * * @return ORMException */ public static function entityMissingAssignedIdForField($entity, $field) { return new self("Entity of type " . get_class($entity) . " is missing an assigned ID for field '" . $field . "'. " . "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." ); } /** * @param string $field * * @return ORMException */ public static function unrecognizedField($field) { return new self("Unrecognized field: $field"); } /** * @param string $className * @param string $field * * @return ORMException */ public static function invalidOrientation($className, $field) { return new self("Invalid order by orientation specified for " . $className . "#" . $field); } /** * @param string $mode * * @return ORMException */ public static function invalidFlushMode($mode) { return new self("'$mode' is an invalid flush mode."); } /** * @return ORMException */ public static function entityManagerClosed() { return new self("The EntityManager is closed."); } /** * @param string $mode * * @return ORMException */ public static function invalidHydrationMode($mode) { return new self("'$mode' is an invalid hydration mode."); } /** * @return ORMException */ public static function mismatchedEventManager() { return new self("Cannot use different EventManager instances for EntityManager and Connection."); } /** * @param string $methodName * * @return ORMException */ public static function findByRequiresParameter($methodName) { return new self("You need to pass a parameter to '".$methodName."'"); } /** * @param string $entityName * @param string $fieldName * @param string $method * * @return ORMException */ public static function invalidFindByCall($entityName, $fieldName, $method) { return new self( "Entity '".$entityName."' has no field '".$fieldName."'. ". "You can therefore not call '".$method."' on the entities' repository" ); } /** * @param string $entityName * @param string $associationFieldName * * @return ORMException */ public static function invalidFindByInverseAssociation($entityName, $associationFieldName) { return new self( "You cannot search for the association field '".$entityName."#".$associationFieldName."', ". "because it is the inverse side of an association. Find methods only work on owning side associations." ); } /** * @return ORMException */ public static function invalidResultCacheDriver() { return new self("Invalid result cache driver; it must implement Doctrine\\Common\\Cache\\Cache."); } /** * @return ORMException */ public static function notSupported() { return new self("This behaviour is (currently) not supported by Doctrine 2"); } /** * @return ORMException */ public static function queryCacheNotConfigured() { return new self('Query Cache is not configured.'); } /** * @return ORMException */ public static function metadataCacheNotConfigured() { return new self('Class Metadata Cache is not configured.'); } /** * @return ORMException */ public static function proxyClassesAlwaysRegenerating() { return new self('Proxy Classes are always regenerating.'); } /** * @param string $entityNamespaceAlias * * @return ORMException */ public static function unknownEntityNamespace($entityNamespaceAlias) { return new self( "Unknown Entity namespace alias '$entityNamespaceAlias'." ); } /** * @param string $className * * @return ORMException */ public static function invalidEntityRepository($className) { return new self("Invalid repository class '".$className."'. It must be a Doctrine\Common\Persistence\ObjectRepository."); } /** * @param string $className * @param string $fieldName * * @return ORMException */ public static function missingIdentifierField($className, $fieldName) { return new self("The identifier $fieldName is missing for a query of " . $className); } /** * @param string $functionName * * @return ORMException */ public static function overwriteInternalDQLFunctionNotAllowed($functionName) { return new self("It is not allowed to overwrite internal function '$functionName' in the DQL parser through user-defined functions."); } } doctrine2-2.4.8/lib/Doctrine/ORM/ORMInvalidArgumentException.php000066400000000000000000000157061257105210500244360ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * Contains exception messages for all invalid lifecycle state exceptions inside UnitOfWork * * @author Benjamin Eberlei */ class ORMInvalidArgumentException extends \InvalidArgumentException { /** * @param object $entity * * @return ORMInvalidArgumentException */ static public function scheduleInsertForManagedEntity($entity) { return new self("A managed+dirty entity " . self::objToStr($entity) . " can not be scheduled for insertion."); } /** * @param object $entity * * @return ORMInvalidArgumentException */ static public function scheduleInsertForRemovedEntity($entity) { return new self("Removed entity " . self::objToStr($entity) . " can not be scheduled for insertion."); } /** * @param object $entity * * @return ORMInvalidArgumentException */ static public function scheduleInsertTwice($entity) { return new self("Entity " . self::objToStr($entity) . " can not be scheduled for insertion twice."); } /** * @param string $className * @param object $entity * * @return ORMInvalidArgumentException */ static public function entityWithoutIdentity($className, $entity) { return new self( "The given entity of type '" . $className . "' (".self::objToStr($entity).") has no identity/no " . "id values set. It cannot be added to the identity map." ); } /** * @param object $entity * * @return ORMInvalidArgumentException */ static public function readOnlyRequiresManagedEntity($entity) { return new self("Only managed entities can be marked or checked as read only. But " . self::objToStr($entity) . " is not"); } /** * @param array $assoc * @param object $entry * * @return ORMInvalidArgumentException */ static public function newEntityFoundThroughRelationship(array $assoc, $entry) { return new self("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) . "." . " To solve this issue: Either explicitly call EntityManager#persist()" . " on this unknown entity or configure cascade persist " . " this association in the mapping for example @ManyToOne(..,cascade={\"persist\"})." . (method_exists($entry, '__toString') ? "": " If you cannot find out which entity causes the problem" ." implement '" . $assoc['targetEntity'] . "#__toString()' to get a clue.")); } /** * @param array $assoc * @param object $entry * * @return ORMInvalidArgumentException */ static public function detachedEntityFoundThroughRelationship(array $assoc, $entry) { return new self("A detached entity of type " . $assoc['targetEntity'] . " (" . self::objToStr($entry) . ") " . " was found through the relationship '" . $assoc['sourceEntity'] . "#" . $assoc['fieldName'] . "' " . "during cascading a persist operation."); } /** * @param object $entity * * @return ORMInvalidArgumentException */ static public function entityNotManaged($entity) { return new self("Entity " . self::objToStr($entity) . " is not managed. An entity is managed if its fetched " . "from the database or registered as new through EntityManager#persist"); } /** * @param object $entity * @param string $operation * * @return ORMInvalidArgumentException */ static public function entityHasNoIdentity($entity, $operation) { return new self("Entity has no identity, therefore " . $operation ." cannot be performed. " . self::objToStr($entity)); } /** * @param object $entity * @param string $operation * * @return ORMInvalidArgumentException */ static public function entityIsRemoved($entity, $operation) { return new self("Entity is removed, therefore " . $operation ." cannot be performed. " . self::objToStr($entity)); } /** * @param object $entity * @param string $operation * * @return ORMInvalidArgumentException */ static public function detachedEntityCannot($entity, $operation) { return new self("A detached entity was found during " . $operation . " " . self::objToStr($entity)); } /** * @param string $context * @param mixed $given * @param int $parameterIndex * * @return ORMInvalidArgumentException */ public static function invalidObject($context, $given, $parameterIndex = 1) { return new self($context . ' expects parameter ' . $parameterIndex . ' to be an entity object, '. gettype($given) . ' given.'); } /** * @return ORMInvalidArgumentException */ public static function invalidCompositeIdentifier() { return new self("Binding an entity with a composite primary key to a query is not supported. " . "You should split the parameter into the explicit fields and bind them separately."); } /** * @return ORMInvalidArgumentException */ public static function invalidIdentifierBindingEntity() { return new self("Binding entities to query parameters only allowed for entities that have an identifier."); } /** * 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); } } doctrine2-2.4.8/lib/Doctrine/ORM/OptimisticLockException.php000066400000000000000000000056311257105210500237200ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * An OptimisticLockException is thrown when a version check on an object * that uses optimistic locking through a version field fails. * * @author Roman Borschel * @author Benjamin Eberlei * @since 2.0 */ class OptimisticLockException extends ORMException { /** * @var object|null */ private $entity; /** * @param string $msg * @param object $entity */ public function __construct($msg, $entity) { parent::__construct($msg); $this->entity = $entity; } /** * Gets the entity that caused the exception. * * @return object|null */ public function getEntity() { return $this->entity; } /** * @param object $entity * * @return OptimisticLockException */ public static function lockFailed($entity) { return new self("The optimistic lock on an entity failed.", $entity); } /** * @param object $entity * @param int $expectedLockVersion * @param int $actualLockVersion * * @return OptimisticLockException */ public static function lockFailedVersionMismatch($entity, $expectedLockVersion, $actualLockVersion) { $expectedLockVersion = ($expectedLockVersion instanceof \DateTime) ? $expectedLockVersion->getTimestamp() : $expectedLockVersion; $actualLockVersion = ($actualLockVersion instanceof \DateTime) ? $actualLockVersion->getTimestamp() : $actualLockVersion; return new self("The optimistic lock failed, version " . $expectedLockVersion . " was expected, but is actually ".$actualLockVersion, $entity); } /** * @param string $entityName * * @return OptimisticLockException */ public static function notVersioned($entityName) { return new self("Cannot obtain optimistic lock on unversioned entity " . $entityName, null); } } doctrine2-2.4.8/lib/Doctrine/ORM/PersistentCollection.php000066400000000000000000000535161257105210500232650ustar00rootroot00000000000000. */ namespace Doctrine\ORM; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Selectable; use Doctrine\Common\Collections\Criteria; use Closure; /** * A PersistentCollection represents a collection of elements that have persistent state. * * Collections of entities represent only the associations (links) to those entities. * That means, if the collection is part of a many-many mapping and you remove * entities from the collection, only the links in the relation table are removed (on flush). * Similarly, if you remove entities from a collection that is part of a one-many * mapping this will only result in the nulling out of the foreign keys on flush. * * @since 2.0 * @author Konsta Vesterinen * @author Roman Borschel * @author Giorgio Sironi * @author Stefano Rodriguez * @todo Design for inheritance to allow custom implementations? */ final class PersistentCollection implements Collection, Selectable { /** * A snapshot of the collection at the moment it was fetched from the database. * This is used to create a diff of the collection at commit time. * * @var array */ private $snapshot = array(); /** * The entity that owns this collection. * * @var object */ private $owner; /** * The association mapping the collection belongs to. * This is currently either a OneToManyMapping or a ManyToManyMapping. * * @var array */ private $association; /** * The EntityManager that manages the persistence of the collection. * * @var \Doctrine\ORM\EntityManager */ private $em; /** * The name of the field on the target entities that points to the owner * of the collection. This is only set if the association is bi-directional. * * @var string */ private $backRefFieldName; /** * The class descriptor of the collection's entity type. * * @var ClassMetadata */ private $typeClass; /** * Whether the collection is dirty and needs to be synchronized with the database * when the UnitOfWork that manages its persistent state commits. * * @var boolean */ private $isDirty = false; /** * Whether the collection has already been initialized. * * @var boolean */ private $initialized = true; /** * The wrapped Collection instance. * * @var Collection */ private $coll; /** * Creates a new persistent collection. * * @param EntityManager $em The EntityManager the collection will be associated with. * @param ClassMetadata $class The class descriptor of the entity type of this collection. * @param array $coll The collection elements. */ public function __construct(EntityManager $em, $class, $coll) { $this->coll = $coll; $this->em = $em; $this->typeClass = $class; } /** * INTERNAL: * Sets the collection's owning entity together with the AssociationMapping that * describes the association between the owner and the elements of the collection. * * @param object $entity * @param array $assoc * * @return void */ public function setOwner($entity, array $assoc) { $this->owner = $entity; $this->association = $assoc; $this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy']; } /** * INTERNAL: * Gets the collection owner. * * @return object */ public function getOwner() { return $this->owner; } /** * @return Mapping\ClassMetadata */ public function getTypeClass() { return $this->typeClass; } /** * INTERNAL: * Adds an element to a collection during hydration. This will automatically * complete bidirectional associations in the case of a one-to-many association. * * @param mixed $element The element to add. * * @return void */ public function hydrateAdd($element) { $this->coll->add($element); // If _backRefFieldName is set and its a one-to-many association, // we need to set the back reference. if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) { // Set back reference to owner $this->typeClass->reflFields[$this->backRefFieldName]->setValue( $element, $this->owner ); $this->em->getUnitOfWork()->setOriginalEntityProperty( spl_object_hash($element), $this->backRefFieldName, $this->owner ); } } /** * INTERNAL: * Sets a keyed element in the collection during hydration. * * @param mixed $key The key to set. * @param mixed $element The element to set. * * @return void */ public function hydrateSet($key, $element) { $this->coll->set($key, $element); // If _backRefFieldName is set, then the association is bidirectional // and we need to set the back reference. if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) { // Set back reference to owner $this->typeClass->reflFields[$this->backRefFieldName]->setValue( $element, $this->owner ); } } /** * Initializes the collection by loading its contents from the database * if the collection is not yet initialized. * * @return void */ public function initialize() { if ($this->initialized || ! $this->association) { return; } // Has NEW objects added through add(). Remember them. $newObjects = array(); if ($this->isDirty) { $newObjects = $this->coll->toArray(); } $this->coll->clear(); $this->em->getUnitOfWork()->loadCollection($this); $this->takeSnapshot(); // Reattach NEW objects added through add(), if any. if ($newObjects) { foreach ($newObjects as $obj) { $this->coll->add($obj); } $this->isDirty = true; } $this->initialized = true; } /** * INTERNAL: * Tells this collection to take a snapshot of its current state. * * @return void */ public function takeSnapshot() { $this->snapshot = $this->coll->toArray(); $this->isDirty = false; } /** * INTERNAL: * Returns the last snapshot of the elements in the collection. * * @return array The last snapshot of the elements. */ public function getSnapshot() { return $this->snapshot; } /** * INTERNAL: * getDeleteDiff * * @return array */ public function getDeleteDiff() { return array_udiff_assoc( $this->snapshot, $this->coll->toArray(), function($a, $b) { return $a === $b ? 0 : 1; } ); } /** * INTERNAL: * getInsertDiff * * @return array */ public function getInsertDiff() { return array_udiff_assoc( $this->coll->toArray(), $this->snapshot, function($a, $b) { return $a === $b ? 0 : 1; } ); } /** * INTERNAL: Gets the association mapping of the collection. * * @return array */ public function getMapping() { return $this->association; } /** * Marks this collection as changed/dirty. * * @return void */ private function changed() { if ($this->isDirty) { return; } $this->isDirty = true; if ($this->association !== null && $this->association['isOwningSide'] && $this->association['type'] === ClassMetadata::MANY_TO_MANY && $this->owner && $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) { $this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner); } } /** * Gets a boolean flag indicating whether this collection is dirty which means * its state needs to be synchronized with the database. * * @return boolean TRUE if the collection is dirty, FALSE otherwise. */ public function isDirty() { return $this->isDirty; } /** * Sets a boolean flag, indicating whether this collection is dirty. * * @param boolean $dirty Whether the collection should be marked dirty or not. * * @return void */ public function setDirty($dirty) { $this->isDirty = $dirty; } /** * Sets the initialized flag of the collection, forcing it into that state. * * @param boolean $bool * * @return void */ public function setInitialized($bool) { $this->initialized = $bool; } /** * Checks whether this collection has been initialized. * * @return boolean */ public function isInitialized() { return $this->initialized; } /** * {@inheritdoc} */ public function first() { $this->initialize(); return $this->coll->first(); } /** * {@inheritdoc} */ public function last() { $this->initialize(); return $this->coll->last(); } /** * {@inheritdoc} */ public function remove($key) { // TODO: If the keys are persistent as well (not yet implemented) // and the collection is not initialized and orphanRemoval is // not used we can issue a straight SQL delete/update on the // association (table). Without initializing the collection. $this->initialize(); $removed = $this->coll->remove($key); if ( ! $removed) { return $removed; } $this->changed(); if ($this->association !== null && $this->association['type'] & ClassMetadata::TO_MANY && $this->owner && $this->association['orphanRemoval']) { $this->em->getUnitOfWork()->scheduleOrphanRemoval($removed); } return $removed; } /** * {@inheritdoc} */ public function removeElement($element) { if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { if ($this->coll->contains($element)) { return $this->coll->removeElement($element); } $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); if ($persister->removeElement($this, $element)) { return $element; } return null; } $this->initialize(); $removed = $this->coll->removeElement($element); if ( ! $removed) { return $removed; } $this->changed(); if ($this->association !== null && $this->association['type'] & ClassMetadata::TO_MANY && $this->owner && $this->association['orphanRemoval']) { $this->em->getUnitOfWork()->scheduleOrphanRemoval($element); } return $removed; } /** * {@inheritdoc} */ public function containsKey($key) { $this->initialize(); return $this->coll->containsKey($key); } /** * {@inheritdoc} */ public function contains($element) { if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); return $this->coll->contains($element) || $persister->contains($this, $element); } $this->initialize(); return $this->coll->contains($element); } /** * {@inheritdoc} */ public function exists(Closure $p) { $this->initialize(); return $this->coll->exists($p); } /** * {@inheritdoc} */ public function indexOf($element) { $this->initialize(); return $this->coll->indexOf($element); } /** * {@inheritdoc} */ public function get($key) { if ( ! $this->initialized && $this->association['type'] === Mapping\ClassMetadataInfo::ONE_TO_MANY && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY && isset($this->association['indexBy']) ) { if (!$this->typeClass->isIdentifierComposite && $this->typeClass->isIdentifier($this->association['indexBy'])) { return $this->em->find($this->typeClass->name, $key); } return $this->em->getUnitOfWork()->getCollectionPersister($this->association)->get($this, $key); } $this->initialize(); return $this->coll->get($key); } /** * {@inheritdoc} */ public function getKeys() { $this->initialize(); return $this->coll->getKeys(); } /** * {@inheritdoc} */ public function getValues() { $this->initialize(); return $this->coll->getValues(); } /** * {@inheritdoc} */ public function count() { if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); return $persister->count($this) + ($this->isDirty ? $this->coll->count() : 0); } $this->initialize(); return $this->coll->count(); } /** * {@inheritdoc} */ public function set($key, $value) { $this->initialize(); $this->coll->set($key, $value); $this->changed(); } /** * {@inheritdoc} */ public function add($value) { $this->coll->add($value); $this->changed(); return true; } /** * {@inheritdoc} */ public function isEmpty() { $this->initialize(); return $this->coll->isEmpty(); } /** * {@inheritdoc} */ public function getIterator() { $this->initialize(); return $this->coll->getIterator(); } /** * {@inheritdoc} */ public function map(Closure $func) { $this->initialize(); return $this->coll->map($func); } /** * {@inheritdoc} */ public function filter(Closure $p) { $this->initialize(); return $this->coll->filter($p); } /** * {@inheritdoc} */ public function forAll(Closure $p) { $this->initialize(); return $this->coll->forAll($p); } /** * {@inheritdoc} */ public function partition(Closure $p) { $this->initialize(); return $this->coll->partition($p); } /** * {@inheritdoc} */ public function toArray() { $this->initialize(); return $this->coll->toArray(); } /** * {@inheritdoc} */ public function clear() { if ($this->initialized && $this->isEmpty()) { return; } $uow = $this->em->getUnitOfWork(); if ($this->association['type'] & ClassMetadata::TO_MANY && $this->association['orphanRemoval'] && $this->owner) { // we need to initialize here, as orphan removal acts like implicit cascadeRemove, // hence for event listeners we need the objects in memory. $this->initialize(); foreach ($this->coll as $element) { $uow->scheduleOrphanRemoval($element); } } $this->coll->clear(); $this->initialized = true; // direct call, {@link initialize()} is too expensive if ($this->association['isOwningSide'] && $this->owner) { $this->changed(); $uow->scheduleCollectionDeletion($this); $this->takeSnapshot(); } } /** * Called by PHP when this collection is serialized. Ensures that only the * elements are properly serialized. * * @return array * * @internal Tried to implement Serializable first but that did not work well * with circular references. This solution seems simpler and works well. */ public function __sleep() { return array('coll', 'initialized'); } /* ArrayAccess implementation */ /** * {@inheritdoc} */ public function offsetExists($offset) { return $this->containsKey($offset); } /** * {@inheritdoc} */ public function offsetGet($offset) { return $this->get($offset); } /** * {@inheritdoc} */ public function offsetSet($offset, $value) { if ( ! isset($offset)) { return $this->add($value); } return $this->set($offset, $value); } /** * {@inheritdoc} */ public function offsetUnset($offset) { return $this->remove($offset); } /** * {@inheritdoc} */ public function key() { $this->initialize(); return $this->coll->key(); } /** * {@inheritdoc} */ public function current() { $this->initialize(); return $this->coll->current(); } /** * {@inheritdoc} */ public function next() { $this->initialize(); return $this->coll->next(); } /** * Retrieves the wrapped Collection instance. * * @return \Doctrine\Common\Collections\Collection */ public function unwrap() { return $this->coll; } /** * Extracts a slice of $length elements starting at position $offset from the Collection. * * If $length is null it returns all elements from $offset to the end of the Collection. * Keys have to be preserved by this method. Calling this method will only return the * selected slice and NOT change the elements contained in the collection slice is called on. * * @param int $offset * @param int|null $length * * @return array */ public function slice($offset, $length = null) { if ( ! $this->initialized && ! $this->isDirty && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); return $persister->slice($this, $offset, $length); } $this->initialize(); return $this->coll->slice($offset, $length); } /** * Cleans up internal state of cloned persistent collection. * * The following problems have to be prevented: * 1. Added entities are added to old PC * 2. New collection is not dirty, if reused on other entity nothing * changes. * 3. Snapshot leads to invalid diffs being generated. * 4. Lazy loading grabs entities from old owner object. * 5. New collection is connected to old owner and leads to duplicate keys. * * @return void */ public function __clone() { if (is_object($this->coll)) { $this->coll = clone $this->coll; } $this->initialize(); $this->owner = null; $this->snapshot = array(); $this->changed(); } /** * Selects all elements from a selectable that match the expression and * return a new collection containing these elements. * * @param \Doctrine\Common\Collections\Criteria $criteria * * @return Collection * * @throws \RuntimeException */ public function matching(Criteria $criteria) { if ($this->isDirty) { $this->initialize(); } if ($this->initialized) { return $this->coll->matching($criteria); } if ($this->association['type'] !== ClassMetadata::ONE_TO_MANY) { throw new \RuntimeException("Matching Criteria on PersistentCollection only works on OneToMany associations at the moment."); } $builder = Criteria::expr(); $ownerExpression = $builder->eq($this->backRefFieldName, $this->owner); $expression = $criteria->getWhereExpression(); $expression = $expression ? $builder->andX($expression, $ownerExpression) : $ownerExpression; $criteria->where($expression); $persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']); return new ArrayCollection($persister->loadCriteria($criteria)); } } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/000077500000000000000000000000001257105210500205315ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php000066400000000000000000000222651257105210500267310ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\EntityManager; use Doctrine\ORM\PersistentCollection; /** * Base class for all collection persisters. * * @since 2.0 * @author Roman Borschel */ abstract class AbstractCollectionPersister { /** * @var EntityManager */ protected $em; /** * @var \Doctrine\DBAL\Connection */ protected $conn; /** * @var \Doctrine\ORM\UnitOfWork */ protected $uow; /** * The database platform. * * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ protected $platform; /** * The quote strategy. * * @var \Doctrine\ORM\Mapping\QuoteStrategy */ protected $quoteStrategy; /** * Initializes a new instance of a class derived from AbstractCollectionPersister. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; $this->uow = $em->getUnitOfWork(); $this->conn = $em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); } /** * Deletes the persistent state represented by the given collection. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return void */ 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)); } /** * Gets the SQL statement for deleting the given collection. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return string */ abstract protected function getDeleteSQL(PersistentCollection $coll); /** * Gets the SQL parameters for the corresponding SQL statement to delete * the given collection. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return array */ abstract protected function getDeleteSQLParameters(PersistentCollection $coll); /** * Updates the given collection, synchronizing its state with the database * by inserting, updating and deleting individual elements. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return void */ public function update(PersistentCollection $coll) { $mapping = $coll->getMapping(); if ( ! $mapping['isOwningSide']) { return; // ignore inverse side } $this->deleteRows($coll); $this->insertRows($coll); } /** * Deletes rows. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return void */ public function deleteRows(PersistentCollection $coll) { $diff = $coll->getDeleteDiff(); $sql = $this->getDeleteRowSQL($coll); foreach ($diff as $element) { $this->conn->executeUpdate($sql, $this->getDeleteRowSQLParameters($coll, $element)); } } /** * Inserts rows. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return void */ public function insertRows(PersistentCollection $coll) { $diff = $coll->getInsertDiff(); $sql = $this->getInsertRowSQL($coll); foreach ($diff as $element) { $this->conn->executeUpdate($sql, $this->getInsertRowSQLParameters($coll, $element)); } } /** * Counts the size of this persistent collection. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return integer * * @throws \BadMethodCallException */ public function count(PersistentCollection $coll) { throw new \BadMethodCallException("Counting the size of this persistent collection is not supported by this CollectionPersister."); } /** * Slices elements. * * @param \Doctrine\ORM\PersistentCollection $coll * @param integer $offset * @param integer $length * * @return array * * @throws \BadMethodCallException */ public function slice(PersistentCollection $coll, $offset, $length = null) { throw new \BadMethodCallException("Slicing elements is not supported by this CollectionPersister."); } /** * Checks for existence of an element. * * @param \Doctrine\ORM\PersistentCollection $coll * @param object $element * * @return boolean * * @throws \BadMethodCallException */ public function contains(PersistentCollection $coll, $element) { throw new \BadMethodCallException("Checking for existence of an element is not supported by this CollectionPersister."); } /** * Checks for existence of a key. * * @param \Doctrine\ORM\PersistentCollection $coll * @param mixed $key * * @return boolean * * @throws \BadMethodCallException */ public function containsKey(PersistentCollection $coll, $key) { throw new \BadMethodCallException("Checking for existence of a key is not supported by this CollectionPersister."); } /** * Removes an element. * * @param \Doctrine\ORM\PersistentCollection $coll * @param object $element * * @return mixed * * @throws \BadMethodCallException */ public function removeElement(PersistentCollection $coll, $element) { throw new \BadMethodCallException("Removing an element is not supported by this CollectionPersister."); } /** * Removes an element by key. * * @param \Doctrine\ORM\PersistentCollection $coll * @param mixed $key * * @return void * * @throws \BadMethodCallException */ public function removeKey(PersistentCollection $coll, $key) { throw new \BadMethodCallException("Removing a key is not supported by this CollectionPersister."); } /** * Gets an element by key. * * @param \Doctrine\ORM\PersistentCollection $coll * @param mixed $index * * @return mixed * * @throws \BadMethodCallException */ public function get(PersistentCollection $coll, $index) { throw new \BadMethodCallException("Selecting a collection by index is not supported by this CollectionPersister."); } /** * Gets the SQL statement used for deleting a row from the collection. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return string */ abstract protected function getDeleteRowSQL(PersistentCollection $coll); /** * Gets the SQL parameters for the corresponding SQL statement to delete the given * element from the given collection. * * @param \Doctrine\ORM\PersistentCollection $coll * @param mixed $element * * @return array */ abstract protected function getDeleteRowSQLParameters(PersistentCollection $coll, $element); /** * Gets the SQL statement used for updating a row in the collection. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return string */ abstract protected function getUpdateRowSQL(PersistentCollection $coll); /** * Gets the SQL statement used for inserting a row in the collection. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return string */ abstract protected function getInsertRowSQL(PersistentCollection $coll); /** * Gets the SQL parameters for the corresponding SQL statement to insert the given * element of the given collection into the database. * * @param \Doctrine\ORM\PersistentCollection $coll * @param mixed $element * * @return array */ abstract protected function getInsertRowSQLParameters(PersistentCollection $coll, $element); } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php000066400000000000000000000066251257105210500302660ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\DBAL\Types\Type; /** * Base class for entity persisters that implement a certain inheritance mapping strategy. * All these persisters are assumed to use a discriminator column to discriminate entity * types in the hierarchy. * * @author Roman Borschel * @author Benjamin Eberlei * @since 2.0 */ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister { /** * {@inheritdoc} */ 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; } /** * Gets the name of the table that contains the discriminator column. * * @return string The table name. */ abstract protected function getDiscriminatorColumnTableName(); /** * {@inheritdoc} */ protected function getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { $tableAlias = $alias == 'r' ? '' : $alias; $columnName = $class->columnNames[$field]; $columnAlias = $this->getSQLColumnAlias($columnName); $sql = $this->getSQLTableAlias($class->name, $tableAlias) . '.' . $this->quoteStrategy->getColumnName($field, $class, $this->platform); $this->rsm->addFieldResult($alias, $columnAlias, $field, $class->name); if (isset($class->fieldMappings[$field]['requireSQLConversion'])) { $type = Type::getType($class->getTypeOfField($field)); $sql = $type->convertToPHPValueSQL($sql, $this->platform); } return $sql . ' AS ' . $columnAlias; } /** * @param string $tableAlias * @param string $joinColumnName * @param string $className * * @return string */ protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className) { $columnAlias = $this->getSQLColumnAlias($joinColumnName); $this->rsm->addMetaResult('r', $columnAlias, $joinColumnName); return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias; } } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php000066400000000000000000002155511257105210500253720ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Connection; use Doctrine\ORM\ORMException; use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\EntityManager; use Doctrine\ORM\UnitOfWork; use Doctrine\ORM\Query; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Common\Util\ClassUtils; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Expr\Comparison; /** * A BasicEntityPersister maps an entity to a single table in a relational database. * * A persister is always responsible for a single entity type. * * EntityPersisters are used during a UnitOfWork to apply any changes to the persistent * state of entities onto a relational database when the UnitOfWork is committed, * as well as for basic querying of entities and their associations (not DQL). * * The persisting operations that are invoked during a commit of a UnitOfWork to * persist the persistent entity state are: * * - {@link addInsert} : To schedule an entity for insertion. * - {@link executeInserts} : To execute all scheduled insertions. * - {@link update} : To update the persistent state of an entity. * - {@link delete} : To delete the persistent state of an entity. * * As can be seen from the above list, insertions are batched and executed all at once * for increased efficiency. * * The querying operations invoked during a UnitOfWork, either through direct find * requests or lazy-loading, are the following: * * - {@link load} : Loads (the state of) a single, managed entity. * - {@link loadAll} : Loads multiple, managed entities. * - {@link loadOneToOneEntity} : Loads a one/many-to-one entity association (lazy-loading). * - {@link loadOneToManyCollection} : Loads a one-to-many entity association (lazy-loading). * - {@link loadManyToManyCollection} : Loads a many-to-many entity association (lazy-loading). * * The BasicEntityPersister implementation provides the default behavior for * persisting and querying entities that are mapped to a single database table. * * Subclasses can be created to provide custom persisting and querying strategies, * i.e. spanning multiple tables. * * @author Roman Borschel * @author Giorgio Sironi * @author Benjamin Eberlei * @author Alexander * @author Fabio B. Silva * @since 2.0 */ class BasicEntityPersister { /** * @var array */ static private $comparisonMap = array( Comparison::EQ => '= %s', Comparison::IS => '= %s', Comparison::NEQ => '!= %s', Comparison::GT => '> %s', Comparison::GTE => '>= %s', Comparison::LT => '< %s', Comparison::LTE => '<= %s', Comparison::IN => 'IN (%s)', Comparison::NIN => 'NOT IN (%s)', Comparison::CONTAINS => 'LIKE %s', ); /** * Metadata object that describes the mapping of the mapped entity class. * * @var \Doctrine\ORM\Mapping\ClassMetadata */ protected $class; /** * The underlying DBAL Connection of the used EntityManager. * * @var \Doctrine\DBAL\Connection $conn */ protected $conn; /** * The database platform. * * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ protected $platform; /** * The EntityManager instance. * * @var \Doctrine\ORM\EntityManager */ protected $em; /** * Queued inserts. * * @var array */ protected $queuedInserts = array(); /** * ResultSetMapping that is used for all queries. Is generated lazily once per request. * * TODO: Evaluate Caching in combination with the other cached SQL snippets. * * @var Query\ResultSetMapping */ protected $rsm; /** * The map of column names to DBAL mapping types of all prepared columns used * when INSERTing or UPDATEing an entity. * * @var array * * @see prepareInsertData($entity) * @see prepareUpdateData($entity) */ protected $columnTypes = array(); /** * The map of quoted column names. * * @var array * * @see prepareInsertData($entity) * @see prepareUpdateData($entity) */ protected $quotedColumns = array(); /** * The INSERT SQL statement used for entities handled by this persister. * This SQL is only generated once per request, if at all. * * @var string */ private $insertSql; /** * The SELECT column list SQL fragment used for querying entities by this persister. * This SQL fragment is only generated once per request, if at all. * * @var string */ protected $selectColumnListSql; /** * The JOIN SQL fragment used to eagerly load all many-to-one and one-to-one * associations configured as FETCH_EAGER, as well as all inverse one-to-one associations. * * @var string */ protected $selectJoinSql; /** * Counter for creating unique SQL table and column aliases. * * @var integer */ protected $sqlAliasCounter = 0; /** * Map from class names (FQCN) to the corresponding generated SQL table aliases. * * @var array */ protected $sqlTableAliases = array(); /** * The quote strategy. * * @var \Doctrine\ORM\Mapping\QuoteStrategy */ protected $quoteStrategy; /** * Initializes a new BasicEntityPersister that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. * * @param \Doctrine\ORM\EntityManager $em * @param \Doctrine\ORM\Mapping\ClassMetadata $class */ public function __construct(EntityManager $em, ClassMetadata $class) { $this->em = $em; $this->class = $class; $this->conn = $em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); } /** * @return \Doctrine\ORM\Mapping\ClassMetadata */ public function getClassMetadata() { return $this->class; } /** * Adds an entity to the queued insertions. * The entity remains queued until {@link executeInserts} is invoked. * * @param object $entity The entity to queue for insertion. * * @return void */ public function addInsert($entity) { $this->queuedInserts[spl_object_hash($entity)] = $entity; } /** * Executes all queued entity insertions and returns any generated post-insert * identifiers that were created as a result of the insertions. * * If no inserts are queued, invoking this method is a NOOP. * * @return array An array of any generated post-insert IDs. This will be an empty array * if the entity class does not use the IDENTITY generation strategy. */ public function executeInserts() { if ( ! $this->queuedInserts) { return array(); } $postInsertIds = array(); $idGenerator = $this->class->idGenerator; $isPostInsertId = $idGenerator->isPostInsertGenerator(); $stmt = $this->conn->prepare($this->getInsertSQL()); $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]); } } $stmt->execute(); if ($isPostInsertId) { $id = $idGenerator->generate($this->em, $entity); $postInsertIds[$id] = $entity; } else { $id = $this->class->getIdentifierValues($entity); } if ($this->class->isVersioned) { $this->assignDefaultVersionValue($entity, $id); } } $stmt->closeCursor(); $this->queuedInserts = array(); return $postInsertIds; } /** * Retrieves the default version value which was created * by the preceding INSERT statement and assigns it back in to the * entities version field. * * @param object $entity * @param mixed $id * * @return void */ protected function assignDefaultVersionValue($entity, $id) { $value = $this->fetchVersionValue($this->class, $id); $this->class->setFieldValue($entity, $this->class->versionField, $value); } /** * Fetches the current version value of a versioned entity. * * @param \Doctrine\ORM\Mapping\ClassMetadata $versionedClass * @param mixed $id * * @return mixed */ protected function fetchVersionValue($versionedClass, $id) { $versionField = $versionedClass->versionField; $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); $identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform); $columnName = $this->quoteStrategy->getColumnName($versionField, $versionedClass, $this->platform); //FIXME: Order with composite keys might not be correct $sql = 'SELECT ' . $columnName . ' FROM ' . $tableName . ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?'; $value = $this->conn->fetchColumn($sql, array_values((array) $id)); return Type::getType($versionedClass->fieldMappings[$versionField]['type'])->convertToPHPValue($value, $this->platform); } /** * Updates a managed entity. The entity is updated according to its current changeset * in the running UnitOfWork. If there is no changeset, nothing is updated. * * The data to update is retrieved through {@link prepareUpdateData}. * Subclasses that override this method are supposed to obtain the update data * in the same way, through {@link prepareUpdateData}. * * Subclasses are also supposed to take care of versioning when overriding this method, * if necessary. The {@link updateTable} method can be used to apply the data retrieved * from {@prepareUpdateData} on the target tables, thereby optionally applying versioning. * * @param object $entity The entity to update. * * @return void */ public function update($entity) { $tableName = $this->class->getTableName(); $updateData = $this->prepareUpdateData($entity); if ( ! isset($updateData[$tableName]) || ! ($data = $updateData[$tableName])) { return; } $isVersioned = $this->class->isVersioned; $quotedTableName = $this->quoteStrategy->getTableName($this->class, $this->platform); $this->updateTable($entity, $quotedTableName, $data, $isVersioned); if ($isVersioned) { $id = $this->em->getUnitOfWork()->getEntityIdentifier($entity); $this->assignDefaultVersionValue($entity, $id); } } /** * Performs an UPDATE statement for an entity on a specific table. * The UPDATE can optionally be versioned, which requires the entity to have a version field. * * @param object $entity The entity object being updated. * @param string $quotedTableName The quoted name of the table to apply the UPDATE on. * @param array $updateData The map of columns to update (column => value). * @param boolean $versioned Whether the UPDATE should be versioned. * * @return void * * @throws \Doctrine\ORM\ORMException * @throws \Doctrine\ORM\OptimisticLockException */ protected final function updateTable($entity, $quotedTableName, array $updateData, $versioned = false) { $set = array(); $types = array(); $params = array(); foreach ($updateData as $columnName => $value) { $placeholder = '?'; $column = $columnName; switch (true) { case isset($this->class->fieldNames[$columnName]): $fieldName = $this->class->fieldNames[$columnName]; $column = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); if (isset($this->class->fieldMappings[$fieldName]['requireSQLConversion'])) { $type = Type::getType($this->columnTypes[$columnName]); $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); } break; case isset($this->quotedColumns[$columnName]): $column = $this->quotedColumns[$columnName]; break; } $params[] = $value; $set[] = $column . ' = ' . $placeholder; $types[] = $this->columnTypes[$columnName]; } $where = array(); $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); foreach ($this->class->identifier as $idField) { if ( ! isset($this->class->associationMappings[$idField])) { $params[] = $identifier[$idField]; $types[] = $this->class->fieldMappings[$idField]['type']; $where[] = $this->quoteStrategy->getColumnName($idField, $this->class, $this->platform); continue; } $params[] = $identifier[$idField]; $where[] = $this->class->associationMappings[$idField]['joinColumns'][0]['name']; $targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]['targetEntity']); switch (true) { case (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])): $types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type']; break; case (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])): $types[] = $targetMapping->associationMappings[$targetMapping->identifier[0]]['type']; break; default: throw ORMException::unrecognizedField($targetMapping->identifier[0]); } } if ($versioned) { $versionField = $this->class->versionField; $versionFieldType = $this->class->fieldMappings[$versionField]['type']; $versionColumn = $this->quoteStrategy->getColumnName($versionField, $this->class, $this->platform); $where[] = $versionColumn; $types[] = $this->class->fieldMappings[$versionField]['type']; $params[] = $this->class->reflFields[$versionField]->getValue($entity); switch ($versionFieldType) { case Type::INTEGER: $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; break; case Type::DATETIME: $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; break; } } $sql = 'UPDATE ' . $quotedTableName . ' SET ' . implode(', ', $set) . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; $result = $this->conn->executeUpdate($sql, $params, $types); if ($versioned && ! $result) { throw OptimisticLockException::lockFailed($entity); } } /** * @todo Add check for platform if it supports foreign keys/cascading. * * @param array $identifier * * @return void */ protected function deleteJoinTableRecords($identifier) { foreach ($this->class->associationMappings as $mapping) { if ($mapping['type'] !== ClassMetadata::MANY_TO_MANY) { continue; } // @Todo this only covers scenarios with no inheritance or of the same level. Is there something // like self-referential relationship between different levels of an inheritance hierarchy? I hope not! $selfReferential = ($mapping['targetEntity'] == $mapping['sourceEntity']); $class = $this->class; $association = $mapping; $otherColumns = array(); $otherKeys = array(); $keys = array(); if ( ! $mapping['isOwningSide']) { $class = $this->em->getClassMetadata($mapping['targetEntity']); $association = $class->associationMappings[$mapping['mappedBy']]; } $joinColumns = $mapping['isOwningSide'] ? $association['joinTable']['joinColumns'] : $association['joinTable']['inverseJoinColumns']; if ($selfReferential) { $otherColumns = (! $mapping['isOwningSide']) ? $association['joinTable']['joinColumns'] : $association['joinTable']['inverseJoinColumns']; } foreach ($joinColumns as $joinColumn) { $keys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } foreach ($otherColumns as $joinColumn) { $otherKeys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } if (isset($mapping['isOnDeleteCascade'])) { continue; } $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); $this->conn->delete($joinTableName, array_combine($keys, $identifier)); if ($selfReferential) { $this->conn->delete($joinTableName, array_combine($otherKeys, $identifier)); } } } /** * Deletes a managed entity. * * The entity to delete must be managed and have a persistent identifier. * The deletion happens instantaneously. * * Subclasses may override this method to customize the semantics of entity deletion. * * @param object $entity The entity to delete. * * @return void */ public function delete($entity) { $class = $this->class; $em = $this->em; $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); $tableName = $this->quoteStrategy->getTableName($class, $this->platform); $idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform); $id = array_combine($idColumns, $identifier); $types = array_map(function ($identifier) use ($class, $em) { if (isset($class->fieldMappings[$identifier])) { return $class->fieldMappings[$identifier]['type']; } $targetMapping = $em->getClassMetadata($class->associationMappings[$identifier]['targetEntity']); if (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])) { return $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type']; } if (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])) { return $targetMapping->associationMappings[$targetMapping->identifier[0]]['type']; } throw ORMException::unrecognizedField($targetMapping->identifier[0]); }, $class->identifier); $this->deleteJoinTableRecords($identifier); $this->conn->delete($tableName, $id, $types); } /** * Prepares the changeset of an entity for database insertion (UPDATE). * * The changeset is obtained from the currently running UnitOfWork. * * During this preparation the array that is passed as the second parameter is filled with * => pairs, grouped by table name. * * Example: * * array( * 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...), * 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...), * ... * ) * * * @param object $entity The entity for which to prepare the data. * * @return array The prepared data. */ protected function prepareUpdateData($entity) { $result = array(); $uow = $this->em->getUnitOfWork(); if (($versioned = $this->class->isVersioned) != false) { $versionField = $this->class->versionField; } foreach ($uow->getEntityChangeSet($entity) as $field => $change) { if ($versioned && $versionField == $field) { continue; } $newVal = $change[1]; if ( ! isset($this->class->associationMappings[$field])) { $columnName = $this->class->columnNames[$field]; $this->columnTypes[$columnName] = $this->class->fieldMappings[$field]['type']; $result[$this->getOwningTable($field)][$columnName] = $newVal; continue; } $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; } 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 // extra update on the UnitOfWork. $uow->scheduleExtraUpdate($entity, array($field => array(null, $newVal))); $newVal = null; } } if ($newVal !== null) { $newValId = $uow->getEntityIdentifier($newVal); } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $owningTable = $this->getOwningTable($field); foreach ($assoc['joinColumns'] as $joinColumn) { $sourceColumn = $joinColumn['name']; $targetColumn = $joinColumn['referencedColumnName']; $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $this->quotedColumns[$sourceColumn] = $quotedColumn; $this->columnTypes[$sourceColumn] = $targetClass->getTypeOfColumn($targetColumn); switch (true) { case $newVal === null: $value = null; break; case $targetClass->containsForeignIdentifier: $value = $newValId[$targetClass->getFieldForColumn($targetColumn)]; break; default: $value = $newValId[$targetClass->fieldNames[$targetColumn]]; break; } $result[$owningTable][$sourceColumn] = $value; } } return $result; } /** * Prepares the data changeset of a managed entity for database insertion (initial INSERT). * The changeset of the entity is obtained from the currently running UnitOfWork. * * The default insert data preparation is the same as for updates. * * @param object $entity The entity for which to prepare the data. * * @return array The prepared data for the tables to update. * * @see prepareUpdateData */ protected function prepareInsertData($entity) { return $this->prepareUpdateData($entity); } /** * Gets the name of the table that owns the column the given field is mapped to. * * The default implementation in BasicEntityPersister always returns the name * of the table the entity type of this persister is mapped to, since an entity * is always persisted to a single table with a BasicEntityPersister. * * @param string $fieldName The field name. * * @return string The table name. */ public function getOwningTable($fieldName) { return $this->class->getTableName(); } /** * Loads an entity by a list of field criteria. * * @param array $criteria The criteria by which to load the entity. * @param object|null $entity The entity to load the data into. If not specified, a new entity is created. * @param array|null $assoc The association that connects the entity to load to another entity, if any. * @param array $hints Hints for entity creation. * @param int $lockMode * @param int|null $limit Limit number of results. * @param array|null $orderBy Criteria to order by. * * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. * * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? */ public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null, array $orderBy = null) { $sql = $this->getSelectSQL($criteria, $assoc, $lockMode, $limit, null, $orderBy); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->conn->executeQuery($sql, $params, $types); if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; $hints[Query::HINT_REFRESH_ENTITY] = $entity; } $hydrator = $this->em->newHydrator($this->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); $entities = $hydrator->hydrateAll($stmt, $this->rsm, $hints); return $entities ? $entities[0] : null; } /** * Loads an entity of this persister's mapped class as part of a single-valued * association from another entity. * * @param array $assoc The association to load. * @param object $sourceEntity The entity that owns the association (not necessarily the "owning side"). * @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. * * @throws \Doctrine\ORM\Mapping\MappingException */ public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array()) { if (($foundEntity = $this->em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) != false) { return $foundEntity; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); if ($assoc['isOwningSide']) { $isInverseSingleValued = $assoc['inversedBy'] && ! $targetClass->isCollectionValuedAssociation($assoc['inversedBy']); // 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']["r"][$assoc['inversedBy']] = true; } /* cascade read-only status if ($this->em->getUnitOfWork()->isReadOnly($sourceEntity)) { $hints[Query::HINT_READ_ONLY] = true; } */ $targetEntity = $this->load($identifier, null, $assoc, $hints); // Complete bidirectional association, if necessary if ($targetEntity !== null && $isInverseSingleValued) { $targetClass->reflFields[$assoc['inversedBy']]->setValue($targetEntity, $sourceEntity); } return $targetEntity; } $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])) { 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, null, $assoc); if ($targetEntity !== null) { $targetClass->setFieldValue($targetEntity, $assoc['mappedBy'], $sourceEntity); } return $targetEntity; } /** * Refreshes a managed entity. * * @param array $id The identifier of the entity as an associative array from * column or field names to values. * @param object $entity The entity to refresh. * @param int $lockMode * * @return void */ public function refresh(array $id, $entity, $lockMode = 0) { $sql = $this->getSelectSQL($id, null, $lockMode); list($params, $types) = $this->expandParameters($id); $stmt = $this->conn->executeQuery($sql, $params, $types); $hydrator = $this->em->newHydrator(Query::HYDRATE_OBJECT); $hydrator->hydrateAll($stmt, $this->rsm, array(Query::HINT_REFRESH => true)); } /** * Loads Entities matching the given Criteria object. * * @param \Doctrine\Common\Collections\Criteria $criteria * * @return array */ public function loadCriteria(Criteria $criteria) { $orderBy = $criteria->getOrderings(); $limit = $criteria->getMaxResults(); $offset = $criteria->getFirstResult(); $query = $this->getSelectSQL($criteria, null, 0, $limit, $offset, $orderBy); list($params, $types) = $this->expandCriteriaParameters($criteria); $stmt = $this->conn->executeQuery($query, $params, $types); $hydrator = $this->em->newHydrator(($this->selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); return $hydrator->hydrateAll($stmt, $this->rsm, array(UnitOfWork::HINT_DEFEREAGERLOAD => true)); } /** * Expands Criteria Parameters by walking the expressions and grabbing all * parameters and types from it. * * @param \Doctrine\Common\Collections\Criteria $criteria * * @return array(array(), array()) */ private function expandCriteriaParameters(Criteria $criteria) { $expression = $criteria->getWhereExpression(); if ($expression === null) { return array(array(), array()); } $valueVisitor = new SqlValueVisitor(); $valueVisitor->dispatch($expression); list($values, $types) = $valueVisitor->getParamsAndTypes(); $sqlValues = array(); foreach ($values as $value) { $sqlValues[] = $this->getValue($value); } $sqlTypes = array(); foreach ($types as $type) { list($field, $value) = $type; $sqlTypes[] = $this->getType($field, $value); } return array($sqlValues, $sqlTypes); } /** * Loads a list of entities by a list of field criteria. * * @param array $criteria * @param array|null $orderBy * @param int|null $limit * @param int|null $offset * * @return array */ public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null) { $sql = $this->getSelectSQL($criteria, null, 0, $limit, $offset, $orderBy); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->conn->executeQuery($sql, $params, $types); $hydrator = $this->em->newHydrator(($this->selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); return $hydrator->hydrateAll($stmt, $this->rsm, array(UnitOfWork::HINT_DEFEREAGERLOAD => true)); } /** * Gets (sliced or full) elements of the given collection. * * @param array $assoc * @param object $sourceEntity * @param int|null $offset * @param int|null $limit * * @return array */ public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); return $this->loadArrayFromStatement($assoc, $stmt); } /** * Loads an array of entities from a given DBAL statement. * * @param array $assoc * @param \Doctrine\DBAL\Statement $stmt * * @return array */ private function loadArrayFromStatement($assoc, $stmt) { $rsm = $this->rsm; $hints = array(UnitOfWork::HINT_DEFEREAGERLOAD => true); if (isset($assoc['indexBy'])) { $rsm = clone ($this->rsm); // this is necessary because the "default rsm" should be changed. $rsm->addIndexBy('r', $assoc['indexBy']); } return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); } /** * Hydrates a collection from a given DBAL statement. * * @param array $assoc * @param \Doctrine\DBAL\Statement $stmt * @param PersistentCollection $coll * * @return array */ private function loadCollectionFromStatement($assoc, $stmt, $coll) { $rsm = $this->rsm; $hints = array( UnitOfWork::HINT_DEFEREAGERLOAD => true, 'collection' => $coll ); if (isset($assoc['indexBy'])) { $rsm = clone ($this->rsm); // this is necessary because the "default rsm" should be changed. $rsm->addIndexBy('r', $assoc['indexBy']); } return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); } /** * Loads a collection of entities of a many-to-many association. * * @param array $assoc The association mapping of the association being loaded. * @param object $sourceEntity The entity that owns the collection. * @param PersistentCollection $coll The collection to fill. * * @return array */ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } /** * @param array $assoc * @param object $sourceEntity * @param int|null $offset * @param int|null $limit * * @return \Doctrine\DBAL\Driver\Statement * * @throws \Doctrine\ORM\Mapping\MappingException */ private function getManyToManyStatement(array $assoc, $sourceEntity, $offset = null, $limit = null) { $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); $class = $sourceClass; $association = $assoc; $criteria = array(); if ( ! $assoc['isOwningSide']) { $class = $this->em->getClassMetadata($assoc['targetEntity']); $association = $class->associationMappings[$assoc['mappedBy']]; } $joinColumns = $assoc['isOwningSide'] ? $association['joinTable']['joinColumns'] : $association['joinTable']['inverseJoinColumns']; $quotedJoinTable = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); foreach ($joinColumns as $joinColumn) { $sourceKeyColumn = $joinColumn['referencedColumnName']; $quotedKeyColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); switch (true) { case $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($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } break; case isset($sourceClass->fieldNames[$sourceKeyColumn]): $field = $sourceClass->fieldNames[$sourceKeyColumn]; $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); break; default: throw MappingException::joinColumnMustPointToMappedField( $sourceClass->name, $sourceKeyColumn ); } $criteria[$quotedJoinTable . '.' . $quotedKeyColumn] = $value; } $sql = $this->getSelectSQL($criteria, $assoc, 0, $limit, $offset); list($params, $types) = $this->expandParameters($criteria); return $this->conn->executeQuery($sql, $params, $types); } /** * Gets the SELECT SQL to select one or more entities by a set of field criteria. * * @param array|\Doctrine\Common\Collections\Criteria $criteria * @param array|null $assoc * @param int $lockMode * @param int|null $limit * @param int|null $offset * @param array|null $orderBy * * @return string */ protected function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) { $lockSql = ''; $joinSql = ''; $orderBySql = ''; if ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) { $joinSql = $this->getSelectManyToManyJoinSQL($assoc); } if (isset($assoc['orderBy'])) { $orderBy = $assoc['orderBy']; } if ($orderBy) { $orderBySql = $this->getOrderBySQL($orderBy, $this->getSQLTableAlias($this->class->name)); } $conditionSql = ($criteria instanceof Criteria) ? $this->getSelectConditionCriteriaSQL($criteria) : $this->getSelectConditionSQL($criteria, $assoc); switch ($lockMode) { case LockMode::PESSIMISTIC_READ: $lockSql = ' ' . $this->platform->getReadLockSql(); break; case LockMode::PESSIMISTIC_WRITE: $lockSql = ' ' . $this->platform->getWriteLockSql(); break; } $columnList = $this->getSelectColumnsSQL(); $tableAlias = $this->getSQLTableAlias($this->class->name); $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); if ('' !== $filterSql) { $conditionSql = $conditionSql ? $conditionSql . ' AND ' . $filterSql : $filterSql; } $select = 'SELECT ' . $columnList; $from = ' FROM ' . $tableName . ' '. $tableAlias; $join = $this->selectJoinSql . $joinSql; $where = ($conditionSql ? ' WHERE ' . $conditionSql : ''); $lock = $this->platform->appendLockHint($from, $lockMode); $query = $select . $lock . $join . $where . $orderBySql; return $this->platform->modifyLimitQuery($query, $limit, $offset) . $lockSql; } /** * Gets the ORDER BY SQL snippet for ordered collections. * * @param array $orderBy * @param string $baseTableAlias * * @return string * * @throws \Doctrine\ORM\ORMException */ protected final function getOrderBySQL(array $orderBy, $baseTableAlias) { $orderByList = array(); foreach ($orderBy as $fieldName => $orientation) { $orientation = strtoupper(trim($orientation)); if ($orientation != 'ASC' && $orientation != 'DESC') { throw ORMException::invalidOrientation($this->class->name, $fieldName); } if (isset($this->class->fieldMappings[$fieldName])) { $tableAlias = isset($this->class->fieldMappings[$fieldName]['inherited']) ? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]['inherited']) : $baseTableAlias; $columnName = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; continue; } if (isset($this->class->associationMappings[$fieldName])) { if ( ! $this->class->associationMappings[$fieldName]['isOwningSide']) { throw ORMException::invalidFindByInverseAssociation($this->class->name, $fieldName); } $tableAlias = isset($this->class->associationMappings[$fieldName]['inherited']) ? $this->getSQLTableAlias($this->class->associationMappings[$fieldName]['inherited']) : $baseTableAlias; foreach ($this->class->associationMappings[$fieldName]['joinColumns'] as $joinColumn) { $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; } continue; } throw ORMException::unrecognizedField($fieldName); } return ' ORDER BY ' . implode(', ', $orderByList); } /** * Gets the SQL fragment with the list of columns to select when querying for * an entity in this persister. * * Subclasses should override this method to alter or change the select column * list SQL fragment. Note that in the implementation of BasicEntityPersister * the resulting SQL fragment is generated only once and cached in {@link selectColumnListSql}. * Subclasses may or may not do the same. * * @return string The SQL fragment. */ protected function getSelectColumnsSQL() { if ($this->selectColumnListSql !== null) { return $this->selectColumnListSql; } $columnList = array(); $this->rsm = new Query\ResultSetMapping(); $this->rsm->addEntityResult($this->class->name, 'r'); // r for root // Add regular columns to select list foreach ($this->class->fieldNames as $field) { $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) { $columnList[] = $assocColumnSQL; } if ( ! (($assoc['type'] & ClassMetadata::TO_ONE) && ($assoc['fetch'] == ClassMetadata::FETCH_EAGER || !$assoc['isOwningSide']))) { continue; } $eagerEntity = $this->em->getClassMetadata($assoc['targetEntity']); if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) { continue; // now this is why you shouldn't use inheritance } $assocAlias = 'e' . ($eagerAliasCounter++); $this->rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField); foreach ($eagerEntity->fieldNames as $field) { $columnList[] = $this->getSelectColumnSQL($field, $eagerEntity, $assocAlias); } foreach ($eagerEntity->associationMappings as $eagerAssocField => $eagerAssoc) { $eagerAssocColumnSQL = $this->getSelectColumnAssociationSQL( $eagerAssocField, $eagerAssoc, $eagerEntity, $assocAlias ); if ($eagerAssocColumnSQL) { $columnList[] = $eagerAssocColumnSQL; } } $association = $assoc; $joinCondition = array(); if ( ! $assoc['isOwningSide']) { $eagerEntity = $this->em->getClassMetadata($assoc['targetEntity']); $association = $eagerEntity->getAssociationMapping($assoc['mappedBy']); } $joinTableAlias = $this->getSQLTableAlias($eagerEntity->name, $assocAlias); $joinTableName = $this->quoteStrategy->getTableName($eagerEntity, $this->platform); if ($assoc['isOwningSide']) { $tableAlias = $this->getSQLTableAlias($association['targetEntity'], $assocAlias); $this->selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($association['joinColumns']); foreach ($association['joinColumns'] as $joinColumn) { $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity']) . '.' . $sourceCol . ' = ' . $tableAlias . '.' . $targetCol; } // Add filter SQL if ($filterSql = $this->generateFilterConditionSQL($eagerEntity, $tableAlias)) { $joinCondition[] = $filterSql; } } else { $this->selectJoinSql .= ' LEFT JOIN'; foreach ($association['joinColumns'] as $joinColumn) { $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' . $this->getSQLTableAlias($association['targetEntity']) . '.' . $targetCol; } } $this->selectJoinSql .= ' ' . $joinTableName . ' ' . $joinTableAlias . ' ON '; $this->selectJoinSql .= implode(' AND ', $joinCondition); } $this->selectColumnListSql = implode(', ', $columnList); 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') { if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) ) { return ''; } $columnList = array(); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); foreach ($assoc['joinColumns'] as $joinColumn) { $type = null; $isIdentifier = isset($assoc['id']) && $assoc['id'] === true; $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $resultColumnName = $this->getSQLColumnAlias($joinColumn['name']); $columnList[] = $this->getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . '.' . $quotedColumn . ' AS ' . $resultColumnName; if (isset($targetClass->fieldNames[$joinColumn['referencedColumnName']])) { $type = $targetClass->fieldMappings[$targetClass->fieldNames[$joinColumn['referencedColumnName']]]['type']; } $this->rsm->addMetaResult($alias, $resultColumnName, $quotedColumn, $isIdentifier, $type); } return implode(', ', $columnList); } /** * Gets the SQL join fragment used when selecting entities from a * many-to-many association. * * @param array $manyToMany * * @return string */ protected function getSelectManyToManyJoinSQL(array $manyToMany) { $conditions = array(); $association = $manyToMany; $sourceTableAlias = $this->getSQLTableAlias($this->class->name); if ( ! $manyToMany['isOwningSide']) { $targetEntity = $this->em->getClassMetadata($manyToMany['targetEntity']); $association = $targetEntity->associationMappings[$manyToMany['mappedBy']]; } $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); $joinColumns = ($manyToMany['isOwningSide']) ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; foreach ($joinColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableName . '.' . $quotedSourceColumn; } return ' INNER JOIN ' . $joinTableName . ' ON ' . implode(' AND ', $conditions); } /** * Gets the INSERT SQL used by the persister to persist a new entity. * * @return string */ protected function getInsertSQL() { if ($this->insertSql !== null) { return $this->insertSql; } $columns = $this->getInsertColumnList(); $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); if (empty($columns)) { $identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform); $this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn); return $this->insertSql; } $values = array(); $columns = array_unique($columns); foreach ($columns as $column) { $placeholder = '?'; if (isset($this->class->fieldNames[$column]) && isset($this->columnTypes[$this->class->fieldNames[$column]]) && isset($this->class->fieldMappings[$this->class->fieldNames[$column]]['requireSQLConversion'])) { $type = Type::getType($this->columnTypes[$this->class->fieldNames[$column]]); $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); } $values[] = $placeholder; } $columns = implode(', ', $columns); $values = implode(', ', $values); $this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values); return $this->insertSql; } /** * Gets the list of columns to put in the INSERT SQL statement. * * Subclasses should override this method to alter or change the list of * columns placed in the INSERT statements used by the persister. * * @return array The list of columns. */ 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['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); } } continue; } if ($this->class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || $this->class->identifier[0] != $name) { $columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform); $this->columnTypes[$name] = $this->class->fieldMappings[$name]['type']; } } return $columns; } /** * Gets the SQL snippet of a qualified column name for the given field name. * * @param string $field The field name. * @param ClassMetadata $class The class that declares this field. The table this class is * mapped to must own the column for the given field. * @param string $alias * * @return string */ protected function getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { $root = $alias == 'r' ? '' : $alias ; $tableAlias = $this->getSQLTableAlias($class->name, $root); $columnName = $this->quoteStrategy->getColumnName($field, $class, $this->platform); $sql = $tableAlias . '.' . $columnName; $columnAlias = $this->getSQLColumnAlias($class->columnNames[$field]); $this->rsm->addFieldResult($alias, $columnAlias, $field); if (isset($class->fieldMappings[$field]['requireSQLConversion'])) { $type = Type::getType($class->getTypeOfField($field)); $sql = $type->convertToPHPValueSQL($sql, $this->platform); } return $sql . ' AS ' . $columnAlias; } /** * Gets the SQL table alias for the given class name. * * @param string $className * @param string $assocName * * @return string The SQL table alias. * * @todo Reconsider. Binding table aliases to class names is not such a good idea. */ protected function getSQLTableAlias($className, $assocName = '') { if ($assocName) { $className .= '#' . $assocName; } if (isset($this->sqlTableAliases[$className])) { return $this->sqlTableAliases[$className]; } $tableAlias = 't' . $this->sqlAliasCounter++; $this->sqlTableAliases[$className] = $tableAlias; return $tableAlias; } /** * Locks all rows of this entity matching the given criteria with the specified pessimistic lock mode. * * @param array $criteria * @param int $lockMode * * @return void */ public function lock(array $criteria, $lockMode) { $lockSql = ''; $conditionSql = $this->getSelectConditionSQL($criteria); switch ($lockMode) { case LockMode::PESSIMISTIC_READ: $lockSql = $this->platform->getReadLockSql(); break; case LockMode::PESSIMISTIC_WRITE: $lockSql = $this->platform->getWriteLockSql(); break; } $lock = $this->getLockTablesSql($lockMode); $where = ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' '; $sql = 'SELECT 1 ' . $lock . $where . $lockSql; list($params, $types) = $this->expandParameters($criteria); $this->conn->executeQuery($sql, $params, $types); } /** * Gets the FROM and optionally JOIN conditions to lock the entity managed by this persister. * * @param integer $lockMode One of the Doctrine\DBAL\LockMode::* constants. * * @return string */ protected function getLockTablesSql($lockMode) { return $this->platform->appendLockHint( 'FROM ' . $this->quoteStrategy->getTableName($this->class, $this->platform) . ' ' . $this->getSQLTableAlias($this->class->name), $lockMode ); } /** * Gets the Select Where Condition from a Criteria object. * * @param \Doctrine\Common\Collections\Criteria $criteria * * @return string */ protected function getSelectConditionCriteriaSQL(Criteria $criteria) { $expression = $criteria->getWhereExpression(); if ($expression === null) { return ''; } $visitor = new SqlExpressionVisitor($this, $this->class); return $visitor->dispatch($expression); } /** * Gets the SQL WHERE condition for matching a field with a given value. * * @param string $field * @param mixed $value * @param array|null $assoc * @param string|null $comparison * * @return string */ public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null) { $placeholder = '?'; $condition = $this->getSelectConditionStatementColumnSQL($field, $assoc); if (isset($this->class->fieldMappings[$field]['requireSQLConversion'])) { $placeholder = Type::getType($this->class->getTypeOfField($field))->convertToDatabaseValueSQL($placeholder, $this->platform); } if ($comparison !== null) { // special case null value handling if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) { return $condition . ' IS NULL'; } else if ($comparison === Comparison::NEQ && $value === null) { return $condition . ' IS NOT NULL'; } return $condition . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder); } if (is_array($value)) { return sprintf('%s IN (%s)' , $condition, $placeholder); } if ($value === null) { return sprintf('%s IS NULL' , $condition); } return sprintf('%s = %s' , $condition, $placeholder); } /** * Builds the left-hand-side of a where condition statement. * * @param string $field * @param array|null $assoc * * @return string * * @throws \Doctrine\ORM\ORMException */ protected function getSelectConditionStatementColumnSQL($field, $assoc = null) { if (isset($this->class->columnNames[$field])) { $className = (isset($this->class->fieldMappings[$field]['inherited'])) ? $this->class->fieldMappings[$field]['inherited'] : $this->class->name; return $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform); } if (isset($this->class->associationMappings[$field])) { if ( ! $this->class->associationMappings[$field]['isOwningSide']) { throw ORMException::invalidFindByInverseAssociation($this->class->name, $field); } $joinColumn = $this->class->associationMappings[$field]['joinColumns'][0]; $className = (isset($this->class->associationMappings[$field]['inherited'])) ? $this->class->associationMappings[$field]['inherited'] : $this->class->name; return $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); } 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" return $field; } throw ORMException::unrecognizedField($field); } /** * Gets the conditional SQL fragment used in the WHERE clause when selecting * entities in this persister. * * Subclasses are supposed to override this method if they intend to change * or alter the criteria by which entities are selected. * * @param array $criteria * @param array|null $assoc * * @return string */ protected function getSelectConditionSQL(array $criteria, $assoc = null) { $conditions = array(); foreach ($criteria as $field => $value) { $conditions[] = $this->getSelectConditionStatementSQL($field, $value, $assoc); } return implode(' AND ', $conditions); } /** * Returns an array with (sliced or full list) of elements in the specified collection. * * @param array $assoc * @param object $sourceEntity * @param int|null $offset * @param int|null $limit * * @return array */ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); return $this->loadArrayFromStatement($assoc, $stmt); } /** * Loads a collection of entities in a one-to-many association. * * @param array $assoc * @param object $sourceEntity * @param PersistentCollection $coll The collection to load/fill. * * @return array */ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } /** * Builds criteria and execute SQL statement to fetch the one to many entities from. * * @param array $assoc * @param object $sourceEntity * @param int|null $offset * @param int|null $limit * * @return \Doctrine\DBAL\Statement */ private function getOneToManyStatement(array $assoc, $sourceEntity, $offset = null, $limit = null) { $criteria = array(); $owningAssoc = $this->class->associationMappings[$assoc['mappedBy']]; $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); $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($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } $criteria[$tableAlias . "." . $targetKeyColumn] = $value; continue; } $criteria[$tableAlias . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } $sql = $this->getSelectSQL($criteria, $assoc, 0, $limit, $offset); list($params, $types) = $this->expandParameters($criteria); return $this->conn->executeQuery($sql, $params, $types); } /** * Expands the parameters from the given criteria and use the correct binding types if found. * * @param array $criteria * * @return array */ private function expandParameters($criteria) { $params = array(); $types = array(); foreach ($criteria as $field => $value) { if ($value === null) { continue; // skip null values. } $types[] = $this->getType($field, $value); $params[] = $this->getValue($value); } return array($params, $types); } /** * Infers field type to be used by parameter type casting. * * @param string $field * @param mixed $value * * @return integer * * @throws \Doctrine\ORM\Query\QueryException */ private function getType($field, $value) { switch (true) { case (isset($this->class->fieldMappings[$field])): $type = $this->class->fieldMappings[$field]['type']; 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 = $targetClass->fieldMappings[$targetClass->fieldNames[$targetColumn]]['type']; } break; default: $type = null; } if (is_array($value)) { $type = Type::getType($type)->getBindingType(); $type += Connection::ARRAY_PARAM_OFFSET; } return $type; } /** * Retrieves parameter value. * * @param mixed $value * * @return mixed */ private function getValue($value) { if ( ! is_array($value)) { return $this->getIndividualValue($value); } $newValue = array(); foreach ($value as $itemValue) { $newValue[] = $this->getIndividualValue($itemValue); } return $newValue; } /** * Retrieves an individual parameter value. * * @param mixed $value * * @return mixed */ private function getIndividualValue($value) { if ( ! is_object($value) || ! $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) { return $value; } return $this->em->getUnitOfWork()->getSingleIdentifierValue($value); } /** * Checks whether the given managed entity exists in the database. * * @param object $entity * @param array $extraConditions * * @return boolean TRUE if the entity exists in the database, FALSE otherwise. */ public function exists($entity, array $extraConditions = array()) { $criteria = $this->class->getIdentifierValues($entity); if ( ! $criteria) { return false; } if ($extraConditions) { $criteria = array_merge($criteria, $extraConditions); } $alias = $this->getSQLTableAlias($this->class->name); $sql = 'SELECT 1 ' . $this->getLockTablesSql(null) . ' WHERE ' . $this->getSelectConditionSQL($criteria); if ($filterSql = $this->generateFilterConditionSQL($this->class, $alias)) { $sql .= ' AND ' . $filterSql; } list($params) = $this->expandParameters($criteria); return (bool) $this->conn->fetchColumn($sql, $params); } /** * Generates the appropriate join SQL for the given join column. * * @param array $joinColumns The join columns definition of an association. * * @return string LEFT JOIN if one of the columns is nullable, INNER JOIN otherwise. */ protected function getJoinSQLForJoinColumns($joinColumns) { // if one of the join columns is nullable, return left join foreach ($joinColumns as $joinColumn) { if ( ! isset($joinColumn['nullable']) || $joinColumn['nullable']) { return 'LEFT JOIN'; } } return 'INNER JOIN'; } /** * Gets an SQL column alias for a column name. * * @param string $columnName * * @return string */ public function getSQLColumnAlias($columnName) { return $this->quoteStrategy->getColumnAlias($columnName, $this->sqlAliasCounter++, $this->platform); } /** * Generates the filter SQL for a given entity and table alias. * * @param ClassMetadata $targetEntity Metadata of the target entity. * @param string $targetTableAlias The table alias of the joined/selected table. * * @return string The SQL query part to add to a query. */ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { $filterClauses = array(); foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { $filterClauses[] = '(' . $filterExpr . ')'; } } $sql = implode(' AND ', $filterClauses); return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL" } } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/ElementCollectionPersister.php000066400000000000000000000024131257105210500265500ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; /** * Persister for collections of basic elements / value types. * * @author robo * @todo Implementation once support for collections of basic elements (i.e. strings) is added. */ abstract class ElementCollectionPersister extends AbstractCollectionPersister { } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php000066400000000000000000000501571257105210500260630ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Types\Type; use Doctrine\Common\Collections\Criteria; /** * The joined subclass persister maps a single entity instance to several tables in the * database as it is defined by the Class Table Inheritance strategy. * * @author Roman Borschel * @author Benjamin Eberlei * @author Alexander * @since 2.0 * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html */ class JoinedSubclassPersister extends AbstractEntityInheritancePersister { /** * Map that maps column names to the table names that own them. * This is mainly a temporary cache, used during a single request. * * @var array */ private $owningTableMap = array(); /** * Map of table to quoted table names. * * @var array */ private $quotedTableMap = array(); /** * {@inheritdoc} */ protected function getDiscriminatorColumnTableName() { $class = ($this->class->name !== $this->class->rootEntityName) ? $this->em->getClassMetadata($this->class->rootEntityName) : $this->class; return $class->getTableName(); } /** * This function finds the ClassMetadata instance in an inheritance hierarchy * that is responsible for enabling versioning. * * @return \Doctrine\ORM\Mapping\ClassMetadata */ private function getVersionedClassMetadata() { 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; } /** * Gets the name of the table that owns the column the given field is mapped to. * * @param string $fieldName * * @return string * * @override */ public function getOwningTable($fieldName) { if (isset($this->owningTableMap[$fieldName])) { return $this->owningTableMap[$fieldName]; } switch (true) { case isset($this->class->associationMappings[$fieldName]['inherited']): $cm = $this->em->getClassMetadata($this->class->associationMappings[$fieldName]['inherited']); break; case isset($this->class->fieldMappings[$fieldName]['inherited']): $cm = $this->em->getClassMetadata($this->class->fieldMappings[$fieldName]['inherited']); break; default: $cm = $this->class; break; } $tableName = $cm->getTableName(); $quotedTableName = $this->quoteStrategy->getTableName($cm, $this->platform); $this->owningTableMap[$fieldName] = $tableName; $this->quotedTableMap[$tableName] = $quotedTableName; return $tableName; } /** * {@inheritdoc} */ public function executeInserts() { if ( ! $this->queuedInserts) { return; } $postInsertIds = array(); $idGenerator = $this->class->idGenerator; $isPostInsertId = $idGenerator->isPostInsertGenerator(); $rootClass = ($this->class->name !== $this->class->rootEntityName) ? $this->em->getClassMetadata($this->class->rootEntityName) : $this->class; // Prepare statement for the root table $rootPersister = $this->em->getUnitOfWork()->getEntityPersister($rootClass->name); $rootTableName = $rootClass->getTableName(); $rootTableStmt = $this->conn->prepare($rootPersister->getInsertSQL()); // Prepare statements for sub tables. $subTableStmts = array(); if ($rootClass !== $this->class) { $subTableStmts[$this->class->getTableName()] = $this->conn->prepare($this->getInsertSQL()); } foreach ($this->class->parentClasses as $parentClassName) { $parentClass = $this->em->getClassMetadata($parentClassName); $parentTableName = $parentClass->getTableName(); if ($parentClass !== $rootClass) { $parentPersister = $this->em->getUnitOfWork()->getEntityPersister($parentClassName); $subTableStmts[$parentTableName] = $this->conn->prepare($parentPersister->getInsertSQL()); } } // Execute all inserts. For each entity: // 1) Insert on root table // 2) Insert on sub tables foreach ($this->queuedInserts as $entity) { $insertData = $this->prepareInsertData($entity); // Execute insert on root table $paramIndex = 1; foreach ($insertData[$rootTableName] as $columnName => $value) { $rootTableStmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]); } $rootTableStmt->execute(); if ($isPostInsertId) { $id = $idGenerator->generate($this->em, $entity); $postInsertIds[$id] = $entity; } else { $id = $this->em->getUnitOfWork()->getEntityIdentifier($entity); } // Execute inserts on subtables. // The order doesn't matter because all child tables link to the root table via FK. foreach ($subTableStmts as $tableName => $stmt) { /** @var \Doctrine\DBAL\Statement $stmt */ $paramIndex = 1; $data = isset($insertData[$tableName]) ? $insertData[$tableName] : array(); 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) { if (!is_array($id) || !isset($id[$columnName])) { $stmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]); } } $stmt->execute(); } } $rootTableStmt->closeCursor(); foreach ($subTableStmts as $stmt) { $stmt->closeCursor(); } if ($this->class->isVersioned) { $this->assignDefaultVersionValue($entity, $id); } $this->queuedInserts = array(); return $postInsertIds; } /** * {@inheritdoc} */ public function update($entity) { $updateData = $this->prepareUpdateData($entity); if ( ! $updateData) { return; } if (($isVersioned = $this->class->isVersioned) === false) { return; } $versionedClass = $this->getVersionedClassMetadata(); $versionedTable = $versionedClass->getTableName(); foreach ($updateData as $tableName => $data) { $tableName = $this->quotedTableMap[$tableName]; $versioned = $isVersioned && $versionedTable === $tableName; $this->updateTable($entity, $tableName, $data, $versioned); } // Make sure the table with the version column is updated even if no columns on that // table were affected. if ($isVersioned) { if ( ! isset($updateData[$versionedTable])) { $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); $this->updateTable($entity, $tableName, array(), true); } $identifiers = $this->em->getUnitOfWork()->getEntityIdentifier($entity); $this->assignDefaultVersionValue($entity, $identifiers); } } /** * {@inheritdoc} */ public function delete($entity) { $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); $id = array_combine($this->class->getIdentifierColumnNames(), $identifier); $this->deleteJoinTableRecords($identifier); // If the database platform supports FKs, just // delete the row from the root table. Cascades do the rest. if ($this->platform->supportsForeignKeyConstraints()) { $rootClass = $this->em->getClassMetadata($this->class->rootEntityName); $rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform); $this->conn->delete($rootTable, $id); return; } // Delete from all tables individually, starting from this class' table up to the root table. $rootTable = $this->quoteStrategy->getTableName($this->class, $this->platform); $this->conn->delete($rootTable, $id); foreach ($this->class->parentClasses as $parentClass) { $parentMetadata = $this->em->getClassMetadata($parentClass); $parentTable = $this->quoteStrategy->getTableName($parentMetadata, $this->platform); $this->conn->delete($parentTable, $id); } } /** * {@inheritdoc} */ protected function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) { $joinSql = ''; $identifierColumn = $this->class->getIdentifierColumnNames(); $baseTableAlias = $this->getSQLTableAlias($this->class->name); // INNER JOIN parent tables foreach ($this->class->parentClasses as $parentClassName) { $conditions = array(); $parentClass = $this->em->getClassMetadata($parentClassName); $tableAlias = $this->getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; foreach ($identifierColumn as $idColumn) { $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } $joinSql .= implode(' AND ', $conditions); } // OUTER JOIN sub tables foreach ($this->class->subClasses as $subClassName) { $conditions = array(); $subClass = $this->em->getClassMetadata($subClassName); $tableAlias = $this->getSQLTableAlias($subClassName); $joinSql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON '; foreach ($identifierColumn as $idColumn) { $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } $joinSql .= implode(' AND ', $conditions); } if ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) { $joinSql .= $this->getSelectManyToManyJoinSQL($assoc); } $conditionSql = ($criteria instanceof Criteria) ? $this->getSelectConditionCriteriaSQL($criteria) : $this->getSelectConditionSQL($criteria, $assoc); // If the current class in the root entity, add the filters if ($filterSql = $this->generateFilterConditionSQL($this->em->getClassMetadata($this->class->rootEntityName), $this->getSQLTableAlias($this->class->rootEntityName))) { $conditionSql .= $conditionSql ? ' AND ' . $filterSql : $filterSql; } $orderBySql = ''; if ($assoc !== null && isset($assoc['orderBy'])) { $orderBy = $assoc['orderBy']; } if ($orderBy) { $orderBySql = $this->getOrderBySQL($orderBy, $baseTableAlias); } $lockSql = ''; switch ($lockMode) { case LockMode::PESSIMISTIC_READ: $lockSql = ' ' . $this->platform->getReadLockSql(); break; case LockMode::PESSIMISTIC_WRITE: $lockSql = ' ' . $this->platform->getWriteLockSql(); break; } $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); $where = $conditionSql != '' ? ' WHERE ' . $conditionSql : ''; $columnList = $this->getSelectColumnsSQL(); $query = 'SELECT ' . $columnList . ' FROM ' . $tableName . ' ' . $baseTableAlias . $joinSql . $where . $orderBySql; return $this->platform->modifyLimitQuery($query, $limit, $offset) . $lockSql; } /** * {@inheritdoc} */ protected function getLockTablesSql($lockMode) { $joinSql = ''; $identifierColumns = $this->class->getIdentifierColumnNames(); $baseTableAlias = $this->getSQLTableAlias($this->class->name); // INNER JOIN parent tables foreach ($this->class->parentClasses as $parentClassName) { $conditions = array(); $tableAlias = $this->getSQLTableAlias($parentClassName); $parentClass = $this->em->getClassMetadata($parentClassName); $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; foreach ($identifierColumns as $idColumn) { $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } $joinSql .= implode(' AND ', $conditions); } return parent::getLockTablesSql($lockMode) . $joinSql; } /** * Ensure this method is never called. This persister overrides getSelectEntitiesSQL directly. * * @return string */ protected function getSelectColumnsSQL() { // Create the column list fragment only once if ($this->selectColumnListSql !== null) { return $this->selectColumnListSql; } $columnList = array(); $this->rsm = new ResultSetMapping(); $discrColumn = $this->class->discriminatorColumn['name']; $baseTableAlias = $this->getSQLTableAlias($this->class->name); $resultColumnName = $this->platform->getSQLResultCasing($discrColumn); $this->rsm->addEntityResult($this->class->name, 'r'); $this->rsm->setDiscriminatorColumn('r', $resultColumnName); $this->rsm->addMetaResult('r', $resultColumnName, $discrColumn); // Add regular columns foreach ($this->class->fieldMappings as $fieldName => $mapping) { $class = isset($mapping['inherited']) ? $this->em->getClassMetadata($mapping['inherited']) : $this->class; $columnList[] = $this->getSelectColumnSQL($fieldName, $class); } // Add foreign key columns foreach ($this->class->associationMappings as $mapping) { if ( ! $mapping['isOwningSide'] || ! ($mapping['type'] & ClassMetadata::TO_ONE)) { continue; } $tableAlias = isset($mapping['inherited']) ? $this->getSQLTableAlias($mapping['inherited']) : $baseTableAlias; foreach ($mapping['targetToSourceKeyColumns'] as $srcColumn) { $className = isset($mapping['inherited']) ? $mapping['inherited'] : $this->class->name; $columnList[] = $this->getSelectJoinColumnSQL($tableAlias, $srcColumn, $className); } } // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#processSQLResult). $tableAlias = ($this->class->rootEntityName == $this->class->name) ? $baseTableAlias : $this->getSQLTableAlias($this->class->rootEntityName); $columnList[] = $tableAlias . '.' . $discrColumn; // sub tables foreach ($this->class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $tableAlias = $this->getSQLTableAlias($subClassName); // Add subclass columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) { continue; } $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass); } // Add join columns (foreign keys) foreach ($subClass->associationMappings as $mapping) { if ( ! $mapping['isOwningSide'] || ! ($mapping['type'] & ClassMetadata::TO_ONE) || isset($mapping['inherited'])) { continue; } foreach ($mapping['targetToSourceKeyColumns'] as $srcColumn) { $className = isset($mapping['inherited']) ? $mapping['inherited'] : $subClass->name; $columnList[] = $this->getSelectJoinColumnSQL($tableAlias, $srcColumn, $className); } } } $this->selectColumnListSql = implode(', ', $columnList); return $this->selectColumnListSql; } /** * {@inheritdoc} */ protected function getInsertColumnList() { // Identifier columns must always come first in the column list of subclasses. $columns = $this->class->parentClasses ? $this->class->getIdentifierColumnNames() : array(); foreach ($this->class->reflFields as $name => $field) { if (isset($this->class->fieldMappings[$name]['inherited']) && ! isset($this->class->fieldMappings[$name]['id']) || isset($this->class->associationMappings[$name]['inherited']) || ($this->class->isVersioned && $this->class->versionField == $name)) { continue; } if (isset($this->class->associationMappings[$name])) { $assoc = $this->class->associationMappings[$name]; if ($assoc['type'] & ClassMetadata::TO_ONE && $assoc['isOwningSide']) { foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { $columns[] = $sourceCol; } } } else if ($this->class->name != $this->class->rootEntityName || ! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] != $name) { $columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform); $this->columnTypes[$name] = $this->class->fieldMappings[$name]['type']; } } // Add discriminator column if it is the topmost class. if ($this->class->name == $this->class->rootEntityName) { $columns[] = $this->class->discriminatorColumn['name']; } return $columns; } /** * {@inheritdoc} */ protected function assignDefaultVersionValue($entity, $id) { $value = $this->fetchVersionValue($this->getVersionedClassMetadata(), $id); $this->class->setFieldValue($entity, $this->class->versionField, $value); } } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php000066400000000000000000000423741257105210500252110ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\UnitOfWork; /** * Persister for many-to-many collections. * * @author Roman Borschel * @author Guilherme Blanco * @author Alexander * @since 2.0 */ class ManyToManyPersister extends AbstractCollectionPersister { /** * {@inheritdoc} * * @override */ protected function getDeleteRowSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); $class = $this->em->getClassMetadata(get_class($coll->getOwner())); $tableName = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } return 'DELETE FROM ' . $tableName . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; } /** * {@inheritdoc} * * @override * * @internal Order of the parameters must be the same as the order of the columns in getDeleteRowSql. */ protected function getDeleteRowSQLParameters(PersistentCollection $coll, $element) { return $this->collectJoinTableColumnParameters($coll, $element); } /** * {@inheritdoc} * * @throws \BadMethodCallException Not used for OneToManyPersister */ protected function getUpdateRowSQL(PersistentCollection $coll) { throw new \BadMethodCallException("Insert Row SQL is not used for ManyToManyPersister"); } /** * {@inheritdoc} * * @override * * @internal Order of the parameters must be the same as the order of the columns in getInsertRowSql. */ protected function getInsertRowSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); $class = $this->em->getClassMetadata(get_class($coll->getOwner())); $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } return 'INSERT INTO ' . $joinTable . ' (' . implode(', ', $columns) . ')' . ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')'; } /** * {@inheritdoc} * * @override * * @internal Order of the parameters must be the same as the order of the columns in getInsertRowSql. */ protected function getInsertRowSQLParameters(PersistentCollection $coll, $element) { return $this->collectJoinTableColumnParameters($coll, $element); } /** * Collects the parameters for inserting/deleting on the join table in the order * of the join table columns as specified in ManyToManyMapping#joinTableColumns. * * @param \Doctrine\ORM\PersistentCollection $coll * @param object $element * * @return array */ private function collectJoinTableColumnParameters(PersistentCollection $coll, $element) { $params = array(); $mapping = $coll->getMapping(); $isComposite = count($mapping['joinTableColumns']) > 2; $identifier1 = $this->uow->getEntityIdentifier($coll->getOwner()); $identifier2 = $this->uow->getEntityIdentifier($element); if ($isComposite) { $class1 = $this->em->getClassMetadata(get_class($coll->getOwner())); $class2 = $coll->getTypeClass(); } foreach ($mapping['joinTableColumns'] as $joinTableColumn) { $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]); if ( ! $isComposite) { $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2); continue; } if ($isRelationToSource) { $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; continue; } $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; } return $params; } /** * {@inheritdoc} * * @override */ protected function getDeleteSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); $class = $this->em->getClassMetadata(get_class($coll->getOwner())); $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } return 'DELETE FROM ' . $joinTable . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; } /** * {@inheritdoc} * * @override * * @internal Order of the parameters must be the same as the order of the columns in getDeleteSql. */ protected function getDeleteSQLParameters(PersistentCollection $coll) { $mapping = $coll->getMapping(); $identifier = $this->uow->getEntityIdentifier($coll->getOwner()); // Optimization for single column identifier if (count($mapping['relationToSourceKeyColumns']) === 1) { return array(reset($identifier)); } // Composite identifier $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $params = array(); foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) { $params[] = isset($sourceClass->fieldNames[$refColumnName]) ? $identifier[$sourceClass->fieldNames[$refColumnName]] : $identifier[$sourceClass->getFieldForColumn($columnName)]; } return $params; } /** * {@inheritdoc} */ public function count(PersistentCollection $coll) { $conditions = array(); $params = array(); $mapping = $coll->getMapping(); $association = $mapping; $class = $this->em->getClassMetadata($mapping['sourceEntity']); $id = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); if ( ! $mapping['isOwningSide']) { $targetEntity = $this->em->getClassMetadata($mapping['targetEntity']); $association = $targetEntity->associationMappings[$mapping['mappedBy']]; } $joinColumns = ( ! $mapping['isOwningSide']) ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; foreach ($joinColumns as $joinColumn) { $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); $referencedName = $joinColumn['referencedColumnName']; $conditions[] = 't.' . $columnName . ' = ?'; $params[] = ($class->containsForeignIdentifier) ? $id[$class->getFieldForColumn($referencedName)] : $id[$class->fieldNames[$referencedName]]; } $joinTableName = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping); if ($filterSql) { $conditions[] = $filterSql; } $sql = 'SELECT COUNT(*)' . ' FROM ' . $joinTableName . ' t' . $joinTargetEntitySQL . ' WHERE ' . implode(' AND ', $conditions); return $this->conn->fetchColumn($sql, $params); } /** * @param \Doctrine\ORM\PersistentCollection $coll * @param int $offset * @param int|null $length * * @return array */ public function slice(PersistentCollection $coll, $offset, $length = null) { $mapping = $coll->getMapping(); return $this->em->getUnitOfWork()->getEntityPersister($mapping['targetEntity'])->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length); } /** * @param \Doctrine\ORM\PersistentCollection $coll * @param object $element * * @return boolean */ public function contains(PersistentCollection $coll, $element) { $uow = $this->em->getUnitOfWork(); // Shortcut for new entities $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); if ($entityState === UnitOfWork::STATE_NEW) { return false; } // Entity is scheduled for inclusion if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, true); $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); return (bool) $this->conn->fetchColumn($sql, $params); } /** * @param \Doctrine\ORM\PersistentCollection $coll * @param object $element * * @return boolean */ public function removeElement(PersistentCollection $coll, $element) { $uow = $this->em->getUnitOfWork(); // shortcut for new entities $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); if ($entityState === UnitOfWork::STATE_NEW) { return false; } // If Entity is scheduled for inclusion, it is not in this collection. // We can assure that because it would have return true before on array check if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, false); $sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); return (bool) $this->conn->executeUpdate($sql, $params); } /** * @param \Doctrine\ORM\PersistentCollection $coll * @param object $element * @param boolean $addFilters Whether the filter SQL should be included or not. * * @return array */ private function getJoinTableRestrictions(PersistentCollection $coll, $element, $addFilters) { $uow = $this->em->getUnitOfWork(); $filterMapping = $coll->getMapping(); $mapping = $filterMapping; if ( ! $mapping['isOwningSide']) { $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']); $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']); $sourceId = $uow->getEntityIdentifier($element); $targetId = $uow->getEntityIdentifier($coll->getOwner()); $mapping = $sourceClass->associationMappings[$mapping['mappedBy']]; } else { $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $sourceId = $uow->getEntityIdentifier($coll->getOwner()); $targetId = $uow->getEntityIdentifier($element); } $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform); $whereClauses = array(); $params = array(); foreach ($mapping['joinTableColumns'] as $joinTableColumn) { $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?'; if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { $params[] = ($targetClass->containsForeignIdentifier) ? $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])] : $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]]; continue; } // relationToSourceKeyColumns $params[] = ($sourceClass->containsForeignIdentifier) ? $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])] : $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]]; } if ($addFilters) { $quotedJoinTable .= ' t'; list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping); if ($filterSql) { $quotedJoinTable .= ' ' . $joinTargetEntitySQL; $whereClauses[] = $filterSql; } } return array($quotedJoinTable, $whereClauses, $params); } /** * Generates the filter SQL for a given mapping. * * This method is not used for actually grabbing the related entities * but when the extra-lazy collection methods are called on a filtered * association. This is why besides the many to many table we also * have to join in the actual entities table leading to additional * JOIN. * * @param array $mapping Array containing mapping information. * * @return string The SQL query part to add to a query. */ public function getFilterSql($mapping) { $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); $filterSql = $this->generateFilterConditionSQL($rootClass, 'te'); if ('' === $filterSql) { return array('', ''); } $conditions = array(); $association = $mapping; if ( ! $mapping['isOwningSide']) { $class = $this->em->getClassMetadata($mapping['targetEntity']); $association = $class->associationMappings[$mapping['mappedBy']]; } // A join is needed if there is filtering on the target entity $tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform); $joinSql = ' JOIN ' . $tableName . ' te' . ' ON'; $joinColumns = $mapping['isOwningSide'] ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; foreach ($joinColumns as $joinColumn) { $joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $refColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); $conditions[] = ' t.' . $joinColumnName . ' = ' . 'te.' . $refColumnName; } $joinSql .= implode(' AND ', $conditions); return array($joinSql, $filterSql); } /** * Generates the filter SQL for a given entity and table alias. * * @param ClassMetadata $targetEntity Metadata of the target entity. * @param string $targetTableAlias The table alias of the joined/selected table. * * @return string The SQL query part to add to a query. */ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { $filterClauses = array(); foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { $filterClauses[] = '(' . $filterExpr . ')'; } } $sql = implode(' AND ', $filterClauses); return $sql ? "(" . $sql . ")" : ""; } } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/OneToManyPersister.php000066400000000000000000000210611257105210500250140ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\UnitOfWork; /** * Persister for one-to-many collections. * * @author Roman Borschel * @author Guilherme Blanco * @author Alexander * @since 2.0 */ class OneToManyPersister extends AbstractCollectionPersister { /** * {@inheritdoc} * * @override */ public function get(PersistentCollection $coll, $index) { $mapping = $coll->getMapping(); $uow = $this->em->getUnitOfWork(); $persister = $uow->getEntityPersister($mapping['targetEntity']); if (!isset($mapping['indexBy'])) { throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections."); } return $persister->load(array($mapping['mappedBy'] => $coll->getOwner(), $mapping['indexBy'] => $index), null, null, array(), 0, 1); } /** * Generates the SQL UPDATE that updates a particular row's foreign * key to null. * * @param \Doctrine\ORM\PersistentCollection $coll * * @return string * * @override */ protected function getDeleteRowSQL(PersistentCollection $coll) { $mapping = $coll->getMapping(); $class = $this->em->getClassMetadata($mapping['targetEntity']); $tableName = $this->quoteStrategy->getTableName($class, $this->platform); $idColumns = $class->getIdentifierColumnNames(); return 'DELETE FROM ' . $tableName . ' WHERE ' . implode('= ? AND ', $idColumns) . ' = ?'; } /** * {@inheritdoc} */ protected function getDeleteRowSQLParameters(PersistentCollection $coll, $element) { return array_values($this->uow->getEntityIdentifier($element)); } /** * {@inheritdoc} * * @throws \BadMethodCallException Not used for OneToManyPersister. */ protected function getInsertRowSQL(PersistentCollection $coll) { throw new \BadMethodCallException("Insert Row SQL is not used for OneToManyPersister"); } /** * {@inheritdoc} * * @throws \BadMethodCallException Not used for OneToManyPersister. */ protected function getInsertRowSQLParameters(PersistentCollection $coll, $element) { throw new \BadMethodCallException("Insert Row SQL is not used for OneToManyPersister"); } /** * {@inheritdoc} * * @throws \BadMethodCallException Not used for OneToManyPersister. */ protected function getUpdateRowSQL(PersistentCollection $coll) { throw new \BadMethodCallException("Update Row SQL is not used for OneToManyPersister"); } /** * {@inheritdoc} * * @throws \BadMethodCallException Not used for OneToManyPersister. */ protected function getDeleteSQL(PersistentCollection $coll) { throw new \BadMethodCallException("Update Row SQL is not used for OneToManyPersister"); } /** * {@inheritdoc} * * @throws \BadMethodCallException Not used for OneToManyPersister. */ protected function getDeleteSQLParameters(PersistentCollection $coll) { throw new \BadMethodCallException("Update Row SQL is not used for OneToManyPersister"); } /** * {@inheritdoc} */ public function count(PersistentCollection $coll) { $mapping = $coll->getMapping(); $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $id = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $whereClauses = array(); $params = array(); $joinColumns = $targetClass->associationMappings[$mapping['mappedBy']]['joinColumns']; foreach ($joinColumns as $joinColumn) { $whereClauses[] = $joinColumn['name'] . ' = ?'; $params[] = ($targetClass->containsForeignIdentifier) ? $id[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])] : $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]]; } $filterTargetClass = $this->em->getClassMetadata($targetClass->rootEntityName); foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { if ($filterExpr = $filter->addFilterConstraint($filterTargetClass, 't')) { $whereClauses[] = '(' . $filterExpr . ')'; } } $sql = 'SELECT count(*)' . ' FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' t' . ' WHERE ' . implode(' AND ', $whereClauses); return $this->conn->fetchColumn($sql, $params); } /** * @param \Doctrine\ORM\PersistentCollection $coll * @param int $offset * @param int|null $length * * @return \Doctrine\Common\Collections\ArrayCollection */ public function slice(PersistentCollection $coll, $offset, $length = null) { $mapping = $coll->getMapping(); $uow = $this->em->getUnitOfWork(); $persister = $uow->getEntityPersister($mapping['targetEntity']); return $persister->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length); } /** * @param \Doctrine\ORM\PersistentCollection $coll * @param object $element * * @return boolean */ public function contains(PersistentCollection $coll, $element) { $mapping = $coll->getMapping(); $uow = $this->em->getUnitOfWork(); // shortcut for new entities $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); if ($entityState === UnitOfWork::STATE_NEW) { return false; } // Entity is scheduled for inclusion if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } $persister = $uow->getEntityPersister($mapping['targetEntity']); // only works with single id identifier entities. Will throw an // exception in Entity Persisters if that is not the case for the // 'mappedBy' field. $id = current( $uow->getEntityIdentifier($coll->getOwner())); return $persister->exists($element, array($mapping['mappedBy'] => $id)); } /** * @param \Doctrine\ORM\PersistentCollection $coll * @param object $element * * @return boolean */ public function removeElement(PersistentCollection $coll, $element) { $mapping = $coll->getMapping(); if ( ! $mapping['orphanRemoval']) { // no-op: this is not the owning side, therefore no operations should be applied return false; } $uow = $this->em->getUnitOfWork(); // shortcut for new entities $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); if ($entityState === UnitOfWork::STATE_NEW) { return false; } // If Entity is scheduled for inclusion, it is not in this collection. // We can assure that because it would have return true before on array check if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } $this ->uow ->getEntityPersister($mapping['targetEntity']) ->delete($element); return true; } } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/PersisterException.php000066400000000000000000000010601257105210500250760ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Common\Collections\Criteria; /** * Persister for entities that participate in a hierarchy mapped with the * SINGLE_TABLE strategy. * * @author Roman Borschel * @author Benjamin Eberlei * @author Alexander * @since 2.0 * @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html */ class SingleTablePersister extends AbstractEntityInheritancePersister { /** * {@inheritdoc} */ protected function getDiscriminatorColumnTableName() { return $this->class->getTableName(); } /** * {@inheritdoc} */ protected function getSelectColumnsSQL() { if ($this->selectColumnListSql !== null) { return $this->selectColumnListSql; } $columnList[] = parent::getSelectColumnsSQL(); $rootClass = $this->em->getClassMetadata($this->class->rootEntityName); $tableAlias = $this->getSQLTableAlias($rootClass->name); // Append discriminator column $discrColumn = $this->class->discriminatorColumn['name']; $columnList[] = $tableAlias . '.' . $discrColumn; $resultColumnName = $this->platform->getSQLResultCasing($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'])) { continue; } $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass); } // Foreign key columns foreach ($subClass->associationMappings as $assoc) { if ( ! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE) || isset($assoc['inherited'])) { continue; } foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { $className = isset($assoc['inherited']) ? $assoc['inherited'] : $this->class->name; $columnList[] = $this->getSelectJoinColumnSQL($tableAlias, $srcColumn, $className); } } } $this->selectColumnListSql = implode(', ', $columnList); return $this->selectColumnListSql; } /** * {@inheritdoc} */ protected function getInsertColumnList() { $columns = parent::getInsertColumnList(); // Add discriminator column to the INSERT SQL $columns[] = $this->class->discriminatorColumn['name']; return $columns; } /** * {@inheritdoc} */ protected function getSQLTableAlias($className, $assocName = '') { return parent::getSQLTableAlias($this->class->rootEntityName, $assocName); } /** * {@inheritdoc} */ protected function getSelectConditionSQL(array $criteria, $assoc = null) { $conditionSql = parent::getSelectConditionSQL($criteria, $assoc); if ($conditionSql) { $conditionSql .= ' AND '; } return $conditionSql . $this->getSelectConditionDiscriminatorValueSQL(); } /** * {@inheritdoc} */ protected function getSelectConditionCriteriaSQL(Criteria $criteria) { $conditionSql = parent::getSelectConditionCriteriaSQL($criteria); if ($conditionSql) { $conditionSql .= ' AND '; } return $conditionSql . $this->getSelectConditionDiscriminatorValueSQL(); } /** * @return string */ protected function getSelectConditionDiscriminatorValueSQL() { $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]); } $values = implode(', ', $values); $discColumn = $this->class->discriminatorColumn['name']; $tableAlias = $this->getSQLTableAlias($this->class->name); return $tableAlias . '.' . $discColumn . ' IN (' . $values . ')'; } /** * {@inheritdoc} */ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { // Ensure that the filters are applied to the root entity of the inheritance tree $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName); // we don't care about the $targetTableAlias, in a STI there is only one table. return parent::generateFilterConditionSQL($targetEntity, $targetTableAlias); } } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/SqlExpressionVisitor.php000066400000000000000000000075601257105210500254510ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Common\Collections\Expr\ExpressionVisitor; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Collections\Expr\Value; use Doctrine\Common\Collections\Expr\CompositeExpression; /** * Visit Expressions and generate SQL WHERE conditions from them. * * @author Benjamin Eberlei * @since 2.3 */ class SqlExpressionVisitor extends ExpressionVisitor { /** * @var \Doctrine\ORM\Persisters\BasicEntityPersister */ private $persister; /** * @var \Doctrine\ORM\Mapping\ClassMetadata */ private $classMetadata; /** * @param \Doctrine\ORM\Persisters\BasicEntityPersister $persister */ public function __construct(BasicEntityPersister $persister, ClassMetadata $classMetadata) { $this->persister = $persister; $this->classMetadata = $classMetadata; } /** * Converts a comparison expression into the target query language output. * * @param \Doctrine\Common\Collections\Expr\Comparison $comparison * * @return mixed */ public function walkComparison(Comparison $comparison) { $field = $comparison->getField(); $value = $comparison->getValue()->getValue(); // shortcut for walkValue() if (isset($this->classMetadata->associationMappings[$field]) && $value !== null && ! is_object($value) && ! in_array($comparison->getOperator(), array(Comparison::IN, Comparison::NIN))) { throw PersisterException::matchingAssocationFieldRequiresObject($this->classMetadata->name, $field); } return $this->persister->getSelectConditionStatementSQL($field, $value, null, $comparison->getOperator()); } /** * Converts a composite expression into the target query language output. * * @param \Doctrine\Common\Collections\Expr\CompositeExpression $expr * * @return mixed * * @throws \RuntimeException */ public function walkCompositeExpression(CompositeExpression $expr) { $expressionList = array(); foreach ($expr->getExpressionList() as $child) { $expressionList[] = $this->dispatch($child); } switch($expr->getType()) { case CompositeExpression::TYPE_AND: return '(' . implode(' AND ', $expressionList) . ')'; case CompositeExpression::TYPE_OR: return '(' . implode(' OR ', $expressionList) . ')'; default: throw new \RuntimeException("Unknown composite " . $expr->getType()); } } /** * Converts a value expression into the target query language part. * * @param \Doctrine\Common\Collections\Expr\Value $value * * @return mixed */ public function walkValue(Value $value) { return '?'; } } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/SqlValueVisitor.php000066400000000000000000000071551257105210500243660ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; use Doctrine\Common\Collections\Expr\ExpressionVisitor; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Collections\Expr\Value; use Doctrine\Common\Collections\Expr\CompositeExpression; /** * Extract the values from a criteria/expression * * @author Benjamin Eberlei */ class SqlValueVisitor extends ExpressionVisitor { /** * @var array */ private $values = array(); /** * @var array */ private $types = array(); /** * Converts a comparison expression into the target query language output. * * @param \Doctrine\Common\Collections\Expr\Comparison $comparison * * @return mixed */ public function walkComparison(Comparison $comparison) { $value = $this->getValueFromComparison($comparison); $field = $comparison->getField(); $operator = $comparison->getOperator(); if (($operator === Comparison::EQ || $operator === Comparison::IS) && $value === null) { return; } else if ($operator === Comparison::NEQ && $value === null) { return; } $this->values[] = $value; $this->types[] = array($field, $value); } /** * Converts a composite expression into the target query language output. * * @param \Doctrine\Common\Collections\Expr\CompositeExpression $expr * * @return mixed */ public function walkCompositeExpression(CompositeExpression $expr) { foreach ($expr->getExpressionList() as $child) { $this->dispatch($child); } } /** * Converts a value expression into the target query language part. * * @param \Doctrine\Common\Collections\Expr\Value $value * * @return mixed */ public function walkValue(Value $value) { return; } /** * Returns the Parameters and Types necessary for matching the last visited expression. * * @return array */ public function getParamsAndTypes() { return array($this->values, $this->types); } /** * Returns the value from a Comparison. In case of a CONTAINS comparison, * the value is wrapped in %-signs, because it will be used in a LIKE clause. * * @param \Doctrine\Common\Collections\Expr\Comparison $comparison * @return mixed */ protected function getValueFromComparison(Comparison $comparison) { $value = $comparison->getValue()->getValue(); return $comparison->getOperator() == Comparison::CONTAINS ? "%{$value}%" : $value; } } doctrine2-2.4.8/lib/Doctrine/ORM/Persisters/UnionSubclassPersister.php000066400000000000000000000020771257105210500257410ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Persisters; class UnionSubclassPersister extends BasicEntityPersister { } doctrine2-2.4.8/lib/Doctrine/ORM/PessimisticLockException.php000066400000000000000000000027611257105210500240710ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * Pessimistic Lock Exception * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Roman Borschel */ class PessimisticLockException extends ORMException { /** * @return PessimisticLockException */ public static function lockFailed() { return new self("The pessimistic lock failed."); } } doctrine2-2.4.8/lib/Doctrine/ORM/Proxy/000077500000000000000000000000001257105210500175075ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Proxy/Autoloader.php000066400000000000000000000022461257105210500223230ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Proxy; use Doctrine\Common\Proxy\Autoloader as BaseAutoloader; /** * @deprecated use \Doctrine\Common\Proxy\Autoloader instead */ class Autoloader extends BaseAutoloader { } doctrine2-2.4.8/lib/Doctrine/ORM/Proxy/Proxy.php000066400000000000000000000022751257105210500213470ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Proxy; use Doctrine\Common\Proxy\Proxy as BaseProxy; /** * Interface for proxy classes. * * @author Roman Borschel * @since 2.0 */ interface Proxy extends BaseProxy { } doctrine2-2.4.8/lib/Doctrine/ORM/Proxy/ProxyFactory.php000066400000000000000000000177211257105210500227010ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Proxy; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\Proxy\AbstractProxyFactory; use Doctrine\Common\Proxy\ProxyDefinition; use Doctrine\Common\Util\ClassUtils; use Doctrine\Common\Proxy\Proxy as BaseProxy; use Doctrine\Common\Proxy\ProxyGenerator; use Doctrine\ORM\ORMInvalidArgumentException; use Doctrine\ORM\Persisters\BasicEntityPersister; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityNotFoundException; /** * This factory is used to create proxy objects for entities at runtime. * * @author Roman Borschel * @author Giorgio Sironi * @author Marco Pivetta * @since 2.0 */ class ProxyFactory extends AbstractProxyFactory { /** * @var \Doctrine\ORM\EntityManager The EntityManager this factory is bound to. */ private $em; /** * @var \Doctrine\ORM\UnitOfWork The UnitOfWork this factory uses to retrieve persisters */ private $uow; /** * @var string */ private $proxyNs; /** * Initializes a new instance of the ProxyFactory class that is * connected to the given EntityManager. * * @param \Doctrine\ORM\EntityManager $em The EntityManager the new factory works for. * @param string $proxyDir The directory to use for the proxy classes. It must exist. * @param string $proxyNs The namespace to use for the proxy classes. * @param boolean $autoGenerate Whether to automatically generate proxy classes. */ public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false) { $proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs); $proxyGenerator->setPlaceholder('baseProxyInterface', 'Doctrine\ORM\Proxy\Proxy'); parent::__construct($proxyGenerator, $em->getMetadataFactory(), $autoGenerate); $this->em = $em; $this->uow = $em->getUnitOfWork(); $this->proxyNs = $proxyNs; } /** * {@inheritDoc} */ protected function skipClass(ClassMetadata $metadata) { /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadataInfo */ return $metadata->isMappedSuperclass || $metadata->getReflectionClass()->isAbstract(); } /** * {@inheritDoc} */ protected function createProxyDefinition($className) { $classMetadata = $this->em->getClassMetadata($className); $entityPersister = $this->uow->getEntityPersister($className); return new ProxyDefinition( ClassUtils::generateProxyClassName($className, $this->proxyNs), $classMetadata->getIdentifierFieldNames(), $classMetadata->getReflectionProperties(), $this->createInitializer($classMetadata, $entityPersister), $this->createCloner($classMetadata, $entityPersister) ); } /** * Creates a closure capable of initializing a proxy * * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata * @param \Doctrine\ORM\Persisters\BasicEntityPersister $entityPersister * * @return \Closure * * @throws \Doctrine\ORM\EntityNotFoundException */ private function createInitializer(ClassMetadata $classMetadata, BasicEntityPersister $entityPersister) { if ($classMetadata->getReflectionClass()->hasMethod('__wakeup')) { return function (BaseProxy $proxy) use ($entityPersister, $classMetadata) { $initializer = $proxy->__getInitializer(); $cloner = $proxy->__getCloner(); $proxy->__setInitializer(null); $proxy->__setCloner(null); if ($proxy->__isInitialized()) { return; } $properties = $proxy->__getLazyProperties(); foreach ($properties as $propertyName => $property) { if (!isset($proxy->$propertyName)) { $proxy->$propertyName = $properties[$propertyName]; } } $proxy->__setInitialized(true); $proxy->__wakeup(); if (null === $entityPersister->load($classMetadata->getIdentifierValues($proxy), $proxy)) { $proxy->__setInitializer($initializer); $proxy->__setCloner($cloner); $proxy->__setInitialized(false); throw new EntityNotFoundException(); } }; } return function (BaseProxy $proxy) use ($entityPersister, $classMetadata) { $initializer = $proxy->__getInitializer(); $cloner = $proxy->__getCloner(); $proxy->__setInitializer(null); $proxy->__setCloner(null); if ($proxy->__isInitialized()) { return; } $properties = $proxy->__getLazyProperties(); foreach ($properties as $propertyName => $property) { if (!isset($proxy->$propertyName)) { $proxy->$propertyName = $properties[$propertyName]; } } $proxy->__setInitialized(true); if (null === $entityPersister->load($classMetadata->getIdentifierValues($proxy), $proxy)) { $proxy->__setInitializer($initializer); $proxy->__setCloner($cloner); $proxy->__setInitialized(false); throw new EntityNotFoundException(); } }; } /** * Creates a closure capable of finalizing state a cloned proxy * * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata * @param \Doctrine\ORM\Persisters\BasicEntityPersister $entityPersister * * @return \Closure * * @throws \Doctrine\ORM\EntityNotFoundException */ private function createCloner(ClassMetadata $classMetadata, BasicEntityPersister $entityPersister) { return function (BaseProxy $proxy) use ($entityPersister, $classMetadata) { if ($proxy->__isInitialized()) { return; } $proxy->__setInitialized(true); $proxy->__setInitializer(null); $class = $entityPersister->getClassMetadata(); $original = $entityPersister->load($classMetadata->getIdentifierValues($proxy)); if (null === $original) { throw new EntityNotFoundException(); } foreach ($class->getReflectionClass()->getProperties() as $reflectionProperty) { $propertyName = $reflectionProperty->getName(); if ($class->hasField($propertyName) || $class->hasAssociation($propertyName)) { $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($proxy, $reflectionProperty->getValue($original)); } } }; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query.php000066400000000000000000000424721257105210500202150ustar00rootroot00000000000000. */ namespace Doctrine\ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\LockMode; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\ParserResult; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\ParameterTypeInferer; /** * A Query object represents a DQL query. * * @since 1.0 * @author Guilherme Blanco * @author Konsta Vesterinen * @author Roman Borschel */ final class Query extends AbstractQuery { /** * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts. */ const STATE_CLEAN = 1; /** * A query object is in state DIRTY when it has DQL parts that have not yet been * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart * is called. */ const STATE_DIRTY = 2; /* Query HINTS */ /** * The refresh hint turns any query into a refresh query with the result that * any local changes in entities are overridden with the fetched values. * * @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. * * @var string * @todo Rename: HINT_OPTIMIZE */ const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad'; /** * The includeMetaColumns query hint causes meta columns like foreign keys and * discriminator columns to be selected and returned as part of the query result. * * This hint does only apply to non-object queries. * * @var string */ const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns'; /** * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and * are iterated and executed after the DQL has been parsed into an AST. * * @var string */ const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers'; /** * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker * and is used for generating the target SQL from any DQL AST tree. * * @var string */ const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker'; //const HINT_READ_ONLY = 'doctrine.readOnly'; /** * @var string */ const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration'; /** * @var string */ const HINT_LOCK_MODE = 'doctrine.lockMode'; /** * The current state of this query. * * @var integer */ private $_state = self::STATE_CLEAN; /** * Cached DQL query. * * @var string */ private $_dql = null; /** * The parser result that holds DQL => SQL information. * * @var \Doctrine\ORM\Query\ParserResult */ private $_parserResult; /** * The first result to return (the "offset"). * * @var integer */ private $_firstResult = null; /** * The maximum number of results to return (the "limit"). * * @var integer */ private $_maxResults = null; /** * The cache driver used for caching queries. * * @var \Doctrine\Common\Cache\Cache|null */ private $_queryCache; /** * Whether or not expire the query cache. * * @var boolean */ private $_expireQueryCache = false; /** * The query cache lifetime. * * @var int */ private $_queryCacheTTL; /** * Whether to use a query cache, if available. Defaults to TRUE. * * @var boolean */ private $_useQueryCache = true; /** * Initializes a new Query instance. * * @param \Doctrine\ORM\EntityManager $entityManager */ /*public function __construct(EntityManager $entityManager) { parent::__construct($entityManager); }*/ /** * Gets the SQL query/queries that correspond to this DQL query. * * @return mixed The built sql query or an array of all sql queries. * * @override */ public function getSQL() { return $this->_parse()->getSQLExecutor()->getSQLStatements(); } /** * Returns the corresponding AST for this DQL query. * * @return \Doctrine\ORM\Query\AST\SelectStatement | * \Doctrine\ORM\Query\AST\UpdateStatement | * \Doctrine\ORM\Query\AST\DeleteStatement */ public function getAST() { $parser = new Parser($this); return $parser->getAST(); } /** * Parses the DQL query, if necessary, and stores the parser result. * * Note: Populates $this->_parserResult as a side-effect. * * @return \Doctrine\ORM\Query\ParserResult */ private function _parse() { // Return previous parser result if the query and the filter collection are both clean if ($this->_state === self::STATE_CLEAN && $this->_em->isFiltersStateClean()) { return $this->_parserResult; } $this->_state = self::STATE_CLEAN; // Check query cache. if ( ! ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver()))) { $parser = new Parser($this); $this->_parserResult = $parser->parse(); return $this->_parserResult; } $hash = $this->_getQueryCacheId(); $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash); if ($cached instanceof ParserResult) { // Cache hit. $this->_parserResult = $cached; return $this->_parserResult; } // Cache miss. $parser = new Parser($this); $this->_parserResult = $parser->parse(); $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL); return $this->_parserResult; } /** * {@inheritdoc} */ protected function _doExecute() { $executor = $this->_parse()->getSqlExecutor(); if ($this->_queryCacheProfile) { $executor->setQueryCacheProfile($this->_queryCacheProfile); } if ($this->_resultSetMapping === null) { $this->_resultSetMapping = $this->_parserResult->getResultSetMapping(); } // Prepare parameters $paramMappings = $this->_parserResult->getParameterMappings(); if (count($paramMappings) != count($this->parameters)) { throw QueryException::invalidParameterNumber(); } list($sqlParams, $types) = $this->processParameterMappings($paramMappings); return $executor->execute($this->_em->getConnection(), $sqlParams, $types); } /** * Processes query parameter mappings. * * @param array $paramMappings * * @return array * * @throws Query\QueryException */ private function processParameterMappings($paramMappings) { $sqlParams = array(); $types = array(); foreach ($this->parameters as $parameter) { $key = $parameter->getName(); $value = $parameter->getValue(); if ( ! isset($paramMappings[$key])) { throw QueryException::unknownParameter($key); } if (isset($this->_resultSetMapping->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) { $value = $value->getMetadataValue($this->_resultSetMapping->metadataParameterMapping[$key]); } $value = $this->processParameterValue($value); $type = ($parameter->getValue() === $value) ? $parameter->getType() : ParameterTypeInferer::inferType($value); foreach ($paramMappings[$key] as $position) { $types[$position] = $type; } $sqlPositions = $paramMappings[$key]; // optimized multi value sql positions away for now, // they are not allowed in DQL anyways. $value = array($value); $countValue = count($value); for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) { $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)]; } } if (count($sqlParams) != count($types)) { throw QueryException::parameterTypeMismatch(); } if ($sqlParams) { ksort($sqlParams); $sqlParams = array_values($sqlParams); ksort($types); $types = array_values($types); } return array($sqlParams, $types); } /** * Defines a cache driver to be used for caching queries. * * @param \Doctrine\Common\Cache\Cache|null $queryCache Cache driver. * * @return Query This query instance. */ public function setQueryCacheDriver($queryCache) { $this->_queryCache = $queryCache; return $this; } /** * Defines whether the query should make use of a query cache, if available. * * @param boolean $bool * * @return Query This query instance. */ public function useQueryCache($bool) { $this->_useQueryCache = $bool; return $this; } /** * Returns the cache driver used for query caching. * * @return \Doctrine\Common\Cache\Cache|null The cache driver used for query caching or NULL, if * this Query does not use query caching. */ public function getQueryCacheDriver() { if ($this->_queryCache) { return $this->_queryCache; } return $this->_em->getConfiguration()->getQueryCacheImpl(); } /** * Defines how long the query cache will be active before expire. * * @param integer $timeToLive How long the cache entry is valid. * * @return Query This query instance. */ public function setQueryCacheLifetime($timeToLive) { if ($timeToLive !== null) { $timeToLive = (int) $timeToLive; } $this->_queryCacheTTL = $timeToLive; return $this; } /** * Retrieves the lifetime of resultset cache. * * @return int */ public function getQueryCacheLifetime() { return $this->_queryCacheTTL; } /** * Defines if the query cache is active or not. * * @param boolean $expire Whether or not to force query cache expiration. * * @return Query This query instance. */ public function expireQueryCache($expire = true) { $this->_expireQueryCache = $expire; return $this; } /** * Retrieves if the query cache is active or not. * * @return bool */ public function getExpireQueryCache() { return $this->_expireQueryCache; } /** * @override */ public function free() { parent::free(); $this->_dql = null; $this->_state = self::STATE_CLEAN; } /** * Sets a DQL query string. * * @param string $dqlQuery DQL Query. * * @return \Doctrine\ORM\AbstractQuery */ public function setDQL($dqlQuery) { if ($dqlQuery !== null) { $this->_dql = $dqlQuery; $this->_state = self::STATE_DIRTY; } return $this; } /** * Returns the DQL query that is represented by this query object. * * @return string DQL query. */ public function getDQL() { return $this->_dql; } /** * Returns the state of this query object * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY. * * @see AbstractQuery::STATE_CLEAN * @see AbstractQuery::STATE_DIRTY * * @return integer The query state. */ public function getState() { return $this->_state; } /** * Method to check if an arbitrary piece of DQL exists * * @param string $dql Arbitrary piece of DQL to check for. * * @return boolean */ public function contains($dql) { return stripos($this->getDQL(), $dql) === false ? false : true; } /** * Sets the position of the first result to retrieve (the "offset"). * * @param integer $firstResult The first result to return. * * @return Query This query object. */ public function setFirstResult($firstResult) { $this->_firstResult = $firstResult; $this->_state = self::STATE_DIRTY; return $this; } /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * Returns NULL if {@link setFirstResult} was not applied to this query. * * @return integer The position of the first result. */ public function getFirstResult() { return $this->_firstResult; } /** * Sets the maximum number of results to retrieve (the "limit"). * * @param integer $maxResults * * @return Query This query object. */ public function setMaxResults($maxResults) { $this->_maxResults = $maxResults; $this->_state = self::STATE_DIRTY; return $this; } /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if {@link setMaxResults} was not applied to this query. * * @return integer Maximum number of results. */ public function getMaxResults() { return $this->_maxResults; } /** * Executes the query and returns an IterableResult that can be used to incrementally * iterated over the result. * * @param ArrayCollection|array|null $parameters The query parameters. * @param integer $hydrationMode The hydration mode to use. * * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT) { $this->setHint(self::HINT_INTERNAL_ITERATION, true); return parent::iterate($parameters, $hydrationMode); } /** * {@inheritdoc} */ public function setHint($name, $value) { $this->_state = self::STATE_DIRTY; return parent::setHint($name, $value); } /** * {@inheritdoc} */ public function setHydrationMode($hydrationMode) { $this->_state = self::STATE_DIRTY; return parent::setHydrationMode($hydrationMode); } /** * Set the lock mode for this Query. * * @see \Doctrine\DBAL\LockMode * * @param int $lockMode * * @return Query * * @throws TransactionRequiredException */ public function setLockMode($lockMode) { if (in_array($lockMode, array(LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE))) { if ( ! $this->_em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } } $this->setHint(self::HINT_LOCK_MODE, $lockMode); return $this; } /** * Get the current lock mode for this query. * * @return int */ public function getLockMode() { $lockMode = $this->getHint(self::HINT_LOCK_MODE); if ( ! $lockMode) { return LockMode::NONE; } return $lockMode; } /** * Generate a cache id for the query cache - reusing the Result-Cache-Id generator. * * The query cache * * @return string */ protected function _getQueryCacheId() { ksort($this->_hints); return md5( $this->getDql() . var_export($this->_hints, true) . ($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') . '&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults . '&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT' ); } /** * Cleanup Query resource when clone is called. * * @return void */ public function __clone() { parent::__clone(); $this->_state = self::STATE_DIRTY; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/000077500000000000000000000000001257105210500174735ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/000077500000000000000000000000001257105210500201225ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ASTException.php000066400000000000000000000025661257105210500231520ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\QueryException; /** * Base exception class for AST exceptions. */ class ASTException extends QueryException { /** * @param Node $node * * @return ASTException */ public static function noDispatchForNode($node) { return new self("Double-dispatch for node " . get_class($node) . " is not supported."); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/AggregateExpression.php000066400000000000000000000042511257105210500246030ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * Description of AggregateExpression. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class AggregateExpression extends Node { /** * @var string */ public $functionName; /** * @var PathExpression|SimpleArithmeticExpression */ public $pathExpression; /** * Some aggregate expressions support distinct, eg COUNT. * * @var bool */ public $isDistinct = false; /** * @param string $functionName * @param PathExpression|SimpleArithmeticExpression $pathExpression * @param bool $isDistinct */ public function __construct($functionName, $pathExpression, $isDistinct) { $this->functionName = $functionName; $this->pathExpression = $pathExpression; $this->isDistinct = $isDistinct; } /** * {@inheritdoc} */ public function dispatch($walker) { return $walker->walkAggregateExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ArithmeticExpression.php000066400000000000000000000036441257105210500250130ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ArithmeticExpression extends Node { /** * @var SimpleArithmeticExpression|null */ public $simpleArithmeticExpression; /** * @var Subselect|null */ public $subselect; /** * @return bool */ public function isSimpleArithmeticExpression() { return (bool) $this->simpleArithmeticExpression; } /** * @return bool */ public function isSubselect() { return (bool) $this->subselect; } /** * {@inheritdoc} */ public function dispatch($walker) { return $walker->walkArithmeticExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ArithmeticFactor.php000066400000000000000000000042361257105210500240700ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ArithmeticFactor extends Node { /** * @var mixed */ public $arithmeticPrimary; /** * NULL represents no sign, TRUE means positive and FALSE means negative sign. * * @var null|boolean */ public $sign; /** * @param mixed $arithmeticPrimary * @param null|bool $sign */ public function __construct($arithmeticPrimary, $sign = null) { $this->arithmeticPrimary = $arithmeticPrimary; $this->sign = $sign; } /** * @return bool */ public function isPositiveSigned() { return $this->sign === true; } /** * @return bool */ public function isNegativeSigned() { return $this->sign === false; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkArithmeticFactor($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ArithmeticTerm.php000066400000000000000000000033211257105210500235530ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ArithmeticTerm extends Node { /** * @var array */ public $arithmeticFactors; /** * @param array $arithmeticFactors */ public function __construct(array $arithmeticFactors) { $this->arithmeticFactors = $arithmeticFactors; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkArithmeticTerm($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/BetweenExpression.php000066400000000000000000000041161257105210500243060ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * Description of BetweenExpression. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class BetweenExpression extends Node { /** * @var ArithmeticExpression */ public $expression; /** * @var ArithmeticExpression */ public $leftBetweenExpression; /** * @var ArithmeticExpression */ public $rightBetweenExpression; /** * @var bool */ public $not; /** * @param ArithmeticExpression $expr * @param ArithmeticExpression $leftExpr * @param ArithmeticExpression $rightExpr */ public function __construct($expr, $leftExpr, $rightExpr) { $this->expression = $expr; $this->leftBetweenExpression = $leftExpr; $this->rightBetweenExpression = $rightExpr; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkBetweenExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/CoalesceExpression.php000066400000000000000000000034521257105210500244350ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" * * @since 2.1 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class CoalesceExpression extends Node { /** * @var array */ public $scalarExpressions = array(); /** * @param array $scalarExpressions */ public function __construct(array $scalarExpressions) { $this->scalarExpressions = $scalarExpressions; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkCoalesceExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/CollectionMemberExpression.php000066400000000000000000000037521257105210500261450ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class CollectionMemberExpression extends Node { public $entityExpression; /** * @var PathExpression */ public $collectionValuedPathExpression; /** * @var bool */ public $not; /** * @param mixed $entityExpr * @param PathExpression $collValuedPathExpr */ public function __construct($entityExpr, $collValuedPathExpr) { $this->entityExpression = $entityExpr; $this->collectionValuedPathExpression = $collValuedPathExpr; } /** * {@inheritdoc} */ public function dispatch($walker) { return $walker->walkCollectionMemberExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ComparisonExpression.php000066400000000000000000000050321257105210500250250ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) | * StringExpression ComparisonOperator (StringExpression | QuantifiedExpression) | * BooleanExpression ("=" | "<>" | "!=") (BooleanExpression | QuantifiedExpression) | * EnumExpression ("=" | "<>" | "!=") (EnumExpression | QuantifiedExpression) | * DatetimeExpression ComparisonOperator (DatetimeExpression | QuantifiedExpression) | * EntityExpression ("=" | "<>") (EntityExpression | QuantifiedExpression) * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ComparisonExpression extends Node { /** * @var Node */ public $leftExpression; /** * @var Node */ public $rightExpression; /** * @var string */ public $operator; /** * @param Node $leftExpr * @param string $operator * @param Node $rightExpr */ public function __construct($leftExpr, $operator, $rightExpr) { $this->leftExpression = $leftExpr; $this->rightExpression = $rightExpr; $this->operator = $operator; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkComparisonExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ConditionalExpression.php000066400000000000000000000033421257105210500251600ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConditionalExpression extends Node { /** * @var array */ public $conditionalTerms = array(); /** * @param array $conditionalTerms */ public function __construct(array $conditionalTerms) { $this->conditionalTerms = $conditionalTerms; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ConditionalFactor.php000066400000000000000000000034301257105210500242350ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * ConditionalFactor ::= ["NOT"] ConditionalPrimary * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConditionalFactor extends Node { /** * @var bool */ public $not = false; /** * @var ConditionalPrimary */ public $conditionalPrimary; /** * @param ConditionalPrimary $conditionalPrimary */ public function __construct($conditionalPrimary) { $this->conditionalPrimary = $conditionalPrimary; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalFactor($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ConditionalPrimary.php000066400000000000000000000037161257105210500244510ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConditionalPrimary extends Node { /** * @var Node|null */ public $simpleConditionalExpression; /** * @var ConditionalExpression|null */ public $conditionalExpression; /** * @return bool */ public function isSimpleConditionalExpression() { return (bool) $this->simpleConditionalExpression; } /** * @return bool */ public function isConditionalExpression() { return (bool) $this->conditionalExpression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalPrimary($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ConditionalTerm.php000066400000000000000000000033361257105210500237330ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConditionalTerm extends Node { /** * @var array */ public $conditionalFactors = array(); /** * @param array $conditionalFactors */ public function __construct(array $conditionalFactors) { $this->conditionalFactors = $conditionalFactors; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalTerm($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/DeleteClause.php000066400000000000000000000034621257105210500231770ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable] * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class DeleteClause extends Node { /** * @var string */ public $abstractSchemaName; /** * @var string */ public $aliasIdentificationVariable; /** * @param string $abstractSchemaName */ public function __construct($abstractSchemaName) { $this->abstractSchemaName = $abstractSchemaName; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkDeleteClause($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/DeleteStatement.php000066400000000000000000000033621257105210500237260ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * DeleteStatement = DeleteClause [WhereClause] * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class DeleteStatement extends Node { /** * @var DeleteClause */ public $deleteClause; /** * @var WhereClause|null */ public $whereClause; /** * @param DeleteClause $deleteClause */ public function __construct($deleteClause) { $this->deleteClause = $deleteClause; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkDeleteStatement($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/EmptyCollectionComparisonExpression.php000066400000000000000000000034571257105210500300710ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EmptyCollectionComparisonExpression extends Node { /** * @var PathExpression */ public $expression; /** * @var bool */ public $not; /** * @param PathExpression $expression */ public function __construct($expression) { $this->expression = $expression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkEmptyCollectionComparisonExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ExistsExpression.php000066400000000000000000000033261257105210500241760ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ExistsExpression extends Node { /** * @var bool */ public $not; /** * @var Subselect */ public $subselect; /** * @param Subselect $subselect */ public function __construct($subselect) { $this->subselect = $subselect; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkExistsExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/FromClause.php000066400000000000000000000035101257105210500226720ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class FromClause extends Node { /** * @var array */ public $identificationVariableDeclarations = array(); /** * @param array $identificationVariableDeclarations */ public function __construct(array $identificationVariableDeclarations) { $this->identificationVariableDeclarations = $identificationVariableDeclarations; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkFromClause($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/000077500000000000000000000000001257105210500220725ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/AbsFunction.php000066400000000000000000000041261257105210500250210ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "ABS" "(" SimpleArithmeticExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class AbsFunction extends FunctionNode { /** * @var \Doctrine\ORM\Query\AST\SimpleArithmeticExpression */ public $simpleArithmeticExpression; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return 'ABS(' . $sqlWalker->walkSimpleArithmeticExpression( $this->simpleArithmeticExpression ) . ')'; } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/BitAndFunction.php000066400000000000000000000041461257105210500254570ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" * * * @link www.doctrine-project.org * @since 2.2 * @author Fabio B. Silva */ class BitAndFunction extends FunctionNode { public $firstArithmetic; public $secondArithmetic; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); return $platform->getBitAndComparisonExpression( $this->firstArithmetic->dispatch($sqlWalker), $this->secondArithmetic->dispatch($sqlWalker) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->secondArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/BitOrFunction.php000066400000000000000000000041431257105210500253320ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" * * * @link www.doctrine-project.org * @since 2.2 * @author Fabio B. Silva */ class BitOrFunction extends FunctionNode { public $firstArithmetic; public $secondArithmetic; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); return $platform->getBitOrComparisonExpression( $this->firstArithmetic->dispatch($sqlWalker), $this->secondArithmetic->dispatch($sqlWalker) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->secondArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/ConcatFunction.php000066400000000000000000000053701257105210500255250ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary }* ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class ConcatFunction extends FunctionNode { public $firstStringPrimary; public $secondStringPrimary; public $concatExpressions = array(); /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); $args = array(); foreach ($this->concatExpressions as $expression) { $args[] = $sqlWalker->walkStringPrimary($expression); } return call_user_func_array(array($platform,'getConcatExpression'), $args); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstStringPrimary = $parser->StringPrimary(); $this->concatExpressions[] = $this->firstStringPrimary; $parser->match(Lexer::T_COMMA); $this->secondStringPrimary = $parser->StringPrimary(); $this->concatExpressions[] = $this->secondStringPrimary; while ($parser->getLexer()->isNextToken(Lexer::T_COMMA)) { $parser->match(Lexer::T_COMMA); $this->concatExpressions[] = $parser->StringPrimary(); } $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/CurrentDateFunction.php000066400000000000000000000035141257105210500265340ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "CURRENT_DATE" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class CurrentDateFunction extends FunctionNode { /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentDateSQL(); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/CurrentTimeFunction.php000066400000000000000000000035141257105210500265550ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "CURRENT_TIME" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class CurrentTimeFunction extends FunctionNode { /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentTimeSQL(); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/CurrentTimestampFunction.php000066400000000000000000000035331257105210500276230ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "CURRENT_TIMESTAMP" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class CurrentTimestampFunction extends FunctionNode { /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentTimestampSQL(); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php000066400000000000000000000056341257105210500256070ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\QueryException; /** * "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Benjamin Eberlei */ class DateAddFunction extends FunctionNode { public $firstDateExpression = null; public $intervalExpression = null; public $unit = null; /** * @override */ public function getSql(SqlWalker $sqlWalker) { switch (strtolower($this->unit->value)) { case 'day': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddDaysExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'month': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddMonthExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); default: throw QueryException::semanticalError( 'DATE_ADD() only supports units of type day and month.' ); } } /** * @override */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstDateExpression = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->intervalExpression = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->unit = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/DateDiffFunction.php000066400000000000000000000040311257105210500257550ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\Parser; /** * "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei */ class DateDiffFunction extends FunctionNode { public $date1; public $date2; /** * @override */ public function getSql(SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getDateDiffExpression( $this->date1->dispatch($sqlWalker), $this->date2->dispatch($sqlWalker) ); } /** * @override */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->date1 = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->date2 = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php000066400000000000000000000043331257105210500256430ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\QueryException; /** * "DATE_ADD(date1, interval, unit)" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Benjamin Eberlei */ class DateSubFunction extends DateAddFunction { /** * @override */ public function getSql(SqlWalker $sqlWalker) { switch (strtolower($this->unit->value)) { case 'day': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubDaysExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'month': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubMonthExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); default: throw QueryException::semanticalError( 'DATE_SUB() only supports units of type day and month.' ); } } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/FunctionNode.php000066400000000000000000000041341257105210500252000ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; /** * Abstract Function Node. * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ abstract class FunctionNode extends Node { /** * @var string */ public $name; /** * @param string $name */ public function __construct($name) { $this->name = $name; } /** * @param \Doctrine\ORM\Query\SqlWalker $sqlWalker * * @return string */ abstract public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker); /** * @param \Doctrine\ORM\Query\SqlWalker $sqlWalker * * @return string */ public function dispatch($sqlWalker) { return $sqlWalker->walkFunction($this); } /** * @param \Doctrine\ORM\Query\Parser $parser * * @return void */ abstract public function parse(\Doctrine\ORM\Query\Parser $parser); } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/IdentityFunction.php000066400000000000000000000101351257105210500261020ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\QueryException; /** * "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")" * * * @link www.doctrine-project.org * @since 2.2 * @author Guilherme Blanco * @author Benjamin Eberlei */ class IdentityFunction extends FunctionNode { /** * @var \Doctrine\ORM\Query\AST\PathExpression */ public $pathExpression; /** * @var string */ public $fieldMapping; /** * {@inheritdoc} */ public function getSql(SqlWalker $sqlWalker) { $platform = $sqlWalker->getEntityManager()->getConnection()->getDatabasePlatform(); $quoteStrategy = $sqlWalker->getEntityManager()->getConfiguration()->getQuoteStrategy(); $dqlAlias = $this->pathExpression->identificationVariable; $assocField = $this->pathExpression->field; $qComp = $sqlWalker->getQueryComponent($dqlAlias); $class = $qComp['metadata']; $assoc = $class->associationMappings[$assocField]; $targetEntity = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']); $joinColumn = reset($assoc['joinColumns']); if ($this->fieldMapping !== null) { if ( ! isset($targetEntity->fieldMappings[$this->fieldMapping])) { throw new QueryException(sprintf('Undefined reference field mapping "%s"', $this->fieldMapping)); } $field = $targetEntity->fieldMappings[$this->fieldMapping]; $joinColumn = null; foreach ($assoc['joinColumns'] as $mapping) { if($mapping['referencedColumnName'] === $field['columnName']) { $joinColumn = $mapping; break; } } if ($joinColumn === null) { throw new QueryException(sprintf('Unable to resolve the reference field mapping "%s"', $this->fieldMapping)); } } //The table with the relation may be a subclass, so get the table name from the association definition $tableName = $sqlWalker->getEntityManager()->getClassMetadata($assoc['sourceEntity'])->getTableName(); $tableAlias = $sqlWalker->getSQLTableAlias($tableName, $dqlAlias); $columnName = $quoteStrategy->getJoinColumnName($joinColumn, $targetEntity, $platform); return $tableAlias . '.' . $columnName; } /** * {@inheritdoc} */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->pathExpression = $parser->SingleValuedAssociationPathExpression(); if ($parser->getLexer()->isNextToken(Lexer::T_COMMA)) { $parser->match(Lexer::T_COMMA); $parser->match(Lexer::T_STRING); $this->fieldMapping = $parser->getLexer()->token['value']; } $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/LengthFunction.php000066400000000000000000000040111257105210500255260ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "LENGTH" "(" StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class LengthFunction extends FunctionNode { public $stringPrimary; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getLengthExpression( $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/LocateFunction.php000066400000000000000000000055311257105210500255240ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class LocateFunction extends FunctionNode { public $firstStringPrimary; public $secondStringPrimary; /** * @var \Doctrine\ORM\Query\AST\SimpleArithmeticExpression|bool */ public $simpleArithmeticExpression = false; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getLocateExpression( $sqlWalker->walkStringPrimary($this->secondStringPrimary), // its the other way around in platform $sqlWalker->walkStringPrimary($this->firstStringPrimary), (($this->simpleArithmeticExpression) ? $sqlWalker->walkSimpleArithmeticExpression($this->simpleArithmeticExpression) : false ) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstStringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_COMMA); $this->secondStringPrimary = $parser->StringPrimary(); $lexer = $parser->getLexer(); if ($lexer->isNextToken(Lexer::T_COMMA)) { $parser->match(Lexer::T_COMMA); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); } $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/LowerFunction.php000066400000000000000000000040061257105210500254010ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "LOWER" "(" StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class LowerFunction extends FunctionNode { public $stringPrimary; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getLowerExpression( $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/ModFunction.php000066400000000000000000000050431257105210500250320ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class ModFunction extends FunctionNode { /** * @var \Doctrine\ORM\Query\AST\SimpleArithmeticExpression */ public $firstSimpleArithmeticExpression; /** * @var \Doctrine\ORM\Query\AST\SimpleArithmeticExpression */ public $secondSimpleArithmeticExpression; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getModExpression( $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression), $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_COMMA); $this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php000066400000000000000000000116721257105210500252320ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "SIZE" "(" CollectionValuedPathExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class SizeFunction extends FunctionNode { /** * @var \Doctrine\ORM\Query\AST\PathExpression */ public $collectionPathExpression; /** * @override * @todo If the collection being counted is already joined, the SQL can be simpler (more efficient). */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $platform = $sqlWalker->getEntityManager()->getConnection()->getDatabasePlatform(); $quoteStrategy = $sqlWalker->getEntityManager()->getConfiguration()->getQuoteStrategy(); $dqlAlias = $this->collectionPathExpression->identificationVariable; $assocField = $this->collectionPathExpression->field; $qComp = $sqlWalker->getQueryComponent($dqlAlias); $class = $qComp['metadata']; $assoc = $class->associationMappings[$assocField]; $sql = 'SELECT COUNT(*) FROM '; if ($assoc['type'] == \Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_MANY) { $targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']); $targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias); $sql .= $quoteStrategy->getTableName($targetClass, $platform) . ' ' . $targetTableAlias . ' WHERE '; $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; $first = true; foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { if ($first) $first = false; else $sql .= ' AND '; $sql .= $targetTableAlias . '.' . $sourceColumn . ' = ' . $sourceTableAlias . '.' . $quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $platform); } } else { // many-to-many $targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']); $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']]; $joinTable = $owningAssoc['joinTable']; // SQL table aliases $joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable['name']); $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias); // join to target table $sql .= $quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $platform) . ' ' . $joinTableAlias . ' WHERE '; $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; $first = true; foreach ($joinColumns as $joinColumn) { if ($first) $first = false; else $sql .= ' AND '; $sourceColumnName = $quoteStrategy->getColumnName( $class->fieldNames[$joinColumn['referencedColumnName']], $class, $platform ); $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $sourceColumnName; } } return '(' . $sql . ')'; } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->collectionPathExpression = $parser->CollectionValuedPathExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/SqrtFunction.php000066400000000000000000000042201257105210500252400ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "SQRT" "(" SimpleArithmeticExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class SqrtFunction extends FunctionNode { /** * @var \Doctrine\ORM\Query\AST\SimpleArithmeticExpression */ public $simpleArithmeticExpression; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getSqrtExpression( $sqlWalker->walkSimpleArithmeticExpression($this->simpleArithmeticExpression) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/SubstringFunction.php000066400000000000000000000061401257105210500262720ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class SubstringFunction extends FunctionNode { public $stringPrimary; /** * @var \Doctrine\ORM\Query\AST\SimpleArithmeticExpression */ public $firstSimpleArithmeticExpression; /** * @var \Doctrine\ORM\Query\AST\SimpleArithmeticExpression|null */ public $secondSimpleArithmeticExpression = null; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $optionalSecondSimpleArithmeticExpression = null; if ($this->secondSimpleArithmeticExpression !== null) { $optionalSecondSimpleArithmeticExpression = $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression); } return $sqlWalker->getConnection()->getDatabasePlatform()->getSubstringExpression( $sqlWalker->walkStringPrimary($this->stringPrimary), $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression), $optionalSecondSimpleArithmeticExpression ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_COMMA); $this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $lexer = $parser->getLexer(); if ($lexer->isNextToken(Lexer::T_COMMA)) { $parser->match(Lexer::T_COMMA); $this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); } $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/TrimFunction.php000066400000000000000000000103161257105210500252250ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class TrimFunction extends FunctionNode { /** * @var boolean */ public $leading; /** * @var boolean */ public $trailing; /** * @var boolean */ public $both; /** * @var boolean */ public $trimChar = false; /** * @var \Doctrine\ORM\Query\AST\Node */ public $stringPrimary; /** * {@inheritdoc} */ public function getSql(SqlWalker $sqlWalker) { $stringPrimary = $sqlWalker->walkStringPrimary($this->stringPrimary); $platform = $sqlWalker->getConnection()->getDatabasePlatform(); $trimMode = $this->getTrimMode(); $trimChar = ($this->trimChar !== false) ? $sqlWalker->getConnection()->quote($this->trimChar) : false; return $platform->getTrimExpression($stringPrimary, $trimMode, $trimChar); } /** * {@inheritdoc} */ public function parse(Parser $parser) { $lexer = $parser->getLexer(); $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->parseTrimMode($parser); if ($lexer->isNextToken(Lexer::T_STRING)) { $parser->match(Lexer::T_STRING); $this->trimChar = $lexer->token['value']; } if ($this->leading || $this->trailing || $this->both || $this->trimChar) { $parser->match(Lexer::T_FROM); } $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } /** * @param \Doctrine\ORM\Query\Parser $parser * * @return integer */ private function getTrimMode() { if ($this->leading) { return AbstractPlatform::TRIM_LEADING; } if ($this->trailing) { return AbstractPlatform::TRIM_TRAILING; } if ($this->both) { return AbstractPlatform::TRIM_BOTH; } return AbstractPlatform::TRIM_UNSPECIFIED; } /** * @param \Doctrine\ORM\Query\Parser $parser * * @return void */ private function parseTrimMode(Parser $parser) { $lexer = $parser->getLexer(); $value = $lexer->lookahead['value']; if (strcasecmp('leading', $value) === 0) { $parser->match(Lexer::T_LEADING); $this->leading = true; return; } if (strcasecmp('trailing', $value) === 0) { $parser->match(Lexer::T_TRAILING); $this->trailing = true; return; } if (strcasecmp('both', $value) === 0) { $parser->match(Lexer::T_BOTH); $this->both = true; return; } } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Functions/UpperFunction.php000066400000000000000000000040061257105210500254040ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "UPPER" "(" StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class UpperFunction extends FunctionNode { public $stringPrimary; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getUpperExpression( $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/GeneralCaseExpression.php000066400000000000000000000037401257105210500250700ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" * * @since 2.2 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GeneralCaseExpression extends Node { /** * @var array */ public $whenClauses = array(); /** * @var mixed */ public $elseScalarExpression = null; /** * @param array $whenClauses * @param mixed $elseScalarExpression */ public function __construct(array $whenClauses, $elseScalarExpression) { $this->whenClauses = $whenClauses; $this->elseScalarExpression = $elseScalarExpression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkGeneralCaseExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/GroupByClause.php000066400000000000000000000032321257105210500233570ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * Description of GroupByClause. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GroupByClause extends Node { /** * @var array */ public $groupByItems = array(); /** * @param array $groupByItems */ public function __construct(array $groupByItems) { $this->groupByItems = $groupByItems; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkGroupByClause($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/HavingClause.php000066400000000000000000000033241257105210500232060ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * Description of HavingClause. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class HavingClause extends Node { /** * @var ConditionalExpression */ public $conditionalExpression; /** * @param ConditionalExpression $conditionalExpression */ public function __construct($conditionalExpression) { $this->conditionalExpression = $conditionalExpression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkHavingClause($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php000066400000000000000000000042361257105210500274050ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class IdentificationVariableDeclaration extends Node { /** * @var RangeVariableDeclaration|null */ public $rangeVariableDeclaration = null; /** * @var IndexBy|null */ public $indexBy = null; /** * @var array */ public $joins = array(); /** * @param RangeVariableDeclaration|null $rangeVariableDecl * @param IndexBy|null $indexBy * @param array $joins */ public function __construct($rangeVariableDecl, $indexBy, array $joins) { $this->rangeVariableDeclaration = $rangeVariableDecl; $this->indexBy = $indexBy; $this->joins = $joins; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkIdentificationVariableDeclaration($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/InExpression.php000066400000000000000000000036321257105210500232650ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (Literal {"," Literal}* | Subselect) ")" * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class InExpression extends Node { /** * @var bool */ public $not; /** * @var ArithmeticExpression */ public $expression; /** * @var array */ public $literals = array(); /** * @var Subselect|null */ public $subselect; /** * @param ArithmeticExpression $expression */ public function __construct($expression) { $this->expression = $expression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkInExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/IndexBy.php000066400000000000000000000034131257105210500221760ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class IndexBy extends Node { /** * @var PathExpression */ public $simpleStateFieldPathExpression = null; /** * @param PathExpression $simpleStateFieldPathExpression */ public function __construct($simpleStateFieldPathExpression) { $this->simpleStateFieldPathExpression = $simpleStateFieldPathExpression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkIndexBy($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/InputParameter.php000066400000000000000000000036531257105210500236020ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * Description of InputParameter. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class InputParameter extends Node { /** * @var bool */ public $isNamed; /** * @var string */ public $name; /** * @param string $value * * @throws \Doctrine\ORM\Query\QueryException */ public function __construct($value) { if (strlen($value) == 1) { throw \Doctrine\ORM\Query\QueryException::invalidParameterFormat($value); } $param = substr($value, 1); $this->isNamed = ! is_numeric($param); $this->name = $param; } /** * {@inheritdoc} */ public function dispatch($walker) { return $walker->walkInputParameter($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/InstanceOfExpression.php000066400000000000000000000037251257105210500247530ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") * InstanceOfParameter ::= AbstractSchemaName | InputParameter * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class InstanceOfExpression extends Node { /** * @var bool */ public $not; /** * @var string */ public $identificationVariable; /** * @var array */ public $value; /** * @param string $identVariable */ public function __construct($identVariable) { $this->identificationVariable = $identVariable; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkInstanceOfExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Join.php000066400000000000000000000042641257105210500215400ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression] * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Join extends Node { const JOIN_TYPE_LEFT = 1; const JOIN_TYPE_LEFTOUTER = 2; const JOIN_TYPE_INNER = 3; /** * @var int */ public $joinType = self::JOIN_TYPE_INNER; /** * @var Node|null */ public $joinAssociationDeclaration = null; /** * @var ConditionalExpression|null */ public $conditionalExpression = null; /** * @param int $joinType * @param Node $joinAssociationDeclaration */ public function __construct($joinType, $joinAssociationDeclaration) { $this->joinType = $joinType; $this->joinAssociationDeclaration = $joinAssociationDeclaration; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkJoin($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/JoinAssociationDeclaration.php000066400000000000000000000043011257105210500260730ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable * * @link www.doctrine-project.org * @since 2.3 * @author Guilherme Blanco */ class JoinAssociationDeclaration extends Node { /** * @var JoinAssociationPathExpression */ public $joinAssociationPathExpression; /** * @var string */ public $aliasIdentificationVariable; /** * @var IndexBy|null */ public $indexBy; /** * @param JoinAssociationPathExpression $joinAssociationPathExpression * @param string $aliasIdentificationVariable * @param IndexBy|null $indexBy */ public function __construct($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy) { $this->joinAssociationPathExpression = $joinAssociationPathExpression; $this->aliasIdentificationVariable = $aliasIdentificationVariable; $this->indexBy = $indexBy; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkJoinAssociationDeclaration($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/JoinAssociationPathExpression.php000066400000000000000000000037471257105210500266370ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * JoinAssociationPathExpression ::= IdentificationVariable "." (SingleValuedAssociationField | CollectionValuedAssociationField) * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class JoinAssociationPathExpression extends Node { /** * @var string */ public $identificationVariable; /** * @var string */ public $associationField; /** * @param string $identificationVariable * @param string $associationField */ public function __construct($identificationVariable, $associationField) { $this->identificationVariable = $identificationVariable; $this->associationField = $associationField; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkPathExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/JoinClassPathExpression.php000066400000000000000000000036201257105210500254160ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * JoinClassPathExpression ::= AbstractSchemaName ["AS"] AliasIdentificationVariable * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.3 * @author Alexander */ class JoinClassPathExpression extends Node { /** * @var mixed */ public $abstractSchemaName; /** * @var mixed */ public $aliasIdentificationVariable; /** * @param mixed $abstractSchemaName * @param mixed $aliasIdentificationVar */ public function __construct($abstractSchemaName, $aliasIdentificationVar) { $this->abstractSchemaName = $abstractSchemaName; $this->aliasIdentificationVariable = $aliasIdentificationVar; } /** * {@inheritdoc} */ public function dispatch($walker) { return $walker->walkJoinPathExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/LikeExpression.php000066400000000000000000000041401257105210500235760ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class LikeExpression extends Node { /** * @var bool */ public $not; /** * @var Node */ public $stringExpression; /** * @var InputParameter */ public $stringPattern; /** * @var Literal|null */ public $escapeChar; /** * @param Node $stringExpression * @param InputParameter $stringPattern * @param Literal|null $escapeChar */ public function __construct($stringExpression, $stringPattern, $escapeChar = null) { $this->stringExpression = $stringExpression; $this->stringPattern = $stringPattern; $this->escapeChar = $escapeChar; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkLikeExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Literal.php000066400000000000000000000030151257105210500222260ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; class Literal extends Node { const STRING = 1; const BOOLEAN = 2; const NUMERIC = 3; /** * @var int */ public $type; /** * @var mixed */ public $value; /** * @param int $type * @param mixed $value */ public function __construct($type, $value) { $this->type = $type; $this->value = $value; } /** * {@inheritdoc} */ public function dispatch($walker) { return $walker->walkLiteral($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/NewObjectExpression.php000066400000000000000000000033201257105210500245710ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")" * * @link www.doctrine-project.org * @since 2.4 * @author Fabio B. Silva */ class NewObjectExpression extends Node { /** * @var string */ public $className; /** * @var array */ public $args; /** * @param string $className * @param array $args */ public function __construct($className, array $args) { $this->className = $className; $this->args = $args; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkNewObject($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Node.php000066400000000000000000000060511257105210500215220ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * Abstract class of an AST node. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ abstract class Node { /** * Double-dispatch method, supposed to dispatch back to the walker. * * Implementation is not mandatory for all nodes. * * @param \Doctrine\ORM\Query\SqlWalker $walker * * @return string * * @throws ASTException */ public function dispatch($walker) { throw ASTException::noDispatchForNode($this); } /** * Dumps the AST Node into a string representation for information purpose only. * * @return string */ public function __toString() { return $this->dump($this); } /** * @param object $obj * * @return string */ public function dump($obj) { static $ident = 0; $str = ''; if ($obj instanceof Node) { $str .= get_class($obj) . '(' . PHP_EOL; $props = get_object_vars($obj); foreach ($props as $name => $prop) { $ident += 4; $str .= str_repeat(' ', $ident) . '"' . $name . '": ' . $this->dump($prop) . ',' . PHP_EOL; $ident -= 4; } $str .= str_repeat(' ', $ident) . ')'; } else if (is_array($obj)) { $ident += 4; $str .= 'array('; $some = false; foreach ($obj as $k => $v) { $str .= PHP_EOL . str_repeat(' ', $ident) . '"' . $k . '" => ' . $this->dump($v) . ','; $some = true; } $ident -= 4; $str .= ($some ? PHP_EOL . str_repeat(' ', $ident) : '') . ')'; } else if (is_object($obj)) { $str .= 'instanceof(' . get_class($obj) . ')'; } else { $str .= var_export($obj, true); } return $str; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/NullComparisonExpression.php000066400000000000000000000034101257105210500256560ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class NullComparisonExpression extends Node { /** * @var bool */ public $not; /** * @var Node */ public $expression; /** * @param Node $expression */ public function __construct($expression) { $this->expression = $expression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkNullComparisonExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/NullIfExpression.php000066400000000000000000000036641257105210500241150ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" * * @since 2.1 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class NullIfExpression extends Node { /** * @var mixed */ public $firstExpression; /** * @var mixed */ public $secondExpression; /** * @param mixed $firstExpression * @param mixed $secondExpression */ public function __construct($firstExpression, $secondExpression) { $this->firstExpression = $firstExpression; $this->secondExpression = $secondExpression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkNullIfExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/OrderByClause.php000066400000000000000000000032721257105210500233420ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class OrderByClause extends Node { /** * @var array */ public $orderByItems = array(); /** * @param array $orderByItems */ public function __construct(array $orderByItems) { $this->orderByItems = $orderByItems; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkOrderByClause($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/OrderByItem.php000066400000000000000000000037431257105210500230270ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class OrderByItem extends Node { /** * @var mixed */ public $expression; /** * @var string */ public $type; /** * @param mixed $expression */ public function __construct($expression) { $this->expression = $expression; } /** * @return bool */ public function isAsc() { return strtoupper($this->type) == 'ASC'; } /** * @return bool */ public function isDesc() { return strtoupper($this->type) == 'DESC'; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkOrderByItem($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/ParenthesisExpression.php000066400000000000000000000031101257105210500251730ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * ParenthesisExpression ::= "(" ArithmeticPrimary ")" * * @author Fabio B. Silva * @since 2.4 */ class ParenthesisExpression extends Node { /** * @var \Doctrine\ORM\Query\AST\Node */ public $expression; /** * @param \Doctrine\ORM\Query\AST\Node $expression */ public function __construct(Node $expression) { $this->expression = $expression; } /** * {@inheritdoc} */ public function dispatch($walker) { return $walker->walkParenthesisExpression($this); } }doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/PartialObjectExpression.php000066400000000000000000000027531257105210500254450ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; class PartialObjectExpression extends Node { /** * @var string */ public $identificationVariable; /** * @var array */ public $partialFieldSet; /** * @param string $identificationVariable * @param array $partialFieldSet */ public function __construct($identificationVariable, array $partialFieldSet) { $this->identificationVariable = $identificationVariable; $this->partialFieldSet = $partialFieldSet; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/PathExpression.php000066400000000000000000000053421257105210500236130ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression * StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField * StateField ::= {EmbeddedClassStateField "."}* SimpleStateField * SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField * * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class PathExpression extends Node { const TYPE_COLLECTION_VALUED_ASSOCIATION = 2; const TYPE_SINGLE_VALUED_ASSOCIATION = 4; const TYPE_STATE_FIELD = 8; /** * @var int */ public $type; /** * @var int */ public $expectedType; /** * @var string */ public $identificationVariable; /** * @var string|null */ public $field; /** * @param int $expectedType * @param string $identificationVariable * @param string|null $field */ public function __construct($expectedType, $identificationVariable, $field = null) { $this->expectedType = $expectedType; $this->identificationVariable = $identificationVariable; $this->field = $field; } /** * {@inheritdoc} */ public function dispatch($walker) { return $walker->walkPathExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/QuantifiedExpression.php000066400000000000000000000041501257105210500250040ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class QuantifiedExpression extends Node { /** * @var string */ public $type; /** * @var Subselect */ public $subselect; /** * @param Subselect $subselect */ public function __construct($subselect) { $this->subselect = $subselect; } /** * @return bool */ public function isAll() { return strtoupper($this->type) == 'ALL'; } /** * @return bool */ public function isAny() { return strtoupper($this->type) == 'ANY'; } /** * @return bool */ public function isSome() { return strtoupper($this->type) == 'SOME'; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkQuantifiedExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php000066400000000000000000000041601257105210500255040ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class RangeVariableDeclaration extends Node { /** * @var string */ public $abstractSchemaName; /** * @var string */ public $aliasIdentificationVariable; /** * @var boolean */ public $isRoot; /** * @param string $abstractSchemaName * @param string $aliasIdentificationVar * @param boolean $isRoot */ public function __construct($abstractSchemaName, $aliasIdentificationVar, $isRoot = true) { $this->abstractSchemaName = $abstractSchemaName; $this->aliasIdentificationVariable = $aliasIdentificationVar; $this->isRoot = $isRoot; } /** * {@inheritdoc} */ public function dispatch($walker) { return $walker->walkRangeVariableDeclaration($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/SelectClause.php000066400000000000000000000035601257105210500232130ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * SelectClause = "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SelectClause extends Node { /** * @var bool */ public $isDistinct; /** * @var array */ public $selectExpressions = array(); /** * @param array $selectExpressions * @param bool $isDistinct */ public function __construct(array $selectExpressions, $isDistinct) { $this->isDistinct = $isDistinct; $this->selectExpressions = $selectExpressions; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSelectClause($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/SelectExpression.php000066400000000000000000000044221257105210500241340ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression | * (AggregateExpression | "(" Subselect ")") [["AS"] ["HIDDEN"] FieldAliasIdentificationVariable] * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SelectExpression extends Node { /** * @var mixed */ public $expression; /** * @var string|null */ public $fieldIdentificationVariable; /** * @var bool */ public $hiddenAliasResultVariable; /** * @param mixed $expression * @param string|null $fieldIdentificationVariable * @param bool $hiddenAliasResultVariable */ public function __construct($expression, $fieldIdentificationVariable, $hiddenAliasResultVariable = false) { $this->expression = $expression; $this->fieldIdentificationVariable = $fieldIdentificationVariable; $this->hiddenAliasResultVariable = $hiddenAliasResultVariable; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSelectExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/SelectStatement.php000066400000000000000000000042501257105210500237400ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * SelectStatement = SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SelectStatement extends Node { /** * @var SelectClause */ public $selectClause; /** * @var FromClause */ public $fromClause; /** * @var WhereClause|null */ public $whereClause; /** * @var GroupByClause|null */ public $groupByClause; /** * @var HavingClause|null */ public $havingClause; /** * @var OrderByClause|null */ public $orderByClause; /** * @param SelectClause $selectClause * @param FromClause $fromClause */ public function __construct($selectClause, $fromClause) { $this->selectClause = $selectClause; $this->fromClause = $fromClause; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSelectStatement($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/SimpleArithmeticExpression.php000066400000000000000000000033611257105210500261610ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SimpleArithmeticExpression extends Node { /** * @var array */ public $arithmeticTerms = array(); /** * @param array $arithmeticTerms */ public function __construct(array $arithmeticTerms) { $this->arithmeticTerms = $arithmeticTerms; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleArithmeticExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/SimpleCaseExpression.php000066400000000000000000000043241257105210500247430ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" * * @since 2.2 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SimpleCaseExpression extends Node { /** * @var PathExpression */ public $caseOperand = null; /** * @var array */ public $simpleWhenClauses = array(); /** * @var mixed */ public $elseScalarExpression = null; /** * @param PathExpression $caseOperand * @param array $simpleWhenClauses * @param mixed $elseScalarExpression */ public function __construct($caseOperand, array $simpleWhenClauses, $elseScalarExpression) { $this->caseOperand = $caseOperand; $this->simpleWhenClauses = $simpleWhenClauses; $this->elseScalarExpression = $elseScalarExpression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleCaseExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/SimpleSelectClause.php000066400000000000000000000036701257105210500243670ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SimpleSelectClause extends Node { /** * @var bool */ public $isDistinct = false; /** * @var SimpleSelectExpression */ public $simpleSelectExpression; /** * @param SimpleSelectExpression $simpleSelectExpression * @param bool $isDistinct */ public function __construct($simpleSelectExpression, $isDistinct) { $this->simpleSelectExpression = $simpleSelectExpression; $this->isDistinct = $isDistinct; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleSelectClause($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/SimpleSelectExpression.php000066400000000000000000000035521257105210500253110ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable * | (AggregateExpression [["AS"] FieldAliasIdentificationVariable]) * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SimpleSelectExpression extends Node { /** * @var Node */ public $expression; /** * @var string */ public $fieldIdentificationVariable; /** * @param Node $expression */ public function __construct($expression) { $this->expression = $expression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleSelectExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/SimpleWhenClause.php000066400000000000000000000037531257105210500240530ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression * * @since 2.2 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SimpleWhenClause extends Node { /** * @var mixed */ public $caseScalarExpression = null; /** * @var mixed */ public $thenScalarExpression = null; /** * @param mixed $caseScalarExpression * @param mixed $thenScalarExpression */ public function __construct($caseScalarExpression, $thenScalarExpression) { $this->caseScalarExpression = $caseScalarExpression; $this->thenScalarExpression = $thenScalarExpression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkWhenClauseExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/Subselect.php000066400000000000000000000044171257105210500225720ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Subselect extends Node { /** * @var SimpleSelectClause */ public $simpleSelectClause; /** * @var SubselectFromClause */ public $subselectFromClause; /** * @var WhereClause|null */ public $whereClause; /** * @var GroupByClause|null */ public $groupByClause; /** * @var HavingClause|null */ public $havingClause; /** * @var OrderByClause|null */ public $orderByClause; /** * @param SimpleSelectClause $simpleSelectClause * @param SubselectFromClause $subselectFromClause */ public function __construct($simpleSelectClause, $subselectFromClause) { $this->simpleSelectClause = $simpleSelectClause; $this->subselectFromClause = $subselectFromClause; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSubselect($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/SubselectFromClause.php000066400000000000000000000035621257105210500245530ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SubselectFromClause extends Node { /** * @var array */ public $identificationVariableDeclarations = array(); /** * @param array $identificationVariableDeclarations */ public function __construct(array $identificationVariableDeclarations) { $this->identificationVariableDeclarations = $identificationVariableDeclarations; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSubselectFromClause($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/UpdateClause.php000066400000000000000000000037631257105210500232230ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}* * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class UpdateClause extends Node { /** * @var string */ public $abstractSchemaName; /** * @var string */ public $aliasIdentificationVariable; /** * @var array */ public $updateItems = array(); /** * @param string $abstractSchemaName * @param array $updateItems */ public function __construct($abstractSchemaName, array $updateItems) { $this->abstractSchemaName = $abstractSchemaName; $this->updateItems = $updateItems; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkUpdateClause($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/UpdateItem.php000066400000000000000000000041541257105210500227000ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * UpdateItem ::= [IdentificationVariable "."] {StateField | SingleValuedAssociationField} "=" NewValue * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary | * EnumPrimary | SimpleEntityExpression | "NULL" * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class UpdateItem extends Node { /** * @var PathExpression */ public $pathExpression; /** * @var InputParameter|ArithmeticExpression|null */ public $newValue; /** * @param PathExpression $pathExpression * @param InputParameter|ArithmeticExpression|null $newValue */ public function __construct($pathExpression, $newValue) { $this->pathExpression = $pathExpression; $this->newValue = $newValue; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkUpdateItem($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/UpdateStatement.php000066400000000000000000000033621257105210500237460ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * UpdateStatement = UpdateClause [WhereClause] * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class UpdateStatement extends Node { /** * @var UpdateClause */ public $updateClause; /** * @var WhereClause|null */ public $whereClause; /** * @param UpdateClause $updateClause */ public function __construct($updateClause) { $this->updateClause = $updateClause; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkUpdateStatement($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/WhenClause.php000066400000000000000000000040431257105210500226720ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression * * @since 2.2 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class WhenClause extends Node { /** * @var ConditionalExpression */ public $caseConditionExpression = null; /** * @var mixed */ public $thenScalarExpression = null; /** * @param ConditionalExpression $caseConditionExpression * @param mixed $thenScalarExpression */ public function __construct($caseConditionExpression, $thenScalarExpression) { $this->caseConditionExpression = $caseConditionExpression; $this->thenScalarExpression = $thenScalarExpression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkWhenClauseExpression($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/AST/WhereClause.php000066400000000000000000000033431257105210500230450ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\AST; /** * WhereClause ::= "WHERE" ConditionalExpression * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class WhereClause extends Node { /** * @var ConditionalExpression */ public $conditionalExpression; /** * @param ConditionalExpression $conditionalExpression */ public function __construct($conditionalExpression) { $this->conditionalExpression = $conditionalExpression; } /** * {@inheritdoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkWhereClause($this); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Exec/000077500000000000000000000000001257105210500203575ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php000066400000000000000000000045641257105210500250430ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Cache\QueryCacheProfile; /** * Base class for SQL statement executors. * * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.doctrine-project.org * @since 2.0 * @todo Rename: AbstractSQLExecutor */ abstract class AbstractSqlExecutor { /** * @var array */ protected $_sqlStatements; /** * @var QueryCacheProfile */ protected $queryCacheProfile; /** * Gets the SQL statements that are executed by the executor. * * @return array All the SQL update statements. */ public function getSqlStatements() { return $this->_sqlStatements; } /** * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp * * @return void */ public function setQueryCacheProfile(QueryCacheProfile $qcp) { $this->queryCacheProfile = $qcp; } /** * Executes all sql statements. * * @param Connection $conn The database connection that is used to execute the queries. * @param array $params The parameters. * @param array $types The parameter types. * * @return \Doctrine\DBAL\Driver\Statement */ abstract public function execute(Connection $conn, array $params, array $types); } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php000066400000000000000000000134301257105210500257750ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\ORM\Query\AST; /** * Executes the SQL statements for bulk DQL DELETE statements on classes in * Class Table Inheritance (JOINED). * * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.doctrine-project.org * @since 2.0 */ class MultiTableDeleteExecutor extends AbstractSqlExecutor { /** * @var string */ private $_createTempTableSql; /** * @var string */ private $_dropTempTableSql; /** * @var string */ private $_insertSql; /** * Initializes a new MultiTableDeleteExecutor. * * @param \Doctrine\ORM\Query\AST\Node $AST The root AST node of the DQL query. * @param \Doctrine\ORM\Query\SqlWalker $sqlWalker The walker used for SQL generation from the AST. * * @internal Any SQL construction and preparation takes place in the constructor for * best performance. With a query cache the executor will be cached. */ public function __construct(AST\Node $AST, $sqlWalker) { $em = $sqlWalker->getEntityManager(); $conn = $em->getConnection(); $platform = $conn->getDatabasePlatform(); $quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); $primaryClass = $em->getClassMetadata($AST->deleteClause->abstractSchemaName); $primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable; $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $primaryDqlAlias); $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' . ' SELECT t0.' . implode(', t0.', $idColumnNames); $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $primaryDqlAlias); $fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array()))); $this->_insertSql .= $sqlWalker->walkFromClause($fromClause); // Append WHERE clause, if there is one. if ($AST->whereClause) { $this->_insertSql .= $sqlWalker->walkWhereClause($AST->whereClause); } // 2. Create ID subselect statement used in DELETE ... WHERE ... IN (subselect) $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable; // 3. Create and store DELETE statements $classNames = array_merge($primaryClass->parentClasses, array($primaryClass->name), $primaryClass->subClasses); foreach (array_reverse($classNames) as $className) { $tableName = $quoteStrategy->getTableName($em->getClassMetadata($className), $platform); $this->_sqlStatements[] = 'DELETE FROM ' . $tableName . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')'; } // 4. Store DDL for temporary identifier table. $columnDefinitions = array(); foreach ($idColumnNames as $idColumnName) { $columnDefinitions[$idColumnName] = array( 'notnull' => true, 'type' => \Doctrine\DBAL\Types\Type::getType($rootClass->getTypeOfColumn($idColumnName)) ); } $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); } /** * {@inheritDoc} */ public function execute(Connection $conn, array $params, array $types) { $numDeleted = 0; // Create temporary id table $conn->executeUpdate($this->_createTempTableSql); try { // Insert identifiers $numDeleted = $conn->executeUpdate($this->_insertSql, $params, $types); // Execute DELETE statements foreach ($this->_sqlStatements as $sql) { $conn->executeUpdate($sql); } } catch (\Exception $exception) { // FAILURE! Drop temporary table to avoid possible collisions $conn->executeUpdate($this->_dropTempTableSql); // Re-throw exception throw $exception; } // Drop temporary table $conn->executeUpdate($this->_dropTempTableSql); return $numDeleted; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php000066400000000000000000000173401257105210500260210ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Query\ParameterTypeInferer; use Doctrine\ORM\Query\AST; /** * Executes the SQL statements for bulk DQL UPDATE statements on classes in * Class Table Inheritance (JOINED). * * @author Roman Borschel * @since 2.0 */ class MultiTableUpdateExecutor extends AbstractSqlExecutor { /** * @var string */ private $_createTempTableSql; /** * @var string */ private $_dropTempTableSql; /** * @var string */ private $_insertSql; /** * @var array */ private $_sqlParameters = array(); /** * @var int */ private $_numParametersInUpdateClause = 0; /** * Initializes a new MultiTableUpdateExecutor. * * @param \Doctrine\ORM\Query\AST\Node $AST The root AST node of the DQL query. * @param \Doctrine\ORM\Query\SqlWalker $sqlWalker The walker used for SQL generation from the AST. * * @internal Any SQL construction and preparation takes place in the constructor for * best performance. With a query cache the executor will be cached. */ public function __construct(AST\Node $AST, $sqlWalker) { $em = $sqlWalker->getEntityManager(); $conn = $em->getConnection(); $platform = $conn->getDatabasePlatform(); $quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); $updateClause = $AST->updateClause; $primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName); $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); $updateItems = $updateClause->updateItems; $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $updateClause->aliasIdentificationVariable); $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' . ' SELECT t0.' . implode(', t0.', $idColumnNames); $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable); $fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array()))); $this->_insertSql .= $sqlWalker->walkFromClause($fromClause); // 2. Create ID subselect statement used in UPDATE ... WHERE ... IN (subselect) $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable; // 3. Create and store UPDATE statements $classNames = array_merge($primaryClass->parentClasses, array($primaryClass->name), $primaryClass->subClasses); $i = -1; foreach (array_reverse($classNames) as $className) { $affected = false; $class = $em->getClassMetadata($className); $updateSql = 'UPDATE ' . $quoteStrategy->getTableName($class, $platform) . ' SET '; foreach ($updateItems as $updateItem) { $field = $updateItem->pathExpression->field; if (isset($class->fieldMappings[$field]) && ! isset($class->fieldMappings[$field]['inherited']) || isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]['inherited'])) { $newValue = $updateItem->newValue; if ( ! $affected) { $affected = true; ++$i; } else { $updateSql .= ', '; } $updateSql .= $sqlWalker->walkUpdateItem($updateItem); if ($newValue instanceof AST\InputParameter) { $this->_sqlParameters[$i][] = $newValue->name; ++$this->_numParametersInUpdateClause; } } } if ($affected) { $this->_sqlStatements[$i] = $updateSql . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')'; } } // Append WHERE clause to insertSql, if there is one. if ($AST->whereClause) { $this->_insertSql .= $sqlWalker->walkWhereClause($AST->whereClause); } // 4. Store DDL for temporary identifier table. $columnDefinitions = array(); foreach ($idColumnNames as $idColumnName) { $columnDefinitions[$idColumnName] = array( 'notnull' => true, 'type' => Type::getType($rootClass->getTypeOfColumn($idColumnName)) ); } $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); } /** * {@inheritDoc} */ public function execute(Connection $conn, array $params, array $types) { $numUpdated = 0; // Create temporary id table $conn->executeUpdate($this->_createTempTableSql); try { // Insert identifiers. Parameters from the update clause are cut off. $numUpdated = $conn->executeUpdate( $this->_insertSql, array_slice($params, $this->_numParametersInUpdateClause), array_slice($types, $this->_numParametersInUpdateClause) ); // Execute UPDATE statements foreach ($this->_sqlStatements as $key => $statement) { $paramValues = array(); $paramTypes = array(); if (isset($this->_sqlParameters[$key])) { foreach ($this->_sqlParameters[$key] as $parameterKey => $parameterName) { $paramValues[] = $params[$parameterKey]; $paramTypes[] = isset($types[$parameterKey]) ? $types[$parameterKey] : ParameterTypeInferer::inferType($params[$parameterKey]); } } $conn->executeUpdate($statement, $paramValues, $paramTypes); } } catch (\Exception $exception) { // FAILURE! Drop temporary table to avoid possible collisions $conn->executeUpdate($this->_dropTempTableSql); // Re-throw exception throw $exception; } // Drop temporary table $conn->executeUpdate($this->_dropTempTableSql); return $numUpdated; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php000066400000000000000000000037161257105210500251770ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\ORM\Query\AST\SelectStatement; use Doctrine\ORM\Query\SqlWalker; /** * Executor that executes the SQL statement for simple DQL SELECT statements. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Roman Borschel * @link www.doctrine-project.org * @since 2.0 */ class SingleSelectExecutor extends AbstractSqlExecutor { /** * @param \Doctrine\ORM\Query\AST\SelectStatement $AST * @param \Doctrine\ORM\Query\SqlWalker $sqlWalker */ public function __construct(SelectStatement $AST, SqlWalker $sqlWalker) { $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST); } /** * {@inheritDoc} */ public function execute(Connection $conn, array $params, array $types) { return $conn->executeQuery($this->_sqlStatements, $params, $types, $this->queryCacheProfile); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php000066400000000000000000000042751257105210500272760ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\ORM\Query\AST; /** * Executor that executes the SQL statements for DQL DELETE/UPDATE statements on classes * that are mapped to a single table. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Roman Borschel * @link www.doctrine-project.org * @since 2.0 * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor. */ class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor { /** * @param \Doctrine\ORM\Query\AST\Node $AST * @param \Doctrine\ORM\Query\SqlWalker $sqlWalker */ public function __construct(AST\Node $AST, $sqlWalker) { if ($AST instanceof AST\UpdateStatement) { $this->_sqlStatements = $sqlWalker->walkUpdateStatement($AST); } else if ($AST instanceof AST\DeleteStatement) { $this->_sqlStatements = $sqlWalker->walkDeleteStatement($AST); } } /** * {@inheritDoc} */ public function execute(Connection $conn, array $params, array $types) { return $conn->executeUpdate($this->_sqlStatements, $params, $types); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr.php000066400000000000000000000444561257105210500211370ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; /** * This class is used to generate DQL expressions via a set of PHP static functions. * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei * @todo Rename: ExpressionBuilder */ class Expr { /** * Creates a conjunction of the given boolean expressions. * * Example: * * [php] * // (u.type = ?1) AND (u.role = ?2) * $expr->andX($expr->eq('u.type', ':1'), $expr->eq('u.role', ':2')); * * @param \Doctrine\ORM\Query\Expr\Comparison | * \Doctrine\ORM\Query\Expr\Func | * \Doctrine\ORM\Query\Expr\Orx * $x Optional clause. Defaults to null, but requires at least one defined when converting to string. * * @return Expr\Andx */ public function andX($x = null) { return new Expr\Andx(func_get_args()); } /** * Creates a disjunction of the given boolean expressions. * * Example: * * [php] * // (u.type = ?1) OR (u.role = ?2) * $q->where($q->expr()->orX('u.type = ?1', 'u.role = ?2')); * * @param mixed $x Optional clause. Defaults to null, but requires * at least one defined when converting to string. * * @return Expr\Orx */ public function orX($x = null) { return new Expr\Orx(func_get_args()); } /** * Creates an ASCending order expression. * * @param mixed $expr * * @return Expr\OrderBy */ public function asc($expr) { return new Expr\OrderBy($expr, 'ASC'); } /** * Creates a DESCending order expression. * * @param mixed $expr * * @return Expr\OrderBy */ public function desc($expr) { return new Expr\OrderBy($expr, 'DESC'); } /** * Creates an equality comparison expression with the given arguments. * * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a = . Example: * * [php] * // u.id = ?1 * $expr->eq('u.id', '?1'); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function eq($x, $y) { return new Expr\Comparison($x, Expr\Comparison::EQ, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <> . Example: * * [php] * // u.id <> ?1 * $q->where($q->expr()->neq('u.id', '?1')); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function neq($x, $y) { return new Expr\Comparison($x, Expr\Comparison::NEQ, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a < . Example: * * [php] * // u.id < ?1 * $q->where($q->expr()->lt('u.id', '?1')); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function lt($x, $y) { return new Expr\Comparison($x, Expr\Comparison::LT, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <= . Example: * * [php] * // u.id <= ?1 * $q->where($q->expr()->lte('u.id', '?1')); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function lte($x, $y) { return new Expr\Comparison($x, Expr\Comparison::LTE, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a > . Example: * * [php] * // u.id > ?1 * $q->where($q->expr()->gt('u.id', '?1')); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function gt($x, $y) { return new Expr\Comparison($x, Expr\Comparison::GT, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a >= . Example: * * [php] * // u.id >= ?1 * $q->where($q->expr()->gte('u.id', '?1')); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function gte($x, $y) { return new Expr\Comparison($x, Expr\Comparison::GTE, $y); } /** * Creates an instance of AVG() function, with the given argument. * * @param mixed $x Argument to be used in AVG() function. * * @return Expr\Func */ public function avg($x) { return new Expr\Func('AVG', array($x)); } /** * Creates an instance of MAX() function, with the given argument. * * @param mixed $x Argument to be used in MAX() function. * * @return Expr\Func */ public function max($x) { return new Expr\Func('MAX', array($x)); } /** * Creates an instance of MIN() function, with the given argument. * * @param mixed $x Argument to be used in MIN() function. * * @return Expr\Func */ public function min($x) { return new Expr\Func('MIN', array($x)); } /** * Creates an instance of COUNT() function, with the given argument. * * @param mixed $x Argument to be used in COUNT() function. * * @return Expr\Func */ public function count($x) { return new Expr\Func('COUNT', array($x)); } /** * Creates an instance of COUNT(DISTINCT) function, with the given argument. * * @param mixed $x Argument to be used in COUNT(DISTINCT) function. * * @return string */ public function countDistinct($x) { return 'COUNT(DISTINCT ' . implode(', ', func_get_args()) . ')'; } /** * Creates an instance of EXISTS() function, with the given DQL Subquery. * * @param mixed $subquery DQL Subquery to be used in EXISTS() function. * * @return Expr\Func */ public function exists($subquery) { return new Expr\Func('EXISTS', array($subquery)); } /** * Creates an instance of ALL() function, with the given DQL Subquery. * * @param mixed $subquery DQL Subquery to be used in ALL() function. * * @return Expr\Func */ public function all($subquery) { return new Expr\Func('ALL', array($subquery)); } /** * Creates a SOME() function expression with the given DQL subquery. * * @param mixed $subquery DQL Subquery to be used in SOME() function. * * @return Expr\Func */ public function some($subquery) { return new Expr\Func('SOME', array($subquery)); } /** * Creates an ANY() function expression with the given DQL subquery. * * @param mixed $subquery DQL Subquery to be used in ANY() function. * * @return Expr\Func */ public function any($subquery) { return new Expr\Func('ANY', array($subquery)); } /** * Creates a negation expression of the given restriction. * * @param mixed $restriction Restriction to be used in NOT() function. * * @return Expr\Func */ public function not($restriction) { return new Expr\Func('NOT', array($restriction)); } /** * Creates an ABS() function expression with the given argument. * * @param mixed $x Argument to be used in ABS() function. * * @return Expr\Func */ public function abs($x) { return new Expr\Func('ABS', array($x)); } /** * Creates a product mathematical expression with the given arguments. * * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a * . Example: * * [php] * // u.salary * u.percentAnnualSalaryIncrease * $q->expr()->prod('u.salary', 'u.percentAnnualSalaryIncrease') * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Math */ public function prod($x, $y) { return new Expr\Math($x, '*', $y); } /** * Creates a difference mathematical expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a - . Example: * * [php] * // u.monthlySubscriptionCount - 1 * $q->expr()->diff('u.monthlySubscriptionCount', '1') * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Math */ public function diff($x, $y) { return new Expr\Math($x, '-', $y); } /** * Creates a sum mathematical expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a + . Example: * * [php] * // u.numChildren + 1 * $q->expr()->diff('u.numChildren', '1') * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Math */ public function sum($x, $y) { return new Expr\Math($x, '+', $y); } /** * Creates a quotient mathematical expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a / . Example: * * [php] * // u.total / u.period * $expr->quot('u.total', 'u.period') * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Math */ public function quot($x, $y) { return new Expr\Math($x, '/', $y); } /** * Creates a SQRT() function expression with the given argument. * * @param mixed $x Argument to be used in SQRT() function. * * @return Expr\Func */ public function sqrt($x) { return new Expr\Func('SQRT', array($x)); } /** * Creates an IN() expression with the given arguments. * * @param string $x Field in string format to be restricted by IN() function. * @param mixed $y Argument to be used in IN() function. * * @return Expr\Func */ public function in($x, $y) { if (is_array($y)) { foreach ($y as &$literal) { if ( ! ($literal instanceof Expr\Literal)) { $literal = $this->_quoteLiteral($literal); } } } return new Expr\Func($x . ' IN', (array) $y); } /** * Creates a NOT IN() expression with the given arguments. * * @param string $x Field in string format to be restricted by NOT IN() function. * @param mixed $y Argument to be used in NOT IN() function. * * @return Expr\Func */ public function notIn($x, $y) { if (is_array($y)) { foreach ($y as &$literal) { if ( ! ($literal instanceof Expr\Literal)) { $literal = $this->_quoteLiteral($literal); } } } return new Expr\Func($x . ' NOT IN', (array) $y); } /** * Creates an IS NULL expression with the given arguments. * * @param string $x Field in string format to be restricted by IS NULL. * * @return string */ public function isNull($x) { return $x . ' IS NULL'; } /** * Creates an IS NOT NULL expression with the given arguments. * * @param string $x Field in string format to be restricted by IS NOT NULL. * * @return string */ public function isNotNull($x) { return $x . ' IS NOT NULL'; } /** * Creates a LIKE() comparison expression with the given arguments. * * @param string $x Field in string format to be inspected by LIKE() comparison. * @param mixed $y Argument to be used in LIKE() comparison. * * @return Expr\Comparison */ public function like($x, $y) { return new Expr\Comparison($x, 'LIKE', $y); } /** * Creates a NOT LIKE() comparison expression with the given arguments. * * @param string $x Field in string format to be inspected by LIKE() comparison. * @param mixed $y Argument to be used in LIKE() comparison. * * @return Expr\Comparison */ public function notLike($x, $y) { return new Expr\Comparison($x, 'NOT LIKE', $y); } /** * Creates a CONCAT() function expression with the given arguments. * * @param mixed $x First argument to be used in CONCAT() function. * @param mixed $y Second argument to be used in CONCAT() function. * * @return Expr\Func */ public function concat($x, $y) { return new Expr\Func('CONCAT', array($x, $y)); } /** * Creates a SUBSTRING() function expression with the given arguments. * * @param mixed $x Argument to be used as string to be cropped by SUBSTRING() function. * @param int $from Initial offset to start cropping string. May accept negative values. * @param int|null $len Length of crop. May accept negative values. * * @return Expr\Func */ public function substring($x, $from, $len = null) { $args = array($x, $from); if (null !== $len) { $args[] = $len; } return new Expr\Func('SUBSTRING', $args); } /** * Creates a LOWER() function expression with the given argument. * * @param mixed $x Argument to be used in LOWER() function. * * @return Expr\Func A LOWER function expression. */ public function lower($x) { return new Expr\Func('LOWER', array($x)); } /** * Creates an UPPER() function expression with the given argument. * * @param mixed $x Argument to be used in UPPER() function. * * @return Expr\Func An UPPER function expression. */ public function upper($x) { return new Expr\Func('UPPER', array($x)); } /** * Creates a LENGTH() function expression with the given argument. * * @param mixed $x Argument to be used as argument of LENGTH() function. * * @return Expr\Func A LENGTH function expression. */ public function length($x) { return new Expr\Func('LENGTH', array($x)); } /** * Creates a literal expression of the given argument. * * @param mixed $literal Argument to be converted to literal. * * @return Expr\Literal */ public function literal($literal) { return new Expr\Literal($this->_quoteLiteral($literal)); } /** * Quotes a literal value, if necessary, according to the DQL syntax. * * @param mixed $literal The literal value. * * @return string */ private function _quoteLiteral($literal) { if (is_numeric($literal) && !is_string($literal)) { return (string) $literal; } else if (is_bool($literal)) { return $literal ? "true" : "false"; } else { return "'" . str_replace("'", "''", $literal) . "'"; } } /** * Creates an instance of BETWEEN() function, with the given argument. * * @param mixed $val Valued to be inspected by range values. * @param integer $x Starting range value to be used in BETWEEN() function. * @param integer $y End point value to be used in BETWEEN() function. * * @return Expr\Func A BETWEEN expression. */ public function between($val, $x, $y) { return $val . ' BETWEEN ' . $x . ' AND ' . $y; } /** * Creates an instance of TRIM() function, with the given argument. * * @param mixed $x Argument to be used as argument of TRIM() function. * * @return Expr\Func a TRIM expression. */ public function trim($x) { return new Expr\Func('TRIM', $x); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/000077500000000000000000000000001257105210500204115ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/Andx.php000066400000000000000000000033321257105210500220150ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL and parts. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Andx extends Composite { /** * @var string */ protected $separator = ' AND '; /** * @var array */ protected $allowedClasses = array( 'Doctrine\ORM\Query\Expr\Comparison', 'Doctrine\ORM\Query\Expr\Func', 'Doctrine\ORM\Query\Expr\Orx', 'Doctrine\ORM\Query\Expr\Andx', ); /** * @return array */ public function getParts() { return $this->parts; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/Base.php000066400000000000000000000060651257105210500220030ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Abstract base Expr class for building DQL parts. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ abstract class Base { /** * @var string */ protected $preSeparator = '('; /** * @var string */ protected $separator = ', '; /** * @var string */ protected $postSeparator = ')'; /** * @var array */ protected $allowedClasses = array(); /** * @var array */ protected $parts = array(); /** * @param array $args */ public function __construct($args = array()) { $this->addMultiple($args); } /** * @param array $args * * @return Base */ public function addMultiple($args = array()) { foreach ((array) $args as $arg) { $this->add($arg); } return $this; } /** * @param mixed $arg * * @return Base * * @throws \InvalidArgumentException */ public function add($arg) { 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); if ( ! in_array($class, $this->allowedClasses)) { throw new \InvalidArgumentException("Expression of type '$class' not allowed in this context."); } } $this->parts[] = $arg; } return $this; } /** * @return integer */ public function count() { return count($this->parts); } /** * @return string */ public function __toString() { if ($this->count() == 1) { return (string) $this->parts[0]; } return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/Comparison.php000066400000000000000000000047331257105210500232430ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL comparison expressions. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Comparison { const EQ = '='; const NEQ = '<>'; const LT = '<'; const LTE = '<='; const GT = '>'; const GTE = '>='; /** * @var mixed */ protected $leftExpr; /** * @var string */ protected $operator; /** * @var mixed */ protected $rightExpr; /** * Creates a comparison expression with the given arguments. * * @param mixed $leftExpr * @param string $operator * @param mixed $rightExpr */ public function __construct($leftExpr, $operator, $rightExpr) { $this->leftExpr = $leftExpr; $this->operator = $operator; $this->rightExpr = $rightExpr; } /** * @return mixed */ public function getLeftExpr() { return $this->leftExpr; } /** * @return string */ public function getOperator() { return $this->operator; } /** * @return mixed */ public function getRightExpr() { return $this->rightExpr; } /** * @return string */ public function __toString() { return $this->leftExpr . ' ' . $this->operator . ' ' . $this->rightExpr; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/Composite.php000066400000000000000000000044631257105210500230730ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL and parts. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Composite extends Base { /** * @return string */ public function __toString() { if ($this->count() === 1) { return (string) $this->parts[0]; } $components = array(); foreach ($this->parts as $part) { $components[] = $this->processQueryPart($part); } return implode($this->separator, $components); } /** * @param string $part * * @return string */ 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 (stripos($queryPart, ' OR ') !== false || stripos($queryPart, ' AND ') !== false) { return $this->preSeparator . $queryPart . $this->postSeparator; } return $queryPart; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/From.php000066400000000000000000000044641257105210500220350ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL from. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class From { /** * @var string */ protected $from; /** * @var string */ protected $alias; /** * @var string */ protected $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->indexBy = $indexBy; } /** * @return string */ public function getFrom() { return $this->from; } /** * @return string */ public function getAlias() { return $this->alias; } /** * @return string */ public function getIndexBy() { return $this->indexBy; } /** * @return string */ public function __toString() { return $this->from . ' ' . $this->alias . ($this->indexBy ? ' INDEX BY ' . $this->indexBy : ''); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/Func.php000066400000000000000000000040631257105210500220200ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for generating DQL functions. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Func { /** * @var string */ protected $name; /** * @var array */ protected $arguments; /** * Creates a function, with the given argument. * * @param string $name * @param array $arguments */ public function __construct($name, $arguments) { $this->name = $name; $this->arguments = (array) $arguments; } /** * @return string */ public function getName() { return $this->name; } /** * @return array */ public function getArguments() { return $this->arguments; } /** * @return string */ public function __toString() { return $this->name . '(' . implode(', ', $this->arguments) . ')'; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/GroupBy.php000066400000000000000000000030551257105210500225140ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL Group By parts. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GroupBy extends Base { /** * @var string */ protected $preSeparator = ''; /** * @var string */ protected $postSeparator = ''; /** * @return array */ public function getParts() { return $this->parts; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/Join.php000066400000000000000000000072361257105210500220310ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL join. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Join { const INNER_JOIN = 'INNER'; const LEFT_JOIN = 'LEFT'; const ON = 'ON'; const WITH = 'WITH'; /** * @var string */ protected $joinType; /** * @var string */ protected $join; /** * @var string */ protected $alias; /** * @var string */ protected $conditionType; /** * @var string */ protected $condition; /** * @var string */ protected $indexBy; /** * @param string $joinType The condition type constant. Either INNER_JOIN or LEFT_JOIN. * @param string $join The relationship to join. * @param string|null $alias The alias of the join. * @param string|null $conditionType The condition type constant. Either ON or WITH. * @param string|null $condition The condition for the join. * @param string|null $indexBy The index for the join. */ public function __construct($joinType, $join, $alias = null, $conditionType = null, $condition = null, $indexBy = null) { $this->joinType = $joinType; $this->join = $join; $this->alias = $alias; $this->conditionType = $conditionType; $this->condition = $condition; $this->indexBy = $indexBy; } /** * @return string */ public function getJoinType() { return $this->joinType; } /** * @return string */ public function getJoin() { return $this->join; } /** * @return string */ public function getAlias() { return $this->alias; } /** * @return string */ public function getConditionType() { return $this->conditionType; } /** * @return string */ public function getCondition() { return $this->condition; } /** * @return string */ public function getIndexBy() { return $this->indexBy; } /** * @return string */ public function __toString() { return strtoupper($this->joinType) . ' JOIN ' . $this->join . ($this->alias ? ' ' . $this->alias : '') . ($this->indexBy ? ' INDEX BY ' . $this->indexBy : '') . ($this->condition ? ' ' . strtoupper($this->conditionType) . ' ' . $this->condition : ''); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/Literal.php000066400000000000000000000030521257105210500225160ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for generating DQL functions. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Literal extends Base { /** * @var string */ protected $preSeparator = ''; /** * @var string */ protected $postSeparator = ''; /** * @return array */ public function getParts() { return $this->parts; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/Math.php000066400000000000000000000053011257105210500220120ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL math statements. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Math { /** * @var mixed */ protected $leftExpr; /** * @var string */ protected $operator; /** * @var mixed */ protected $rightExpr; /** * Creates a mathematical expression with the given arguments. * * @param mixed $leftExpr * @param string $operator * @param mixed $rightExpr */ public function __construct($leftExpr, $operator, $rightExpr) { $this->leftExpr = $leftExpr; $this->operator = $operator; $this->rightExpr = $rightExpr; } /** * @return mixed */ public function getLeftExpr() { return $this->leftExpr; } /** * @return string */ public function getOperator() { return $this->operator; } /** * @return mixed */ public function getRightExpr() { return $this->rightExpr; } /** * @return string */ public function __toString() { // Adjusting Left Expression $leftExpr = (string) $this->leftExpr; if ($this->leftExpr instanceof Math) { $leftExpr = '(' . $leftExpr . ')'; } // Adjusting Right Expression $rightExpr = (string) $this->rightExpr; if ($this->rightExpr instanceof Math) { $rightExpr = '(' . $rightExpr . ')'; } return $leftExpr . ' ' . $this->operator . ' ' . $rightExpr; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/OrderBy.php000066400000000000000000000050021257105210500224650ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL Order By parts. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class OrderBy { /** * @var string */ protected $preSeparator = ''; /** * @var string */ protected $separator = ', '; /** * @var string */ protected $postSeparator = ''; /** * @var array */ protected $allowedClasses = array(); /** * @var array */ protected $parts = array(); /** * @param string|null $sort * @param string|null $order */ public function __construct($sort = null, $order = null) { if ($sort) { $this->add($sort, $order); } } /** * @param string $sort * @param string|null $order * * @return void */ public function add($sort, $order = null) { $order = ! $order ? 'ASC' : $order; $this->parts[] = $sort . ' '. $order; } /** * @return integer */ public function count() { return count($this->parts); } /** * @return array */ public function getParts() { return $this->parts; } /** * @return string */ public function __tostring() { return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/Orx.php000066400000000000000000000033311257105210500216720ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL OR clauses. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Orx extends Composite { /** * @var string */ protected $separator = ' OR '; /** * @var array */ protected $allowedClasses = array( 'Doctrine\ORM\Query\Expr\Comparison', 'Doctrine\ORM\Query\Expr\Func', 'Doctrine\ORM\Query\Expr\Andx', 'Doctrine\ORM\Query\Expr\Orx', ); /** * @return array */ public function getParts() { return $this->parts; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Expr/Select.php000066400000000000000000000032461257105210500223460ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL select statements. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Select extends Base { /** * @var string */ protected $preSeparator = ''; /** * @var string */ protected $postSeparator = ''; /** * @var array */ protected $allowedClasses = array( 'Doctrine\ORM\Query\Expr\Func' ); /** * @return array */ public function getParts() { return $this->parts; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Filter/000077500000000000000000000000001257105210500207205ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Query/Filter/SQLFilter.php000066400000000000000000000100001257105210500232250ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query\Filter; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\ParameterTypeInferer; /** * The base class that user defined filters should extend. * * Handles the setting and escaping of parameters. * * @author Alexander * @author Benjamin Eberlei * @abstract */ abstract class SQLFilter { /** * The entity manager. * * @var EntityManager */ private $em; /** * Parameters for the filter. * * @var array */ private $parameters; /** * Constructs the SQLFilter object. * * @param EntityManager $em The entity manager. */ final public function __construct(EntityManager $em) { $this->em = $em; } /** * Sets a parameter that can be used by the filter. * * @param string $name Name of the parameter. * @param string $value Value of the parameter. * @param string|null $type The parameter type. If specified, the given value will be run through * the type conversion of this type. This is usually not needed for * strings and numeric types. * * @return SQLFilter The current SQL filter. */ final public function setParameter($name, $value, $type = null) { if (null === $type) { $type = ParameterTypeInferer::inferType($value); } $this->parameters[$name] = array('value' => $value, 'type' => $type); // Keep the parameters sorted for the hash ksort($this->parameters); // The filter collection of the EM is now dirty $this->em->getFilters()->setFiltersStateDirty(); return $this; } /** * Gets a parameter to use in a query. * * The function is responsible for the right output escaping to use the * value in a query. * * @param string $name Name of the parameter. * * @return string The SQL escaped parameter to use in a query. * * @throws \InvalidArgumentException */ final public function getParameter($name) { if (!isset($this->parameters[$name])) { throw new \InvalidArgumentException("Parameter '" . $name . "' does not exist."); } return $this->em->getConnection()->quote($this->parameters[$name]['value'], $this->parameters[$name]['type']); } /** * Returns as string representation of the SQLFilter parameters (the state). * * @return string String representation of the SQLFilter. */ final public function __toString() { return serialize($this->parameters); } /** * Gets the SQL query part to add to a query. * * @param ClassMetaData $targetEntity * @param string $targetTableAlias * * @return string The constraint SQL if there is available, empty string otherwise. */ abstract public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias); } doctrine2-2.4.8/lib/Doctrine/ORM/Query/FilterCollection.php000066400000000000000000000130611257105210500234460ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; /** * Collection class for all the query filters. * * @author Alexander */ class FilterCollection { /* Filter STATES */ /** * A filter object is in CLEAN state when it has no changed parameters. */ const FILTERS_STATE_CLEAN = 1; /** * A filter object is in DIRTY state when it has changed parameters. */ const FILTERS_STATE_DIRTY = 2; /** * The used Configuration. * * @var \Doctrine\ORM\Configuration */ private $config; /** * The EntityManager that "owns" this FilterCollection instance. * * @var \Doctrine\ORM\EntityManager */ private $em; /** * Instances of enabled filters. * * @var array */ private $enabledFilters = array(); /** * @var string The filter hash from the last time the query was parsed. */ private $filterHash; /** * @var integer The current state of this filter. */ private $filtersState = self::FILTERS_STATE_CLEAN; /** * Constructor. * * @param EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; $this->config = $em->getConfiguration(); } /** * Gets all the enabled filters. * * @return array The enabled filters. */ public function getEnabledFilters() { return $this->enabledFilters; } /** * Enables a filter from the collection. * * @param string $name Name of the filter. * * @return \Doctrine\ORM\Query\Filter\SQLFilter The enabled filter. * * @throws \InvalidArgumentException If the filter does not exist. */ public function enable($name) { if (null === $filterClass = $this->config->getFilterClassName($name)) { throw new \InvalidArgumentException("Filter '" . $name . "' does not exist."); } if (!isset($this->enabledFilters[$name])) { $this->enabledFilters[$name] = new $filterClass($this->em); // Keep the enabled filters sorted for the hash ksort($this->enabledFilters); // Now the filter collection is dirty $this->filtersState = self::FILTERS_STATE_DIRTY; } return $this->enabledFilters[$name]; } /** * Disables a filter. * * @param string $name Name of the filter. * * @return \Doctrine\ORM\Query\Filter\SQLFilter The disabled filter. * * @throws \InvalidArgumentException If the filter does not exist. */ public function disable($name) { // Get the filter to return it $filter = $this->getFilter($name); unset($this->enabledFilters[$name]); // Now the filter collection is dirty $this->filtersState = self::FILTERS_STATE_DIRTY; return $filter; } /** * Gets an enabled filter from the collection. * * @param string $name Name of the filter. * * @return \Doctrine\ORM\Query\Filter\SQLFilter The filter. * * @throws \InvalidArgumentException If the filter is not enabled. */ public function getFilter($name) { if (!isset($this->enabledFilters[$name])) { throw new \InvalidArgumentException("Filter '" . $name . "' is not enabled."); } return $this->enabledFilters[$name]; } /** * Checks if a filter is enabled. * * @param string $name Name of the filter. * * @return boolean True if the filter is enabled, false otherwise. */ public function isEnabled($name) { return isset($this->enabledFilters[$name]); } /** * @return boolean True, if the filter collection is clean. */ public function isClean() { return self::FILTERS_STATE_CLEAN === $this->filtersState; } /** * Generates a string of currently enabled filters to use for the cache id. * * @return string */ public function getHash() { // If there are only clean filters, the previous hash can be returned if (self::FILTERS_STATE_CLEAN === $this->filtersState) { return $this->filterHash; } $filterHash = ''; foreach ($this->enabledFilters as $name => $filter) { $filterHash .= $name . $filter; } return $filterHash; } /** * Sets the filter state to dirty. */ public function setFiltersStateDirty() { $this->filtersState = self::FILTERS_STATE_DIRTY; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Lexer.php000066400000000000000000000160571257105210500212740ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; /** * Scans a DQL query for tokens. * * @author Guilherme Blanco * @author Janne Vanhala * @author Roman Borschel * @since 2.0 */ class Lexer extends \Doctrine\Common\Lexer { // All tokens that are not valid identifiers must be < 100 const T_NONE = 1; const T_INTEGER = 2; const T_STRING = 3; const T_INPUT_PARAMETER = 4; const T_FLOAT = 5; const T_CLOSE_PARENTHESIS = 6; const T_OPEN_PARENTHESIS = 7; const T_COMMA = 8; const T_DIVIDE = 9; const T_DOT = 10; const T_EQUALS = 11; const T_GREATER_THAN = 12; const T_LOWER_THAN = 13; const T_MINUS = 14; const T_MULTIPLY = 15; const T_NEGATE = 16; const T_PLUS = 17; const T_OPEN_CURLY_BRACE = 18; const T_CLOSE_CURLY_BRACE = 19; // All tokens that are also identifiers should be >= 100 const T_IDENTIFIER = 100; const T_ALL = 101; const T_AND = 102; const T_ANY = 103; const T_AS = 104; const T_ASC = 105; const T_AVG = 106; const T_BETWEEN = 107; const T_BOTH = 108; const T_BY = 109; const T_CASE = 110; const T_COALESCE = 111; const T_COUNT = 112; const T_DELETE = 113; const T_DESC = 114; const T_DISTINCT = 115; 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_SOME = 147; const T_SUM = 148; const T_THEN = 149; const T_TRAILING = 150; const T_TRUE = 151; const T_UPDATE = 152; const T_WHEN = 153; const T_WHERE = 154; const T_WITH = 155; const T_PARTIAL = 156; const T_NEW = 157; /** * Creates a new query scanner object. * * @param string $input A query string. */ public function __construct($input) { $this->setInput($input); } /** * @inheritdoc */ protected function getCatchablePatterns() { return array( '[a-z_\\\][a-z0-9_\:\\\]*[a-z0-9_]{1}', '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', "'(?:[^']|'')*'", '\?[0-9]*|:[a-z]{1}[a-z0-9_]{0,}' ); } /** * @inheritdoc */ protected function getNonCatchablePatterns() { return array('\s+', '(.)'); } /** * @inheritdoc */ protected function getType(&$value) { $type = self::T_NONE; switch (true) { // Recognize numeric values case (is_numeric($value)): if (strpos($value, '.') !== false || stripos($value, 'e') !== false) { return self::T_FLOAT; } return self::T_INTEGER; // Recognize quoted strings case ($value[0] === "'"): $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2)); return self::T_STRING; // Recognize identifiers case (ctype_alpha($value[0]) || $value[0] === '_'): $name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value); if (defined($name)) { $type = constant($name); if ($type > 100) { return $type; } } return self::T_IDENTIFIER; // Recognize input parameters case ($value[0] === '?' || $value[0] === ':'): return self::T_INPUT_PARAMETER; // Recognize symbols case ($value === '.'): return self::T_DOT; case ($value === ','): return self::T_COMMA; case ($value === '('): return self::T_OPEN_PARENTHESIS; case ($value === ')'): return self::T_CLOSE_PARENTHESIS; case ($value === '='): return self::T_EQUALS; case ($value === '>'): return self::T_GREATER_THAN; case ($value === '<'): return self::T_LOWER_THAN; case ($value === '+'): return self::T_PLUS; case ($value === '-'): return self::T_MINUS; case ($value === '*'): return self::T_MULTIPLY; case ($value === '/'): return self::T_DIVIDE; case ($value === '!'): return self::T_NEGATE; case ($value === '{'): return self::T_OPEN_CURLY_BRACE; case ($value === '}'): return self::T_CLOSE_CURLY_BRACE; // Default default: // Do nothing } return $type; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Parameter.php000066400000000000000000000050011257105210500221200ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; /** * Defines a Query Parameter. * * @link www.doctrine-project.org * @since 2.3 * @author Guilherme Blanco */ class Parameter { /** * The parameter name. * * @var string */ private $name; /** * The parameter value. * * @var mixed */ private $value; /** * The parameter type. * * @var mixed */ private $type; /** * Constructor. * * @param string $name Parameter name * @param mixed $value Parameter value * @param mixed $type Parameter type */ public function __construct($name, $value, $type = null) { $this->name = trim($name, ':'); $this->setValue($value, $type); } /** * Retrieves the Parameter name. * * @return string */ public function getName() { return $this->name; } /** * Retrieves the Parameter value. * * @return mixed */ public function getValue() { return $this->value; } /** * Retrieves the Parameter type. * * @return mixed */ public function getType() { return $this->type; } /** * Defines the Parameter value. * * @param mixed $value Parameter value. * @param mixed $type Parameter type. */ public function setValue($value, $type = null) { $this->value = $value; $this->type = $type ?: ParameterTypeInferer::inferType($value); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/ParameterTypeInferer.php000066400000000000000000000043111257105210500243000ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Type; /** * Provides an enclosed support for parameter inferring. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ParameterTypeInferer { /** * Infers type of a given value, returning a compatible constant: * - Type (\Doctrine\DBAL\Types\Type::*) * - Connection (\Doctrine\DBAL\Connection::PARAM_*) * * @param mixed $value Parameter value. * * @return mixed Parameter type constant. */ public static function inferType($value) { if (is_integer($value)) { return Type::INTEGER; } if (is_bool($value)) { return Type::BOOLEAN; } if ($value instanceof \DateTime) { return Type::DATETIME; } if (is_array($value)) { return is_integer(current($value)) ? Connection::PARAM_INT_ARRAY : Connection::PARAM_STR_ARRAY; } return \PDO::PARAM_STR; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Parser.php000066400000000000000000003304601257105210500214460ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; use Doctrine\ORM\Query; use Doctrine\ORM\Mapping\ClassMetadata; /** * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language. * Parses a DQL query, reports any errors in it, and generates an AST. * * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Janne Vanhala * @author Fabio B. Silva */ class Parser { /** * READ-ONLY: Maps BUILT-IN string function names to AST class names. * * @var array */ private static $_STRING_FUNCTIONS = array( 'concat' => 'Doctrine\ORM\Query\AST\Functions\ConcatFunction', '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', 'identity' => 'Doctrine\ORM\Query\AST\Functions\IdentityFunction', ); /** * READ-ONLY: Maps BUILT-IN numeric function names to AST class names. * * @var array */ private static $_NUMERIC_FUNCTIONS = array( 'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction', 'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction', 'abs' => 'Doctrine\ORM\Query\AST\Functions\AbsFunction', 'sqrt' => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction', 'mod' => 'Doctrine\ORM\Query\AST\Functions\ModFunction', 'size' => 'Doctrine\ORM\Query\AST\Functions\SizeFunction', 'date_diff' => 'Doctrine\ORM\Query\AST\Functions\DateDiffFunction', 'bit_and' => 'Doctrine\ORM\Query\AST\Functions\BitAndFunction', 'bit_or' => 'Doctrine\ORM\Query\AST\Functions\BitOrFunction', ); /** * READ-ONLY: Maps BUILT-IN datetime function names to AST class names. * * @var array */ private static $_DATETIME_FUNCTIONS = array( 'current_date' => 'Doctrine\ORM\Query\AST\Functions\CurrentDateFunction', 'current_time' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimeFunction', 'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction', 'date_add' => 'Doctrine\ORM\Query\AST\Functions\DateAddFunction', 'date_sub' => 'Doctrine\ORM\Query\AST\Functions\DateSubFunction', ); /* * Expressions that were encountered during parsing of identifiers and expressions * and still need to be validated. */ /** * @var array */ private $deferredIdentificationVariables = array(); /** * @var array */ private $deferredPartialObjectExpressions = array(); /** * @var array */ private $deferredPathExpressions = array(); /** * @var array */ private $deferredResultVariables = array(); /** * @var array */ private $deferredNewObjectExpressions = array(); /** * The lexer. * * @var \Doctrine\ORM\Query\Lexer */ private $lexer; /** * The parser result. * * @var \Doctrine\ORM\Query\ParserResult */ private $parserResult; /** * The EntityManager. * * @var \Doctrine\ORM\EntityManager */ private $em; /** * The Query to parse. * * @var Query */ private $query; /** * Map of declared query components in the parsed query. * * @var array */ private $queryComponents = array(); /** * Keeps the nesting level of defined ResultVariables. * * @var integer */ private $nestingLevel = 0; /** * Any additional custom tree walkers that modify the AST. * * @var array */ private $customTreeWalkers = array(); /** * The custom last tree walker, if any, that is responsible for producing the output. * * @var TreeWalker */ private $customOutputWalker; /** * @var array */ private $identVariableExpressions = array(); /** * Checks if a function is internally defined. Used to prevent overwriting * of built-in functions through user-defined functions. * * @param string $functionName * * @return bool */ static public function isInternalFunction($functionName) { $functionName = strtolower($functionName); return isset(self::$_STRING_FUNCTIONS[$functionName]) || isset(self::$_DATETIME_FUNCTIONS[$functionName]) || isset(self::$_NUMERIC_FUNCTIONS[$functionName]); } /** * Creates a new query parser object. * * @param Query $query The Query to parse. */ public function __construct(Query $query) { $this->query = $query; $this->em = $query->getEntityManager(); $this->lexer = new Lexer($query->getDql()); $this->parserResult = new ParserResult(); } /** * Sets a custom tree walker that produces output. * This tree walker will be run last over the AST, after any other walkers. * * @param string $className * * @return void */ public function setCustomOutputTreeWalker($className) { $this->customOutputWalker = $className; } /** * Adds a custom tree walker for modifying the AST. * * @param string $className * * @return void */ public function addCustomTreeWalker($className) { $this->customTreeWalkers[] = $className; } /** * Gets the lexer used by the parser. * * @return \Doctrine\ORM\Query\Lexer */ public function getLexer() { return $this->lexer; } /** * Gets the ParserResult that is being filled with information during parsing. * * @return \Doctrine\ORM\Query\ParserResult */ public function getParserResult() { return $this->parserResult; } /** * Gets the EntityManager used by the parser. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } /** * Parses and builds AST for the given Query. * * @return \Doctrine\ORM\Query\AST\SelectStatement | * \Doctrine\ORM\Query\AST\UpdateStatement | * \Doctrine\ORM\Query\AST\DeleteStatement */ public function getAST() { // Parse & build AST $AST = $this->QueryLanguage(); // Process any deferred validations of some nodes in the AST. // This also allows post-processing of the AST for modification purposes. $this->processDeferredIdentificationVariables(); if ($this->deferredPartialObjectExpressions) { $this->processDeferredPartialObjectExpressions(); } if ($this->deferredPathExpressions) { $this->processDeferredPathExpressions($AST); } if ($this->deferredResultVariables) { $this->processDeferredResultVariables(); } if ($this->deferredNewObjectExpressions) { $this->processDeferredNewObjectExpressions($AST); } $this->processRootEntityAliasSelected(); // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot! $this->fixIdentificationVariableOrder($AST); return $AST; } /** * Attempts to match the given token with the current lookahead token. * * If they match, updates the lookahead token; otherwise raises a syntax * error. * * @param int $token The token type. * * @return void * * @throws QueryException If the tokens don't match. */ public function match($token) { $lookaheadType = $this->lexer->lookahead['type']; // short-circuit on first condition, usually types match if ($lookaheadType !== $token && $token !== Lexer::T_IDENTIFIER && $lookaheadType <= Lexer::T_IDENTIFIER) { $this->syntaxError($this->lexer->getLiteral($token)); } $this->lexer->moveNext(); } /** * Frees this parser, enabling it to be reused. * * @param boolean $deep Whether to clean peek and reset errors. * @param integer $position Position to reset. * * @return void */ public function free($deep = false, $position = 0) { // WARNING! Use this method with care. It resets the scanner! $this->lexer->resetPosition($position); // Deep = true cleans peek and also any previously defined errors if ($deep) { $this->lexer->resetPeek(); } $this->lexer->token = null; $this->lexer->lookahead = null; } /** * Parses a query string. * * @return ParserResult */ public function parse() { $AST = $this->getAST(); if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) { $this->customTreeWalkers = $customWalkers; } if (($customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) { $this->customOutputWalker = $customOutputWalker; } // Run any custom tree walkers over the AST if ($this->customTreeWalkers) { $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents); foreach ($this->customTreeWalkers as $walker) { $treeWalkerChain->addTreeWalker($walker); } switch (true) { case ($AST instanceof AST\UpdateStatement): $treeWalkerChain->walkUpdateStatement($AST); break; case ($AST instanceof AST\DeleteStatement): $treeWalkerChain->walkDeleteStatement($AST); break; case ($AST instanceof AST\SelectStatement): default: $treeWalkerChain->walkSelectStatement($AST); } $this->queryComponents = $treeWalkerChain->getQueryComponents(); } $outputWalkerClass = $this->customOutputWalker ?: __NAMESPACE__ . '\SqlWalker'; $outputWalker = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents); // Assign an SQL executor to the parser result $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST)); return $this->parserResult; } /** * Fixes order of identification variables. * * They have to appear in the select clause in the same order as the * declarations (from ... x join ... y join ... z ...) appear in the query * as the hydration process relies on that order for proper operation. * * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST * * @return void */ private function fixIdentificationVariableOrder($AST) { if (count($this->identVariableExpressions) <= 1) { return; } foreach ($this->queryComponents as $dqlAlias => $qComp) { if ( ! isset($this->identVariableExpressions[$dqlAlias])) { continue; } $expr = $this->identVariableExpressions[$dqlAlias]; $key = array_search($expr, $AST->selectClause->selectExpressions); unset($AST->selectClause->selectExpressions[$key]); $AST->selectClause->selectExpressions[] = $expr; } } /** * Generates a new syntax error. * * @param string $expected Expected string. * @param array|null $token Got token. * * @return void * * @throws \Doctrine\ORM\Query\QueryException */ public function syntaxError($expected = '', $token = null) { if ($token === null) { $token = $this->lexer->lookahead; } $tokenPos = (isset($token['position'])) ? $token['position'] : '-1'; $message = "line 0, col {$tokenPos}: Error: "; $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected '; $message .= ($this->lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'"; throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL())); } /** * Generates a new semantical error. * * @param string $message Optional message. * @param array|null $token Optional token. * * @return void * * @throws \Doctrine\ORM\Query\QueryException */ public function semanticalError($message = '', $token = null) { if ($token === null) { $token = $this->lexer->lookahead; } // Minimum exposed chars ahead of token $distance = 12; // Find a position of a final word to display in error string $dql = $this->query->getDql(); $length = strlen($dql); $pos = $token['position'] + $distance; $pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length); $length = ($pos !== false) ? $pos - $token['position'] : $distance; $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1'; $tokenStr = substr($dql, $token['position'], $length); // Building informative message $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message; throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL())); } /** * Peeks beyond the matched closing parenthesis and returns the first token after that one. * * @param boolean $resetPeek Reset peek after finding the closing parenthesis. * * @return array */ private function peekBeyondClosingParenthesis($resetPeek = true) { $token = $this->lexer->peek(); $numUnmatched = 1; while ($numUnmatched > 0 && $token !== null) { switch ($token['type']) { case Lexer::T_OPEN_PARENTHESIS: ++$numUnmatched; break; case Lexer::T_CLOSE_PARENTHESIS: --$numUnmatched; break; default: // Do nothing } $token = $this->lexer->peek(); } if ($resetPeek) { $this->lexer->resetPeek(); } return $token; } /** * Checks if the given token indicates a mathematical operator. * * @param array $token * * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise. */ private function isMathOperator($token) { return in_array($token['type'], array(Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY)); } /** * Checks if the next-next (after lookahead) token starts a function. * * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise. */ private function isFunction() { $lookaheadType = $this->lexer->lookahead['type']; $peek = $this->lexer->peek(); $this->lexer->resetPeek(); return ($lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS); } /** * Checks whether the given token type indicates an aggregate function. * * @param int $tokenType * * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise. */ private function isAggregateFunction($tokenType) { return in_array($tokenType, array(Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT)); } /** * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME. * * @return boolean */ private function isNextAllAnySome() { return in_array($this->lexer->lookahead['type'], array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME)); } /** * Validates that the given IdentificationVariable is semantically correct. * It must exist in query components list. * * @return void */ private function processDeferredIdentificationVariables() { foreach ($this->deferredIdentificationVariables as $deferredItem) { $identVariable = $deferredItem['expression']; // Check if IdentificationVariable exists in queryComponents if ( ! isset($this->queryComponents[$identVariable])) { $this->semanticalError( "'$identVariable' is not defined.", $deferredItem['token'] ); } $qComp = $this->queryComponents[$identVariable]; // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if ( ! isset($qComp['metadata'])) { $this->semanticalError( "'$identVariable' does not point to a Class.", $deferredItem['token'] ); } // Validate if identification variable nesting level is lower or equal than the current one if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { $this->semanticalError( "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token'] ); } } } /** * Validates that the given NewObjectExpression. * * @param \Doctrine\ORM\Query\AST\SelectClause $AST * * @return void */ private function processDeferredNewObjectExpressions($AST) { foreach ($this->deferredNewObjectExpressions as $deferredItem) { $expression = $deferredItem['expression']; $token = $deferredItem['token']; $className = $expression->className; $args = $expression->args; $fromClassName = isset($AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName) ? $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName : null; // If the namespace is not given then assumes the first FROM entity namespace if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) { $namespace = substr($fromClassName, 0 , strrpos($fromClassName, '\\')); $fqcn = $namespace . '\\' . $className; if (class_exists($fqcn)) { $expression->className = $fqcn; $className = $fqcn; } } if ( ! class_exists($className)) { $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token); } $class = new \ReflectionClass($className); if ( ! $class->isInstantiable()) { $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token); } if ($class->getConstructor() === null) { $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token); } if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) { $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token); } } } /** * Validates that the given PartialObjectExpression is semantically correct. * It must exist in query components list. * * @return void */ private function processDeferredPartialObjectExpressions() { foreach ($this->deferredPartialObjectExpressions as $deferredItem) { $expr = $deferredItem['expression']; $class = $this->queryComponents[$expr->identificationVariable]['metadata']; foreach ($expr->partialFieldSet as $field) { if (isset($class->fieldMappings[$field])) { continue; } if (isset($class->associationMappings[$field]) && $class->associationMappings[$field]['isOwningSide'] && $class->associationMappings[$field]['type'] & ClassMetadata::TO_ONE) { continue; } $this->semanticalError( "There is no mapped field named '$field' on class " . $class->name . ".", $deferredItem['token'] ); } if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) { $this->semanticalError( "The partial field selection of class " . $class->name . " must contain the identifier.", $deferredItem['token'] ); } } } /** * Validates that the given ResultVariable is semantically correct. * It must exist in query components list. * * @return void */ private function processDeferredResultVariables() { foreach ($this->deferredResultVariables as $deferredItem) { $resultVariable = $deferredItem['expression']; // Check if ResultVariable exists in queryComponents if ( ! isset($this->queryComponents[$resultVariable])) { $this->semanticalError( "'$resultVariable' is not defined.", $deferredItem['token'] ); } $qComp = $this->queryComponents[$resultVariable]; // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if ( ! isset($qComp['resultVariable'])) { $this->semanticalError( "'$resultVariable' does not point to a ResultVariable.", $deferredItem['token'] ); } // Validate if identification variable nesting level is lower or equal than the current one if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { $this->semanticalError( "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token'] ); } } } /** * Validates that the given PathExpression is semantically correct for grammar rules: * * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression * StateFieldPathExpression ::= IdentificationVariable "." StateField * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField * * @param mixed $AST * * @return void */ private function processDeferredPathExpressions($AST) { foreach ($this->deferredPathExpressions as $deferredItem) { $pathExpression = $deferredItem['expression']; $qComp = $this->queryComponents[$pathExpression->identificationVariable]; $class = $qComp['metadata']; if (($field = $pathExpression->field) === null) { $field = $pathExpression->field = $class->identifier[0]; } // Check if field or association exists if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) { $this->semanticalError( 'Class ' . $class->name . ' has no field or association named ' . $field, $deferredItem['token'] ); } $fieldType = AST\PathExpression::TYPE_STATE_FIELD; if (isset($class->associationMappings[$field])) { $assoc = $class->associationMappings[$field]; $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE) ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION; } // Validate if PathExpression is one of the expected types $expectedType = $pathExpression->expectedType; if ( ! ($expectedType & $fieldType)) { // We need to recognize which was expected type(s) $expectedStringTypes = array(); // Validate state field type if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) { $expectedStringTypes[] = 'StateFieldPathExpression'; } // Validate single valued association (*-to-one) if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) { $expectedStringTypes[] = 'SingleValuedAssociationField'; } // Validate single valued association (*-to-many) if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) { $expectedStringTypes[] = 'CollectionValuedAssociationField'; } // Build the error message $semanticalError = 'Invalid PathExpression. '; $semanticalError .= (count($expectedStringTypes) == 1) ? 'Must be a ' . $expectedStringTypes[0] . '.' : implode(' or ', $expectedStringTypes) . ' expected.'; $this->semanticalError($semanticalError, $deferredItem['token']); } // We need to force the type in PathExpression $pathExpression->type = $fieldType; } } /** * @return void */ private function processRootEntityAliasSelected() { if ( ! count($this->identVariableExpressions)) { return; } $foundRootEntity = false; foreach ($this->identVariableExpressions as $dqlAlias => $expr) { if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) { $foundRootEntity = true; } } if ( ! $foundRootEntity) { $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.'); } } /** * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement * * @return \Doctrine\ORM\Query\AST\SelectStatement | * \Doctrine\ORM\Query\AST\UpdateStatement | * \Doctrine\ORM\Query\AST\DeleteStatement */ public function QueryLanguage() { $this->lexer->moveNext(); switch ($this->lexer->lookahead['type']) { case Lexer::T_SELECT: $statement = $this->SelectStatement(); break; case Lexer::T_UPDATE: $statement = $this->UpdateStatement(); break; case Lexer::T_DELETE: $statement = $this->DeleteStatement(); break; default: $this->syntaxError('SELECT, UPDATE or DELETE'); break; } // Check for end of string if ($this->lexer->lookahead !== null) { $this->syntaxError('end of string'); } return $statement; } /** * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * @return \Doctrine\ORM\Query\AST\SelectStatement */ public function SelectStatement() { $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause()); $selectStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; $selectStatement->havingClause = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; return $selectStatement; } /** * UpdateStatement ::= UpdateClause [WhereClause] * * @return \Doctrine\ORM\Query\AST\UpdateStatement */ public function UpdateStatement() { $updateStatement = new AST\UpdateStatement($this->UpdateClause()); $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; return $updateStatement; } /** * DeleteStatement ::= DeleteClause [WhereClause] * * @return \Doctrine\ORM\Query\AST\DeleteStatement */ public function DeleteStatement() { $deleteStatement = new AST\DeleteStatement($this->DeleteClause()); $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; return $deleteStatement; } /** * IdentificationVariable ::= identifier * * @return string */ public function IdentificationVariable() { $this->match(Lexer::T_IDENTIFIER); $identVariable = $this->lexer->token['value']; $this->deferredIdentificationVariables[] = array( 'expression' => $identVariable, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token, ); return $identVariable; } /** * AliasIdentificationVariable = identifier * * @return string */ public function AliasIdentificationVariable() { $this->match(Lexer::T_IDENTIFIER); $aliasIdentVariable = $this->lexer->token['value']; $exists = isset($this->queryComponents[$aliasIdentVariable]); if ($exists) { $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token); } return $aliasIdentVariable; } /** * AbstractSchemaName ::= identifier * * @return string */ public function AbstractSchemaName() { $this->match(Lexer::T_IDENTIFIER); $schemaName = ltrim($this->lexer->token['value'], '\\'); if (strrpos($schemaName, ':') !== false) { list($namespaceAlias, $simpleClassName) = explode(':', $schemaName); $schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; } $exists = class_exists($schemaName, true); if ( ! $exists) { $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token); } return $schemaName; } /** * AliasResultVariable ::= identifier * * @return string */ public function AliasResultVariable() { $this->match(Lexer::T_IDENTIFIER); $resultVariable = $this->lexer->token['value']; $exists = isset($this->queryComponents[$resultVariable]); if ($exists) { $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token); } return $resultVariable; } /** * ResultVariable ::= identifier * * @return string */ public function ResultVariable() { $this->match(Lexer::T_IDENTIFIER); $resultVariable = $this->lexer->token['value']; // Defer ResultVariable validation $this->deferredResultVariables[] = array( 'expression' => $resultVariable, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token, ); return $resultVariable; } /** * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) * * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression */ public function JoinAssociationPathExpression() { $identVariable = $this->IdentificationVariable(); if ( ! isset($this->queryComponents[$identVariable])) { $this->semanticalError( 'Identification Variable ' . $identVariable .' used in join path expression but was not defined before.' ); } $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); $field = $this->lexer->token['value']; // Validate association field $qComp = $this->queryComponents[$identVariable]; $class = $qComp['metadata']; if ( ! $class->hasAssociation($field)) { $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field); } return new AST\JoinAssociationPathExpression($identVariable, $field); } /** * Parses an arbitrary path expression and defers semantical validation * based on expected types. * * PathExpression ::= IdentificationVariable "." identifier * * @param integer $expectedTypes * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function PathExpression($expectedTypes) { $identVariable = $this->IdentificationVariable(); $field = null; if ($this->lexer->isNextToken(Lexer::T_DOT)) { $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); $field = $this->lexer->token['value']; } // Creating AST node $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field); // Defer PathExpression validation if requested to be deferred $this->deferredPathExpressions[] = array( 'expression' => $pathExpr, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token, ); return $pathExpr; } /** * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function AssociationPathExpression() { return $this->PathExpression( AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION | AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION ); } /** * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function SingleValuedPathExpression() { return $this->PathExpression( AST\PathExpression::TYPE_STATE_FIELD | AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION ); } /** * StateFieldPathExpression ::= IdentificationVariable "." StateField * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function StateFieldPathExpression() { return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD); } /** * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function SingleValuedAssociationPathExpression() { return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION); } /** * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function CollectionValuedPathExpression() { return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION); } /** * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} * * @return \Doctrine\ORM\Query\AST\SelectClause */ public function SelectClause() { $isDistinct = false; $this->match(Lexer::T_SELECT); // Check for DISTINCT if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; } // Process SelectExpressions (1..N) $selectExpressions = array(); $selectExpressions[] = $this->SelectExpression(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $selectExpressions[] = $this->SelectExpression(); } return new AST\SelectClause($selectExpressions, $isDistinct); } /** * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression * * @return \Doctrine\ORM\Query\AST\SimpleSelectClause */ public function SimpleSelectClause() { $isDistinct = false; $this->match(Lexer::T_SELECT); if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; } return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct); } /** * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* * * @return \Doctrine\ORM\Query\AST\UpdateClause */ public function UpdateClause() { $this->match(Lexer::T_UPDATE); $token = $this->lexer->lookahead; $abstractSchemaName = $this->AbstractSchemaName(); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $class = $this->em->getClassMetadata($abstractSchemaName); // Building queryComponent $queryComponent = array( 'metadata' => $class, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $token, ); $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; $this->match(Lexer::T_SET); $updateItems = array(); $updateItems[] = $this->UpdateItem(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $updateItems[] = $this->UpdateItem(); } $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems); $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable; return $updateClause; } /** * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable * * @return \Doctrine\ORM\Query\AST\DeleteClause */ public function DeleteClause() { $this->match(Lexer::T_DELETE); if ($this->lexer->isNextToken(Lexer::T_FROM)) { $this->match(Lexer::T_FROM); } $token = $this->lexer->lookahead; $deleteClause = new AST\DeleteClause($this->AbstractSchemaName()); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable; $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName); // Building queryComponent $queryComponent = array( 'metadata' => $class, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $token, ); $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; return $deleteClause; } /** * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* * * @return \Doctrine\ORM\Query\AST\FromClause */ public function FromClause() { $this->match(Lexer::T_FROM); $identificationVariableDeclarations = array(); $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); } return new AST\FromClause($identificationVariableDeclarations); } /** * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* * * @return \Doctrine\ORM\Query\AST\SubselectFromClause */ public function SubselectFromClause() { $this->match(Lexer::T_FROM); $identificationVariables = array(); $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); } return new AST\SubselectFromClause($identificationVariables); } /** * WhereClause ::= "WHERE" ConditionalExpression * * @return \Doctrine\ORM\Query\AST\WhereClause */ public function WhereClause() { $this->match(Lexer::T_WHERE); return new AST\WhereClause($this->ConditionalExpression()); } /** * HavingClause ::= "HAVING" ConditionalExpression * * @return \Doctrine\ORM\Query\AST\HavingClause */ public function HavingClause() { $this->match(Lexer::T_HAVING); return new AST\HavingClause($this->ConditionalExpression()); } /** * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* * * @return \Doctrine\ORM\Query\AST\GroupByClause */ public function GroupByClause() { $this->match(Lexer::T_GROUP); $this->match(Lexer::T_BY); $groupByItems = array($this->GroupByItem()); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $groupByItems[] = $this->GroupByItem(); } return new AST\GroupByClause($groupByItems); } /** * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* * * @return \Doctrine\ORM\Query\AST\OrderByClause */ public function OrderByClause() { $this->match(Lexer::T_ORDER); $this->match(Lexer::T_BY); $orderByItems = array(); $orderByItems[] = $this->OrderByItem(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $orderByItems[] = $this->OrderByItem(); } return new AST\OrderByClause($orderByItems); } /** * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * @return \Doctrine\ORM\Query\AST\Subselect */ public function Subselect() { // Increase query nesting level $this->nestingLevel++; $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause()); $subselect->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; $subselect->havingClause = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; // Decrease query nesting level $this->nestingLevel--; return $subselect; } /** * UpdateItem ::= SingleValuedPathExpression "=" NewValue * * @return \Doctrine\ORM\Query\AST\UpdateItem */ public function UpdateItem() { $pathExpr = $this->SingleValuedPathExpression(); $this->match(Lexer::T_EQUALS); $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue()); return $updateItem; } /** * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression * * @return string | \Doctrine\ORM\Query\AST\PathExpression */ public function GroupByItem() { // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression $glimpse = $this->lexer->glimpse(); if ($glimpse['type'] === Lexer::T_DOT) { return $this->SingleValuedPathExpression(); } // Still need to decide between IdentificationVariable or ResultVariable $lookaheadValue = $this->lexer->lookahead['value']; if ( ! isset($this->queryComponents[$lookaheadValue])) { $this->semanticalError('Cannot group by undefined identification or result variable.'); } return (isset($this->queryComponents[$lookaheadValue]['metadata'])) ? $this->IdentificationVariable() : $this->ResultVariable(); } /** * OrderByItem ::= ( * SimpleArithmeticExpression | SingleValuedPathExpression | * ScalarExpression | ResultVariable * ) ["ASC" | "DESC"] * * @return \Doctrine\ORM\Query\AST\OrderByItem */ public function OrderByItem() { $this->lexer->peek(); // lookahead => '.' $this->lexer->peek(); // lookahead => token after '.' $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' $this->lexer->resetPeek(); $glimpse = $this->lexer->glimpse(); switch (true) { case ($this->isMathOperator($peek)): $expr = $this->SimpleArithmeticExpression(); break; case ($glimpse['type'] === Lexer::T_DOT): $expr = $this->SingleValuedPathExpression(); break; case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())): $expr = $this->ScalarExpression(); break; default: $expr = $this->ResultVariable(); break; } $type = 'ASC'; $item = new AST\OrderByItem($expr); switch (true) { case ($this->lexer->isNextToken(Lexer::T_DESC)): $this->match(Lexer::T_DESC); $type = 'DESC'; break; case ($this->lexer->isNextToken(Lexer::T_ASC)): $this->match(Lexer::T_ASC); break; default: // Do nothing } $item->type = $type; return $item; } /** * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary | * EnumPrimary | SimpleEntityExpression | "NULL" * * NOTE: Since it is not possible to correctly recognize individual types, here is the full * grammar that needs to be supported: * * NewValue ::= SimpleArithmeticExpression | "NULL" * * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression * * @return AST\ArithmeticExpression */ public function NewValue() { if ($this->lexer->isNextToken(Lexer::T_NULL)) { $this->match(Lexer::T_NULL); return null; } if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); return new AST\InputParameter($this->lexer->token['value']); } return $this->ArithmeticExpression(); } /** * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}* * * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration */ public function IdentificationVariableDeclaration() { $rangeVariableDeclaration = $this->RangeVariableDeclaration(); $rangeVariableDeclaration->isRoot = true; $indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; $joins = array(); while ( $this->lexer->isNextToken(Lexer::T_LEFT) || $this->lexer->isNextToken(Lexer::T_INNER) || $this->lexer->isNextToken(Lexer::T_JOIN) ) { $joins[] = $this->Join(); } return new AST\IdentificationVariableDeclaration( $rangeVariableDeclaration, $indexBy, $joins ); } /** * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) * * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration | * \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration */ public function SubselectIdentificationVariableDeclaration() { $this->lexer->glimpse(); /* NOT YET IMPLEMENTED! if ($glimpse['type'] == Lexer::T_DOT) { $subselectIdVarDecl = new AST\SubselectIdentificationVariableDeclaration(); $subselectIdVarDecl->associationPathExpression = $this->AssociationPathExpression(); $this->match(Lexer::T_AS); $subselectIdVarDecl->aliasIdentificationVariable = $this->AliasIdentificationVariable(); return $subselectIdVarDecl; } */ return $this->IdentificationVariableDeclaration(); } /** * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" * (JoinAssociationDeclaration | RangeVariableDeclaration) * ["WITH" ConditionalExpression] * * @return \Doctrine\ORM\Query\AST\Join */ public function Join() { // Check Join type $joinType = AST\Join::JOIN_TYPE_INNER; switch (true) { case ($this->lexer->isNextToken(Lexer::T_LEFT)): $this->match(Lexer::T_LEFT); $joinType = AST\Join::JOIN_TYPE_LEFT; // Possible LEFT OUTER join if ($this->lexer->isNextToken(Lexer::T_OUTER)) { $this->match(Lexer::T_OUTER); $joinType = AST\Join::JOIN_TYPE_LEFTOUTER; } break; case ($this->lexer->isNextToken(Lexer::T_INNER)): $this->match(Lexer::T_INNER); break; default: // Do nothing } $this->match(Lexer::T_JOIN); $next = $this->lexer->glimpse(); $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration(); $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH); $join = new AST\Join($joinType, $joinDeclaration); // Describe non-root join declaration if ($joinDeclaration instanceof AST\RangeVariableDeclaration) { $joinDeclaration->isRoot = false; $adhocConditions = true; } // Check for ad-hoc Join conditions if ($adhocConditions) { $this->match(Lexer::T_WITH); $join->conditionalExpression = $this->ConditionalExpression(); } return $join; } /** * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable * * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration */ public function RangeVariableDeclaration() { $abstractSchemaName = $this->AbstractSchemaName(); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $token = $this->lexer->lookahead; $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $classMetadata = $this->em->getClassMetadata($abstractSchemaName); // Building queryComponent $queryComponent = array( 'metadata' => $classMetadata, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $token ); $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable); } /** * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy] * * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression */ public function JoinAssociationDeclaration() { $joinAssociationPathExpression = $this->JoinAssociationPathExpression(); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; $identificationVariable = $joinAssociationPathExpression->identificationVariable; $field = $joinAssociationPathExpression->associationField; $class = $this->queryComponents[$identificationVariable]['metadata']; $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']); // Building queryComponent $joinQueryComponent = array( 'metadata' => $targetClass, 'parent' => $joinAssociationPathExpression->identificationVariable, 'relation' => $class->getAssociationMapping($field), 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->lookahead ); $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy); } /** * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" * * @return array */ public function PartialObjectExpression() { $this->match(Lexer::T_PARTIAL); $partialFieldSet = array(); $identificationVariable = $this->IdentificationVariable(); $this->match(Lexer::T_DOT); $this->match(Lexer::T_OPEN_CURLY_BRACE); $this->match(Lexer::T_IDENTIFIER); $partialFieldSet[] = $this->lexer->token['value']; while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $this->match(Lexer::T_IDENTIFIER); $partialFieldSet[] = $this->lexer->token['value']; } $this->match(Lexer::T_CLOSE_CURLY_BRACE); $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet); // Defer PartialObjectExpression validation $this->deferredPartialObjectExpressions[] = array( 'expression' => $partialObjectExpression, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token, ); return $partialObjectExpression; } /** * NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")" * * @return \Doctrine\ORM\Query\AST\NewObjectExpression */ public function NewObjectExpression() { $this->match(Lexer::T_NEW); $this->match(Lexer::T_IDENTIFIER); $token = $this->lexer->token; $className = $token['value']; if (strrpos($className, ':') !== false) { list($namespaceAlias, $simpleClassName) = explode(':', $className); $className = $this->em->getConfiguration() ->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; } $this->match(Lexer::T_OPEN_PARENTHESIS); $args[] = $this->NewObjectArg(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $args[] = $this->NewObjectArg(); } $this->match(Lexer::T_CLOSE_PARENTHESIS); $expression = new AST\NewObjectExpression($className, $args); // Defer NewObjectExpression validation $this->deferredNewObjectExpressions[] = array( 'token' => $token, 'expression' => $expression, 'nestingLevel' => $this->nestingLevel, ); return $expression; } /** * NewObjectArg ::= ScalarExpression * * @TODO - Maybe you should support other expressions and nested "new" operator * * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression */ public function NewObjectArg() { return $this->ScalarExpression(); } /** * IndexBy ::= "INDEX" "BY" StateFieldPathExpression * * @return \Doctrine\ORM\Query\AST\IndexBy */ public function IndexBy() { $this->match(Lexer::T_INDEX); $this->match(Lexer::T_BY); $pathExpr = $this->StateFieldPathExpression(); // Add the INDEX BY info to the query component $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field; return new AST\IndexBy($pathExpr); } /** * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | * StateFieldPathExpression | BooleanPrimary | CaseExpression | * InstanceOfExpression * * @return mixed One of the possible expressions or subexpressions. */ public function ScalarExpression() { $lookahead = $this->lexer->lookahead['type']; $peek = $this->lexer->glimpse(); switch (true) { case ($lookahead === Lexer::T_INTEGER): case ($lookahead === Lexer::T_FLOAT): // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) or ( - 1 ) or ( + 1 ) case ($lookahead === Lexer::T_MINUS): case ($lookahead === Lexer::T_PLUS): return $this->SimpleArithmeticExpression(); case ($lookahead === Lexer::T_STRING): return $this->StringPrimary(); case ($lookahead === Lexer::T_TRUE): case ($lookahead === Lexer::T_FALSE): $this->match($lookahead); return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']); case ($lookahead === Lexer::T_INPUT_PARAMETER): switch (true) { case $this->isMathOperator($peek): // :param + u.value return $this->SimpleArithmeticExpression(); default: return $this->InputParameter(); } case ($lookahead === Lexer::T_CASE): case ($lookahead === Lexer::T_COALESCE): case ($lookahead === Lexer::T_NULLIF): // Since NULLIF and COALESCE can be identified as a function, // we need to check these before checking for FunctionDeclaration return $this->CaseExpression(); case ($lookahead === Lexer::T_OPEN_PARENTHESIS): return $this->SimpleArithmeticExpression(); //this check must be done before checking for a filed path expression case ($this->isFunction()): $this->lexer->peek(); // "(" switch (true) { case ($this->isMathOperator($this->peekBeyondClosingParenthesis())): // SUM(u.id) + COUNT(u.id) return $this->SimpleArithmeticExpression(); case ($this->isAggregateFunction($this->lexer->lookahead['type'])): return $this->AggregateExpression(); default: // IDENTITY(u) return $this->FunctionDeclaration(); } break; //it is no function, so it must be a field path case ($lookahead === Lexer::T_IDENTIFIER): $this->lexer->peek(); // lookahead => '.' $this->lexer->peek(); // lookahead => token after '.' $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' $this->lexer->resetPeek(); if ($this->isMathOperator($peek)) { return $this->SimpleArithmeticExpression(); } return $this->StateFieldPathExpression(); default: $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() { $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(); if ($peek['type'] === Lexer::T_WHEN) { return $this->GeneralCaseExpression(); } return $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\GeneralCaseExpression */ 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 * * @return AST\SimpleCaseExpression */ 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\WhenClause */ 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\SimpleWhenClause */ 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 | ScalarExpression | AggregateExpression | FunctionDeclaration | * PartialObjectExpression | "(" Subselect ")" | CaseExpression * ) [["AS"] ["HIDDEN"] AliasResultVariable] * * @return \Doctrine\ORM\Query\AST\SelectExpression */ public function SelectExpression() { $expression = null; $identVariable = null; $peek = $this->lexer->glimpse(); $lookaheadType = $this->lexer->lookahead['type']; switch (true) { // ScalarExpression (u.name) case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT): $expression = $this->ScalarExpression(); break; // IdentificationVariable (u) case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS): $expression = $identVariable = $this->IdentificationVariable(); break; // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...)) case ($lookaheadType === Lexer::T_CASE): case ($lookaheadType === Lexer::T_COALESCE): case ($lookaheadType === Lexer::T_NULLIF): $expression = $this->CaseExpression(); break; // DQL Function (SUM(u.value) or SUM(u.value) + 1) case ($this->isFunction()): $this->lexer->peek(); // "(" switch (true) { case ($this->isMathOperator($this->peekBeyondClosingParenthesis())): // SUM(u.id) + COUNT(u.id) $expression = $this->ScalarExpression(); break; case ($this->isAggregateFunction($lookaheadType)): // COUNT(u.id) $expression = $this->AggregateExpression(); break; default: // IDENTITY(u) $expression = $this->FunctionDeclaration(); break; } break; // PartialObjectExpression (PARTIAL u.{id, name}) case ($lookaheadType === Lexer::T_PARTIAL): $expression = $this->PartialObjectExpression(); $identVariable = $expression->identificationVariable; break; // Subselect case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT): $this->match(Lexer::T_OPEN_PARENTHESIS); $expression = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); break; // Shortcut: ScalarExpression => SimpleArithmeticExpression case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS): case ($lookaheadType === Lexer::T_INTEGER): case ($lookaheadType === Lexer::T_STRING): case ($lookaheadType === Lexer::T_FLOAT): // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) case ($lookaheadType === Lexer::T_MINUS): case ($lookaheadType === Lexer::T_PLUS): $expression = $this->SimpleArithmeticExpression(); break; // NewObjectExpression (New ClassName(id, name)) case ($lookaheadType === Lexer::T_NEW): $expression = $this->NewObjectExpression(); break; default: $this->syntaxError( 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', $this->lexer->lookahead ); } // [["AS"] ["HIDDEN"] AliasResultVariable] if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $hiddenAliasResultVariable = false; if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) { $this->match(Lexer::T_HIDDEN); $hiddenAliasResultVariable = true; } $aliasResultVariable = null; if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { $token = $this->lexer->lookahead; $aliasResultVariable = $this->AliasResultVariable(); // Include AliasResultVariable in query components. $this->queryComponents[$aliasResultVariable] = array( 'resultVariable' => $expression, 'nestingLevel' => $this->nestingLevel, 'token' => $token, ); } // AST $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable); if ($identVariable) { $this->identVariableExpressions[$identVariable] = $expr; } return $expr; } /** * SimpleSelectExpression ::= ( * StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | * AggregateExpression | "(" Subselect ")" | ScalarExpression * ) [["AS"] AliasResultVariable] * * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression */ public function SimpleSelectExpression() { $peek = $this->lexer->glimpse(); switch ($this->lexer->lookahead['type']) { case Lexer::T_IDENTIFIER: switch (true) { case ($peek['type'] === Lexer::T_DOT): $expression = $this->StateFieldPathExpression(); return new AST\SimpleSelectExpression($expression); case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS): $expression = $this->IdentificationVariable(); return new AST\SimpleSelectExpression($expression); case ($this->isFunction()): // SUM(u.id) + COUNT(u.id) if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) { return new AST\SimpleSelectExpression($this->ScalarExpression()); } // COUNT(u.id) if ($this->isAggregateFunction($this->lexer->lookahead['type'])) { return new AST\SimpleSelectExpression($this->AggregateExpression()); } // IDENTITY(u) return new AST\SimpleSelectExpression($this->FunctionDeclaration()); default: // Do nothing } break; case Lexer::T_OPEN_PARENTHESIS: if ($peek['type'] !== Lexer::T_SELECT) { // Shortcut: ScalarExpression => SimpleArithmeticExpression $expression = $this->SimpleArithmeticExpression(); return new AST\SimpleSelectExpression($expression); } // Subselect $this->match(Lexer::T_OPEN_PARENTHESIS); $expression = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\SimpleSelectExpression($expression); default: // Do nothing } $this->lexer->peek(); $expression = $this->ScalarExpression(); $expr = new AST\SimpleSelectExpression($expression); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { $token = $this->lexer->lookahead; $resultVariable = $this->AliasResultVariable(); $expr->fieldIdentificationVariable = $resultVariable; // Include AliasResultVariable in query components. $this->queryComponents[$resultVariable] = array( 'resultvariable' => $expr, 'nestingLevel' => $this->nestingLevel, 'token' => $token, ); } return $expr; } /** * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* * * @return \Doctrine\ORM\Query\AST\ConditionalExpression */ public function ConditionalExpression() { $conditionalTerms = array(); $conditionalTerms[] = $this->ConditionalTerm(); while ($this->lexer->isNextToken(Lexer::T_OR)) { $this->match(Lexer::T_OR); $conditionalTerms[] = $this->ConditionalTerm(); } // Phase 1 AST optimization: Prevent AST\ConditionalExpression // if only one AST\ConditionalTerm is defined if (count($conditionalTerms) == 1) { return $conditionalTerms[0]; } return new AST\ConditionalExpression($conditionalTerms); } /** * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* * * @return \Doctrine\ORM\Query\AST\ConditionalTerm */ public function ConditionalTerm() { $conditionalFactors = array(); $conditionalFactors[] = $this->ConditionalFactor(); while ($this->lexer->isNextToken(Lexer::T_AND)) { $this->match(Lexer::T_AND); $conditionalFactors[] = $this->ConditionalFactor(); } // Phase 1 AST optimization: Prevent AST\ConditionalTerm // if only one AST\ConditionalFactor is defined if (count($conditionalFactors) == 1) { return $conditionalFactors[0]; } return new AST\ConditionalTerm($conditionalFactors); } /** * ConditionalFactor ::= ["NOT"] ConditionalPrimary * * @return \Doctrine\ORM\Query\AST\ConditionalFactor */ public function ConditionalFactor() { $not = false; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $conditionalPrimary = $this->ConditionalPrimary(); // Phase 1 AST optimization: Prevent AST\ConditionalFactor // if only one AST\ConditionalPrimary is defined if ( ! $not) { return $conditionalPrimary; } $conditionalFactor = new AST\ConditionalFactor($conditionalPrimary); $conditionalFactor->not = $not; return $conditionalFactor; } /** * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" * * @return \Doctrine\ORM\Query\AST\ConditionalPrimary */ public function ConditionalPrimary() { $condPrimary = new AST\ConditionalPrimary; if ( ! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); return $condPrimary; } // Peek beyond the matching closing parenthesis ')' $peek = $this->peekBeyondClosingParenthesis(); if (in_array($peek['value'], array("=", "<", "<=", "<>", ">", ">=", "!=")) || in_array($peek['type'], array(Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS)) || $this->isMathOperator($peek)) { $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); return $condPrimary; } $this->match(Lexer::T_OPEN_PARENTHESIS); $condPrimary->conditionalExpression = $this->ConditionalExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $condPrimary; } /** * SimpleConditionalExpression ::= * ComparisonExpression | BetweenExpression | LikeExpression | * InExpression | NullComparisonExpression | ExistsExpression | * EmptyCollectionComparisonExpression | CollectionMemberExpression | * InstanceOfExpression */ public function SimpleConditionalExpression() { if ($this->lexer->isNextToken(Lexer::T_EXISTS)) { return $this->ExistsExpression(); } $token = $this->lexer->lookahead; $peek = $this->lexer->glimpse(); $lookahead = $token; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $token = $this->lexer->glimpse(); } if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) { // Peek beyond the matching closing parenthesis. $beyond = $this->lexer->peek(); switch ($peek['value']) { case '(': //Peeks beyond the matched closing parenthesis. $token = $this->peekBeyondClosingParenthesis(false); if ($token['type'] === Lexer::T_NOT) { $token = $this->lexer->peek(); } if ($token['type'] === Lexer::T_IS) { $lookahead = $this->lexer->peek(); } break; default: // Peek beyond the PathExpression or InputParameter. $token = $beyond; while ($token['value'] === '.') { $this->lexer->peek(); $token = $this->lexer->peek(); } // Also peek beyond a NOT if there is one. if ($token['type'] === Lexer::T_NOT) { $token = $this->lexer->peek(); } // We need to go even further in case of IS (differentiate between NULL and EMPTY) $lookahead = $this->lexer->peek(); } // Also peek beyond a NOT if there is one. if ($lookahead['type'] === Lexer::T_NOT) { $lookahead = $this->lexer->peek(); } $this->lexer->resetPeek(); } if ($token['type'] === Lexer::T_BETWEEN) { return $this->BetweenExpression(); } if ($token['type'] === Lexer::T_LIKE) { return $this->LikeExpression(); } if ($token['type'] === Lexer::T_IN) { return $this->InExpression(); } if ($token['type'] === Lexer::T_INSTANCE) { return $this->InstanceOfExpression(); } if ($token['type'] === Lexer::T_MEMBER) { return $this->CollectionMemberExpression(); } if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) { return $this->NullComparisonExpression(); } if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_EMPTY) { return $this->EmptyCollectionComparisonExpression(); } return $this->ComparisonExpression(); } /** * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" * * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression */ public function EmptyCollectionComparisonExpression() { $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression( $this->CollectionValuedPathExpression() ); $this->match(Lexer::T_IS); if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $emptyCollectionCompExpr->not = true; } $this->match(Lexer::T_EMPTY); return $emptyCollectionCompExpr; } /** * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression * * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression * SimpleEntityExpression ::= IdentificationVariable | InputParameter * * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression */ public function CollectionMemberExpression() { $not = false; $entityExpr = $this->EntityExpression(); if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_MEMBER); if ($this->lexer->isNextToken(Lexer::T_OF)) { $this->match(Lexer::T_OF); } $collMemberExpr = new AST\CollectionMemberExpression( $entityExpr, $this->CollectionValuedPathExpression() ); $collMemberExpr->not = $not; return $collMemberExpr; } /** * Literal ::= string | char | integer | float | boolean * * @return string */ public function Literal() { switch ($this->lexer->lookahead['type']) { case Lexer::T_STRING: $this->match(Lexer::T_STRING); return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']); case Lexer::T_INTEGER: case Lexer::T_FLOAT: $this->match( $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT ); return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']); case Lexer::T_TRUE: case Lexer::T_FALSE: $this->match( $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE ); return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']); default: $this->syntaxError('Literal'); } } /** * InParameter ::= Literal | InputParameter * * @return string | \Doctrine\ORM\Query\AST\InputParameter */ public function InParameter() { if ($this->lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) { return $this->InputParameter(); } return $this->Literal(); } /** * InputParameter ::= PositionalParameter | NamedParameter * * @return \Doctrine\ORM\Query\AST\InputParameter */ public function InputParameter() { $this->match(Lexer::T_INPUT_PARAMETER); return new AST\InputParameter($this->lexer->token['value']); } /** * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" * * @return \Doctrine\ORM\Query\AST\ArithmeticExpression */ public function ArithmeticExpression() { $expr = new AST\ArithmeticExpression; if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $peek = $this->lexer->glimpse(); if ($peek['type'] === Lexer::T_SELECT) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expr->subselect = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $expr; } } $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression(); return $expr; } /** * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* * * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression */ public function SimpleArithmeticExpression() { $terms = array(); $terms[] = $this->ArithmeticTerm(); while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) { $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); $terms[] = $this->lexer->token['value']; $terms[] = $this->ArithmeticTerm(); } // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression // if only one AST\ArithmeticTerm is defined if (count($terms) == 1) { return $terms[0]; } return new AST\SimpleArithmeticExpression($terms); } /** * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* * * @return \Doctrine\ORM\Query\AST\ArithmeticTerm */ public function ArithmeticTerm() { $factors = array(); $factors[] = $this->ArithmeticFactor(); while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) { $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE); $factors[] = $this->lexer->token['value']; $factors[] = $this->ArithmeticFactor(); } // Phase 1 AST optimization: Prevent AST\ArithmeticTerm // if only one AST\ArithmeticFactor is defined if (count($factors) == 1) { return $factors[0]; } return new AST\ArithmeticTerm($factors); } /** * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary * * @return \Doctrine\ORM\Query\AST\ArithmeticFactor */ public function ArithmeticFactor() { $sign = null; if (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) { $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); $sign = $isPlus; } $primary = $this->ArithmeticPrimary(); // Phase 1 AST optimization: Prevent AST\ArithmeticFactor // if only one AST\ArithmeticPrimary is defined if ($sign === null) { return $primary; } return new AST\ArithmeticFactor($primary, $sign); } /** * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings * | FunctionsReturningDatetime | IdentificationVariable | ResultVariable * | InputParameter | CaseExpression */ public function ArithmeticPrimary() { if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expr = $this->SimpleArithmeticExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\ParenthesisExpression($expr); } 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(); if ($peek['value'] == '(') { return $this->FunctionDeclaration(); } 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: return $this->InputParameter(); default: $peek = $this->lexer->glimpse(); if ($peek['value'] == '(') { if ($this->isAggregateFunction($this->lexer->lookahead['type'])) { return $this->AggregateExpression(); } return $this->FunctionDeclaration(); } return $this->Literal(); } } /** * StringExpression ::= StringPrimary | "(" Subselect ")" * * @return \Doctrine\ORM\Query\AST\StringPrimary | * \Doctrine\ORM\Query\AST\Subselect */ public function StringExpression() { if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $peek = $this->lexer->glimpse(); if ($peek['type'] === Lexer::T_SELECT) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expr = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $expr; } } return $this->StringPrimary(); } /** * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression */ public function StringPrimary() { $lookaheadType = $this->lexer->lookahead['type']; switch ($lookaheadType) { case Lexer::T_IDENTIFIER: $peek = $this->lexer->glimpse(); if ($peek['value'] == '.') { return $this->StateFieldPathExpression(); } if ($peek['value'] == '(') { // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions. return $this->FunctionDeclaration(); } $this->syntaxError("'.' or '('"); break; case Lexer::T_STRING: $this->match(Lexer::T_STRING); return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); case Lexer::T_CASE: case Lexer::T_COALESCE: case Lexer::T_NULLIF: return $this->CaseExpression(); default: if ($this->isAggregateFunction($lookaheadType)) { return $this->AggregateExpression(); } } $this->syntaxError( 'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression' ); } /** * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression * * @return \Doctrine\ORM\Query\AST\SingleValuedAssociationPathExpression | * \Doctrine\ORM\Query\AST\SimpleEntityExpression */ public function EntityExpression() { $glimpse = $this->lexer->glimpse(); if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') { return $this->SingleValuedAssociationPathExpression(); } return $this->SimpleEntityExpression(); } /** * SimpleEntityExpression ::= IdentificationVariable | InputParameter * * @return string | \Doctrine\ORM\Query\AST\InputParameter */ public function SimpleEntityExpression() { if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { return $this->InputParameter(); } return $this->StateFieldPathExpression(); } /** * AggregateExpression ::= * ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | * "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" * * @return \Doctrine\ORM\Query\AST\AggregateExpression */ public function AggregateExpression() { $lookaheadType = $this->lexer->lookahead['type']; $isDistinct = false; if ( ! in_array($lookaheadType, array(Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM))) { $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); } $this->match($lookaheadType); $functionName = $this->lexer->token['value']; $this->match(Lexer::T_OPEN_PARENTHESIS); if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; } $pathExp = ($lookaheadType === Lexer::T_COUNT) ? $this->SingleValuedPathExpression() : $this->SimpleArithmeticExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\AggregateExpression($functionName, $pathExp, $isDistinct); } /** * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" * * @return \Doctrine\ORM\Query\AST\QuantifiedExpression */ public function QuantifiedExpression() { $lookaheadType = $this->lexer->lookahead['type']; $value = $this->lexer->lookahead['value']; if ( ! in_array($lookaheadType, array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME))) { $this->syntaxError('ALL, ANY or SOME'); } $this->match($lookaheadType); $this->match(Lexer::T_OPEN_PARENTHESIS); $qExpr = new AST\QuantifiedExpression($this->Subselect()); $qExpr->type = $value; $this->match(Lexer::T_CLOSE_PARENTHESIS); return $qExpr; } /** * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression * * @return \Doctrine\ORM\Query\AST\BetweenExpression */ public function BetweenExpression() { $not = false; $arithExpr1 = $this->ArithmeticExpression(); if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_BETWEEN); $arithExpr2 = $this->ArithmeticExpression(); $this->match(Lexer::T_AND); $arithExpr3 = $this->ArithmeticExpression(); $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3); $betweenExpr->not = $not; return $betweenExpr; } /** * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) * * @return \Doctrine\ORM\Query\AST\ComparisonExpression */ public function ComparisonExpression() { $this->lexer->glimpse(); $leftExpr = $this->ArithmeticExpression(); $operator = $this->ComparisonOperator(); $rightExpr = ($this->isNextAllAnySome()) ? $this->QuantifiedExpression() : $this->ArithmeticExpression(); return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr); } /** * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" * * @return \Doctrine\ORM\Query\AST\InExpression */ public function InExpression() { $inExpression = new AST\InExpression($this->ArithmeticExpression()); if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $inExpression->not = true; } $this->match(Lexer::T_IN); $this->match(Lexer::T_OPEN_PARENTHESIS); if ($this->lexer->isNextToken(Lexer::T_SELECT)) { $inExpression->subselect = $this->Subselect(); } else { $literals = array(); $literals[] = $this->InParameter(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $literals[] = $this->InParameter(); } $inExpression->literals = $literals; } $this->match(Lexer::T_CLOSE_PARENTHESIS); return $inExpression; } /** * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") * * @return \Doctrine\ORM\Query\AST\InstanceOfExpression */ public function InstanceOfExpression() { $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable()); if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $instanceOfExpression->not = true; } $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(); 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; } $exprValues[] = $this->InstanceOfParameter(); $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" StringPrimary ["ESCAPE" char] * * @return \Doctrine\ORM\Query\AST\LikeExpression */ public function LikeExpression() { $stringExpr = $this->StringExpression(); $not = false; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_LIKE); if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); $stringPattern = new AST\InputParameter($this->lexer->token['value']); } else { $stringPattern = $this->StringPrimary(); } $escapeChar = null; if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) { $this->match(Lexer::T_ESCAPE); $this->match(Lexer::T_STRING); $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']); } $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar); $likeExpr->not = $not; return $likeExpr; } /** * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | SingleValuedPathExpression) "IS" ["NOT"] "NULL" * * @return \Doctrine\ORM\Query\AST\NullComparisonExpression */ public function NullComparisonExpression() { switch (true) { case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER): $this->match(Lexer::T_INPUT_PARAMETER); $expr = new AST\InputParameter($this->lexer->token['value']); break; case $this->lexer->isNextToken(Lexer::T_NULLIF): $expr = $this->NullIfExpression(); break; case $this->lexer->isNextToken(Lexer::T_COALESCE): $expr = $this->CoalesceExpression(); break; case $this->isFunction(): $expr = $this->FunctionDeclaration(); break; default: $expr = $this->SingleValuedPathExpression(); break; } $nullCompExpr = new AST\NullComparisonExpression($expr); $this->match(Lexer::T_IS); if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $nullCompExpr->not = true; } $this->match(Lexer::T_NULL); return $nullCompExpr; } /** * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" * * @return \Doctrine\ORM\Query\AST\ExistsExpression */ public function ExistsExpression() { $not = false; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_EXISTS); $this->match(Lexer::T_OPEN_PARENTHESIS); $existsExpression = new AST\ExistsExpression($this->Subselect()); $existsExpression->not = $not; $this->match(Lexer::T_CLOSE_PARENTHESIS); return $existsExpression; } /** * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" * * @return string */ public function ComparisonOperator() { switch ($this->lexer->lookahead['value']) { case '=': $this->match(Lexer::T_EQUALS); return '='; case '<': $this->match(Lexer::T_LOWER_THAN); $operator = '<'; if ($this->lexer->isNextToken(Lexer::T_EQUALS)) { $this->match(Lexer::T_EQUALS); $operator .= '='; } else if ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) { $this->match(Lexer::T_GREATER_THAN); $operator .= '>'; } return $operator; case '>': $this->match(Lexer::T_GREATER_THAN); $operator = '>'; if ($this->lexer->isNextToken(Lexer::T_EQUALS)) { $this->match(Lexer::T_EQUALS); $operator .= '='; } return $operator; case '!': $this->match(Lexer::T_NEGATE); $this->match(Lexer::T_EQUALS); return '<>'; default: $this->syntaxError('=, <, <=, <>, >, >=, !='); } } /** * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime * * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode */ public function FunctionDeclaration() { $token = $this->lexer->lookahead; $funcName = strtolower($token['value']); // Check for built-in functions first! switch (true) { case (isset(self::$_STRING_FUNCTIONS[$funcName])): return $this->FunctionsReturningStrings(); case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])): return $this->FunctionsReturningNumerics(); case (isset(self::$_DATETIME_FUNCTIONS[$funcName])): return $this->FunctionsReturningDatetime(); default: return $this->CustomFunctionDeclaration(); } } /** * Helper function for FunctionDeclaration grammar rule. * * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode */ private function CustomFunctionDeclaration() { $token = $this->lexer->lookahead; $funcName = strtolower($token['value']); // Check for custom functions afterwards $config = $this->em->getConfiguration(); switch (true) { case ($config->getCustomStringFunction($funcName) !== null): return $this->CustomFunctionsReturningStrings(); case ($config->getCustomNumericFunction($funcName) !== null): return $this->CustomFunctionsReturningNumerics(); case ($config->getCustomDatetimeFunction($funcName) !== null): return $this->CustomFunctionsReturningDatetime(); default: $this->syntaxError('known function', $token); } } /** * FunctionsReturningNumerics ::= * "LENGTH" "(" StringPrimary ")" | * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | * "ABS" "(" SimpleArithmeticExpression ")" | * "SQRT" "(" SimpleArithmeticExpression ")" | * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | * "SIZE" "(" CollectionValuedPathExpression ")" * * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode */ public function FunctionsReturningNumerics() { $funcNameLower = strtolower($this->lexer->lookahead['value']); $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower]; $function = new $funcClass($funcNameLower); $function->parse($this); return $function; } /** * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode */ public function CustomFunctionsReturningNumerics() { // getCustomNumericFunction is case-insensitive $funcName = strtolower($this->lexer->lookahead['value']); $funcClass = $this->em->getConfiguration()->getCustomNumericFunction($funcName); $function = new $funcClass($funcName); $function->parse($this); return $function; } /** * FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" * * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode */ public function FunctionsReturningDatetime() { $funcNameLower = strtolower($this->lexer->lookahead['value']); $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower]; $function = new $funcClass($funcNameLower); $function->parse($this); return $function; } /** * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode */ public function CustomFunctionsReturningDatetime() { // getCustomDatetimeFunction is case-insensitive $funcName = $this->lexer->lookahead['value']; $funcClass = $this->em->getConfiguration()->getCustomDatetimeFunction($funcName); $function = new $funcClass($funcName); $function->parse($this); return $function; } /** * FunctionsReturningStrings ::= * "CONCAT" "(" StringPrimary "," StringPrimary ")" | * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | * "LOWER" "(" StringPrimary ")" | * "UPPER" "(" StringPrimary ")" * * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode */ public function FunctionsReturningStrings() { $funcNameLower = strtolower($this->lexer->lookahead['value']); $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower]; $function = new $funcClass($funcNameLower); $function->parse($this); return $function; } /** * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode */ public function CustomFunctionsReturningStrings() { // getCustomStringFunction is case-insensitive $funcName = $this->lexer->lookahead['value']; $funcClass = $this->em->getConfiguration()->getCustomStringFunction($funcName); $function = new $funcClass($funcName); $function->parse($this); return $function; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/ParserResult.php000066400000000000000000000104031257105210500226350ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; /** * Encapsulates the resulting components from a DQL query parsing process that * can be serialized. * * @author Guilherme Blanco * @author Janne Vanhala * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.doctrine-project.org * @since 2.0 */ class ParserResult { /** * The SQL executor used for executing the SQL. * * @var \Doctrine\ORM\Query\Exec\AbstractSqlExecutor */ private $_sqlExecutor; /** * The ResultSetMapping that describes how to map the SQL result set. * * @var \Doctrine\ORM\Query\ResultSetMapping */ private $_resultSetMapping; /** * The mappings of DQL parameter names/positions to SQL parameter positions. * * @var array */ private $_parameterMappings = array(); /** * Initializes a new instance of the ParserResult class. * The new instance is initialized with an empty ResultSetMapping. */ public function __construct() { $this->_resultSetMapping = new ResultSetMapping; } /** * Gets the ResultSetMapping for the parsed query. * * @return ResultSetMapping|null The result set mapping of the parsed query or NULL * if the query is not a SELECT query. */ public function getResultSetMapping() { return $this->_resultSetMapping; } /** * Sets the ResultSetMapping of the parsed query. * * @param ResultSetMapping $rsm * * @return void */ public function setResultSetMapping(ResultSetMapping $rsm) { $this->_resultSetMapping = $rsm; } /** * Sets the SQL executor that should be used for this ParserResult. * * @param \Doctrine\ORM\Query\Exec\AbstractSqlExecutor $executor * * @return void */ public function setSqlExecutor($executor) { $this->_sqlExecutor = $executor; } /** * Gets the SQL executor used by this ParserResult. * * @return \Doctrine\ORM\Query\Exec\AbstractSqlExecutor */ public function getSqlExecutor() { return $this->_sqlExecutor; } /** * Adds a DQL to SQL parameter mapping. One DQL parameter name/position can map to * several SQL parameter positions. * * @param string|integer $dqlPosition * @param integer $sqlPosition * * @return void */ public function addParameterMapping($dqlPosition, $sqlPosition) { $this->_parameterMappings[$dqlPosition][] = $sqlPosition; } /** * Gets all DQL to SQL parameter mappings. * * @return array The parameter mappings. */ public function getParameterMappings() { return $this->_parameterMappings; } /** * Gets the SQL parameter positions for a DQL parameter name/position. * * @param string|integer $dqlPosition The name or position of the DQL parameter. * * @return array The positions of the corresponding SQL parameters. */ public function getSqlParameterPositions($dqlPosition) { return $this->_parameterMappings[$dqlPosition]; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/Printer.php000066400000000000000000000053221257105210500216310ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; /** * A parse tree printer for Doctrine Query Language parser. * * @author Janne Vanhala * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.phpdoctrine.org * @since 2.0 */ class Printer { /** * Current indentation level * * @var int */ protected $_indent = 0; /** * Defines whether parse tree is printed (default, false) or not (true). * * @var bool */ protected $_silent; /** * Constructs a new parse tree printer. * * @param bool $silent Parse tree will not be printed if true. */ public function __construct($silent = false) { $this->_silent = $silent; } /** * Prints an opening parenthesis followed by production name and increases * indentation level by one. * * This method is called before executing a production. * * @param string $name Production name. * * @return void */ public function startProduction($name) { $this->println('(' . $name); $this->_indent++; } /** * Decreases indentation level by one and prints a closing parenthesis. * * This method is called after executing a production. * * @return void */ public function endProduction() { $this->_indent--; $this->println(')'); } /** * Prints text indented with spaces depending on current indentation level. * * @param string $str The text. * * @return void */ public function println($str) { if ( ! $this->_silent) { echo str_repeat(' ', $this->_indent), $str, "\n"; } } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/QueryException.php000066400000000000000000000162041257105210500231730ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; /** * Description of QueryException. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class QueryException extends \Doctrine\ORM\ORMException { /** * @param string $dql * * @return QueryException */ public static function dqlError($dql) { return new self($dql); } /** * @param string $message * @param \Exception|null $previous * * @return QueryException */ public static function syntaxError($message, $previous = null) { return new self('[Syntax Error] ' . $message, 0, $previous); } /** * @param string $message * @param \Exception|null $previous * * @return QueryException */ public static function semanticalError($message, $previous = null) { return new self('[Semantical Error] ' . $message, 0, $previous); } /** * @return QueryException */ public static function invalidLockMode() { return new self('Invalid lock mode hint provided.'); } /** * @param string $expected * @param string $received * * @return QueryException */ public static function invalidParameterType($expected, $received) { return new self('Invalid parameter type, ' . $received . ' given, but ' . $expected . ' expected.'); } /** * @param string $pos * * @return QueryException */ public static function invalidParameterPosition($pos) { return new self('Invalid parameter position: ' . $pos); } /** * @return QueryException */ public static function invalidParameterNumber() { return new self("Invalid parameter number: number of bound variables does not match number of tokens"); } /** * @param string $value * * @return QueryException */ public static function invalidParameterFormat($value) { return new self('Invalid parameter format, '.$value.' given, but : or ? expected.'); } /** * @param string $key * * @return QueryException */ public static function unknownParameter($key) { return new self("Invalid parameter: token ".$key." is not defined in the query."); } /** * @return QueryException */ public static function parameterTypeMismatch() { return new self("DQL Query parameter and type numbers mismatch, but have to be exactly equal."); } /** * @param object $pathExpr * * @return QueryException */ public static function invalidPathExpression($pathExpr) { return new self( "Invalid PathExpression '" . $pathExpr->identificationVariable . "." . $pathExpr->field . "'." ); } /** * @param string $literal * * @return QueryException */ public static function invalidLiteral($literal) { return new self("Invalid literal '$literal'"); } /** * @param array $assoc * * @return QueryException */ public static function iterateWithFetchJoinCollectionNotAllowed($assoc) { return new self( "Invalid query operation: Not allowed to iterate over fetch join collections ". "in class ".$assoc['sourceEntity']." association ".$assoc['fieldName'] ); } /** * @return QueryException */ public static function partialObjectsAreDangerous() { return new self( "Loading partial objects is dangerous. Fetch full objects or consider " . "using a different fetch mode. If you really want partial objects, " . "set the doctrine.forcePartialLoad query hint to TRUE." ); } /** * @param array $assoc * * @return QueryException */ public static function overwritingJoinConditionsNotYetSupported($assoc) { return new self( "Unsupported query operation: It is not yet possible to overwrite the join ". "conditions in class ".$assoc['sourceEntityName']." association ".$assoc['fieldName'].". ". "Use WITH to append additional join conditions to the association." ); } /** * @return QueryException */ public static function associationPathInverseSideNotSupported() { return new self( "A single-valued association path expression to an inverse side is not supported". " in DQL queries. Use an explicit join instead." ); } /** * @param array $assoc * * @return QueryException */ public static function iterateWithFetchJoinNotAllowed($assoc) { return new self( "Iterate with fetch join in class " . $assoc['sourceEntity'] . " using association " . $assoc['fieldName'] . " not allowed." ); } /** * @return QueryException */ public static function associationPathCompositeKeyNotSupported() { return new self( "A single-valued association path expression to an entity with a composite primary ". "key is not supported. Explicitly name the components of the composite primary key ". "in the query." ); } /** * @param string $className * @param string $rootClass * * @return QueryException */ public static function instanceOfUnrelatedClass($className, $rootClass) { return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " . "inheritance hierarchy exists between these two classes."); } /** * @param string $dqlAlias * * @return QueryException */ public static function invalidQueryComponent($dqlAlias) { return new self( "Invalid query component given for DQL alias '" . $dqlAlias . "', ". "requires 'metadata', 'parent', 'relation', 'map', 'nestingLevel' and 'token' keys." ); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/QueryExpressionVisitor.php000066400000000000000000000133031257105210500247510ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Expr\ExpressionVisitor; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Collections\Expr\CompositeExpression; use Doctrine\Common\Collections\Expr\Value; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Query\Parameter; /** * Converts Collection expressions to Query expressions. * * @author Kirill chEbba Chebunin * @since 2.4 */ class QueryExpressionVisitor extends ExpressionVisitor { /** * @var array */ private static $operatorMap = array( Comparison::GT => Expr\Comparison::GT, Comparison::GTE => Expr\Comparison::GTE, Comparison::LT => Expr\Comparison::LT, Comparison::LTE => Expr\Comparison::LTE ); /** * @var string */ private $rootAlias; /** * @var Expr */ private $expr; /** * @var array */ private $parameters = array(); /** * Constructor * * @param string $rootAlias */ public function __construct($rootAlias) { $this->rootAlias = $rootAlias; $this->expr = new Expr(); } /** * Gets bound parameters. * Filled after {@link dispach()}. * * @return \Doctrine\Common\Collections\Collection */ public function getParameters() { return new ArrayCollection($this->parameters); } /** * Clears parameters. * * @return void */ public function clearParameters() { $this->parameters = array(); } /** * Converts Criteria expression to Query one based on static map. * * @param string $criteriaOperator * * @return string|null */ private static function convertComparisonOperator($criteriaOperator) { return isset(self::$operatorMap[$criteriaOperator]) ? self::$operatorMap[$criteriaOperator] : null; } /** * {@inheritDoc} */ public function walkCompositeExpression(CompositeExpression $expr) { $expressionList = array(); foreach ($expr->getExpressionList() as $child) { $expressionList[] = $this->dispatch($child); } switch($expr->getType()) { case CompositeExpression::TYPE_AND: return new Expr\Andx($expressionList); case CompositeExpression::TYPE_OR: return new Expr\Orx($expressionList); default: throw new \RuntimeException("Unknown composite " . $expr->getType()); } } /** * {@inheritDoc} */ public function walkComparison(Comparison $comparison) { $parameterName = str_replace('.', '_', $comparison->getField()); $parameter = new Parameter($parameterName, $this->walkValue($comparison->getValue())); $placeholder = ':' . $parameterName; switch ($comparison->getOperator()) { case Comparison::IN: $this->parameters[] = $parameter; return $this->expr->in($this->rootAlias . '.' . $comparison->getField(), $placeholder); case Comparison::NIN: $this->parameters[] = $parameter; return $this->expr->notIn($this->rootAlias . '.' . $comparison->getField(), $placeholder); case Comparison::EQ: case Comparison::IS: if ($this->walkValue($comparison->getValue()) === null) { return $this->expr->isNull($this->rootAlias . '.' . $comparison->getField()); } $this->parameters[] = $parameter; return $this->expr->eq($this->rootAlias . '.' . $comparison->getField(), $placeholder); case Comparison::NEQ: if ($this->walkValue($comparison->getValue()) === null) { return $this->expr->isNotNull($this->rootAlias . '.' . $comparison->getField()); } $this->parameters[] = $parameter; return $this->expr->neq($this->rootAlias . '.' . $comparison->getField(), $placeholder); default: $operator = self::convertComparisonOperator($comparison->getOperator()); if ($operator) { $this->parameters[] = $parameter; return new Expr\Comparison( $this->rootAlias . '.' . $comparison->getField(), $operator, $placeholder ); } throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator()); } } /** * {@inheritDoc} */ public function walkValue(Value $value) { return $value->getValue(); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/ResultSetMapping.php000066400000000000000000000404521257105210500234570ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; /** * A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result. * * IMPORTANT NOTE: * The properties of this class are only public for fast internal READ access and to (drastically) * reduce the size of serialized instances for more effective caching due to better (un-)serialization * performance. * * Users should use the public methods. * * @author Roman Borschel * @since 2.0 * @todo Think about whether the number of lookup maps can be reduced. */ class ResultSetMapping { /** * Whether the result is mixed (contains scalar values together with field values). * * @ignore * @var boolean */ public $isMixed = false; /** * Maps alias names to class names. * * @ignore * @var array */ public $aliasMap = array(); /** * Maps alias names to related association field names. * * @ignore * @var array */ public $relationMap = array(); /** * Maps alias names to parent alias names. * * @ignore * @var array */ public $parentAliasMap = array(); /** * Maps column names in the result set to field names for each class. * * @ignore * @var array */ public $fieldMappings = array(); /** * Maps column names in the result set to the alias/field name to use in the mapped result. * * @ignore * @var array */ public $scalarMappings = array(); /** * Maps column names in the result set to the alias/field type to use in the mapped result. * * @ignore * @var array */ public $typeMappings = array(); /** * Maps entities in the result set to the alias name to use in the mapped result. * * @ignore * @var array */ public $entityMappings = array(); /** * Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names. * * @ignore * @var array */ public $metaMappings = array(); /** * Maps column names in the result set to the alias they belong to. * * @ignore * @var array */ public $columnOwnerMap = array(); /** * List of columns in the result set that are used as discriminator columns. * * @ignore * @var array */ public $discriminatorColumns = array(); /** * Maps alias names to field names that should be used for indexing. * * @ignore * @var array */ public $indexByMap = array(); /** * Map from column names to class names that declare the field the column is mapped to. * * @ignore * @var array */ public $declaringClasses = array(); /** * This is necessary to hydrate derivate foreign keys correctly. * * @var array */ public $isIdentifierColumn = array(); /** * Maps column names in the result set to field names for each new object expression. * * @var array */ public $newObjectMappings = array(); /** * Maps metadata parameter names to the metadata attribute. * * @var array */ public $metadataParameterMapping = array(); /** * Adds an entity result to this ResultSetMapping. * * @param string $class The class name of the entity. * @param string $alias The alias for the class. The alias must be unique among all entity * results or joined entity results within this ResultSetMapping. * @param string|null $resultAlias The result alias with which the entity result should be * placed in the result structure. * * @return ResultSetMapping This ResultSetMapping instance. * * @todo Rename: addRootEntity */ public function addEntityResult($class, $alias, $resultAlias = null) { $this->aliasMap[$alias] = $class; $this->entityMappings[$alias] = $resultAlias; if ($resultAlias !== null) { $this->isMixed = true; } return $this; } /** * Sets a discriminator column for an entity result or joined entity result. * The discriminator column will be used to determine the concrete class name to * instantiate. * * @param string $alias The alias of the entity result or joined entity result the discriminator * column should be used for. * @param string $discrColumn The name of the discriminator column in the SQL result set. * * @return ResultSetMapping This ResultSetMapping instance. * * @todo Rename: addDiscriminatorColumn */ public function setDiscriminatorColumn($alias, $discrColumn) { $this->discriminatorColumns[$alias] = $discrColumn; $this->columnOwnerMap[$discrColumn] = $alias; return $this; } /** * Sets a field to use for indexing an entity result or joined entity result. * * @param string $alias The alias of an entity result or joined entity result. * @param string $fieldName The name of the field to use for indexing. * * @return ResultSetMapping This ResultSetMapping instance. */ public function addIndexBy($alias, $fieldName) { $found = false; foreach (array_merge($this->metaMappings, $this->fieldMappings) as $columnName => $columnFieldName) { if ( ! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) continue; $this->addIndexByColumn($alias, $columnName); $found = true; break; } /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals if ( ! $found) { $message = sprintf( 'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.', $alias, $fieldName ); throw new \LogicException($message); } */ return $this; } /** * Sets to index by a scalar result column name. * * @param string $resultColumnName * * @return ResultSetMapping This ResultSetMapping instance. */ public function addIndexByScalar($resultColumnName) { $this->indexByMap['scalars'] = $resultColumnName; return $this; } /** * Sets a column to use for indexing an entity or joined entity result by the given alias name. * * @param string $alias * @param string $resultColumnName * * @return ResultSetMapping This ResultSetMapping instance. */ public function addIndexByColumn($alias, $resultColumnName) { $this->indexByMap[$alias] = $resultColumnName; return $this; } /** * Checks whether an entity result or joined entity result with a given alias has * a field set for indexing. * * @param string $alias * * @return boolean * * @todo Rename: isIndexed($alias) */ public function hasIndexBy($alias) { return isset($this->indexByMap[$alias]); } /** * Checks whether the column with the given name is mapped as a field result * as part of an entity result or joined entity result. * * @param string $columnName The name of the column in the SQL result set. * * @return boolean * * @todo Rename: isField */ public function isFieldResult($columnName) { return isset($this->fieldMappings[$columnName]); } /** * Adds a field to the result that belongs to an entity or joined entity. * * @param string $alias The alias of the root entity or joined entity to which the field belongs. * @param string $columnName The name of the column in the SQL result set. * @param string $fieldName The name of the field on the declaring class. * @param string|null $declaringClass The name of the class that declares/owns the specified field. * When $alias refers to a superclass in a mapped hierarchy but * the field $fieldName is defined on a subclass, specify that here. * If not specified, the field is assumed to belong to the class * designated by $alias. * * @return ResultSetMapping This ResultSetMapping instance. * * @todo Rename: addField */ public function addFieldResult($alias, $columnName, $fieldName, $declaringClass = null) { // column name (in result set) => field name $this->fieldMappings[$columnName] = $fieldName; // column name => alias of owner $this->columnOwnerMap[$columnName] = $alias; // field name => class name of declaring class $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias]; if ( ! $this->isMixed && $this->scalarMappings) { $this->isMixed = true; } return $this; } /** * Adds a joined entity result. * * @param string $class The class name of the joined entity. * @param string $alias The unique alias to use for the joined entity. * @param string $parentAlias The alias of the entity result that is the parent of this joined result. * @param object $relation The association field that connects the parent entity result * with the joined entity result. * * @return ResultSetMapping This ResultSetMapping instance. * * @todo Rename: addJoinedEntity */ public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) { $this->aliasMap[$alias] = $class; $this->parentAliasMap[$alias] = $parentAlias; $this->relationMap[$alias] = $relation; return $this; } /** * Adds a scalar result mapping. * * @param string $columnName The name of the column in the SQL result set. * @param string $alias The result alias with which the scalar result should be placed in the result structure. * @param string $type The column type * * @return ResultSetMapping This ResultSetMapping instance. * * @todo Rename: addScalar */ public function addScalarResult($columnName, $alias, $type = 'string') { $this->scalarMappings[$columnName] = $alias; $this->typeMappings[$columnName] = $type; if ( ! $this->isMixed && $this->fieldMappings) { $this->isMixed = true; } return $this; } /** * Adds a metadata parameter mappings. * * @param mixed $parameter The parameter name in the SQL result set. * @param string $attribute The metadata attribute. */ public function addMetadataParameterMapping($parameter, $attribute) { $this->metadataParameterMapping[$parameter] = $attribute; } /** * Checks whether a column with a given name is mapped as a scalar result. * * @param string $columnName The name of the column in the SQL result set. * * @return boolean * * @todo Rename: isScalar */ public function isScalarResult($columnName) { return isset($this->scalarMappings[$columnName]); } /** * Gets the name of the class of an entity result or joined entity result, * identified by the given unique alias. * * @param string $alias * * @return string */ public function getClassName($alias) { return $this->aliasMap[$alias]; } /** * Gets the field alias for a column that is mapped as a scalar value. * * @param string $columnName The name of the column in the SQL result set. * * @return string */ public function getScalarAlias($columnName) { return $this->scalarMappings[$columnName]; } /** * Gets the name of the class that owns a field mapping for the specified column. * * @param string $columnName * * @return string */ public function getDeclaringClass($columnName) { return $this->declaringClasses[$columnName]; } /** * @param string $alias * * @return AssociationMapping */ public function getRelation($alias) { return $this->relationMap[$alias]; } /** * @param string $alias * * @return boolean */ public function isRelation($alias) { return isset($this->relationMap[$alias]); } /** * Gets the alias of the class that owns a field mapping for the specified column. * * @param string $columnName * * @return string */ public function getEntityAlias($columnName) { return $this->columnOwnerMap[$columnName]; } /** * Gets the parent alias of the given alias. * * @param string $alias * * @return string */ public function getParentAlias($alias) { return $this->parentAliasMap[$alias]; } /** * Checks whether the given alias has a parent alias. * * @param string $alias * * @return boolean */ public function hasParentAlias($alias) { return isset($this->parentAliasMap[$alias]); } /** * Gets the field name for a column name. * * @param string $columnName * * @return string */ public function getFieldName($columnName) { return $this->fieldMappings[$columnName]; } /** * @return array */ public function getAliasMap() { return $this->aliasMap; } /** * Gets the number of different entities that appear in the mapped result. * * @return integer */ public function getEntityResultCount() { return count($this->aliasMap); } /** * Checks whether this ResultSetMapping defines a mixed result. * * Mixed results can only occur in object and array (graph) hydration. In such a * case a mixed result means that scalar values are mixed with objects/array in * the result. * * @return boolean */ public function isMixedResult() { return $this->isMixed; } /** * Adds a meta column (foreign key or discriminator column) to the result set. * * @param string $alias The result alias with which the meta result should be placed in the result structure. * @param string $columnName The name of the column in the SQL result set. * @param string $fieldName The name of the field on the declaring class. * @param bool $isIdentifierColumn * @param string $type The column type * * @return ResultSetMapping This ResultSetMapping instance. */ public function addMetaResult($alias, $columnName, $fieldName, $isIdentifierColumn = false, $type = null) { $this->metaMappings[$columnName] = $fieldName; $this->columnOwnerMap[$columnName] = $alias; if ($isIdentifierColumn) { $this->isIdentifierColumn[$alias][$columnName] = true; } if ($type) { $this->typeMappings[$columnName] = $type; } return $this; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php000066400000000000000000000417641257105210500247750ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields. * * @author Michael Ridgway * @since 2.1 */ class ResultSetMappingBuilder extends ResultSetMapping { /** * Picking this rename mode will register entity columns as is, * as they are in the database. This can cause clashes when multiple * entities are fetched that have columns with the same name. * * @var int */ const COLUMN_RENAMING_NONE = 1; /** * Picking custom renaming allows the user to define the renaming * of specific columns with a rename array that contains column names as * keys and result alias as values. * * @var int */ const COLUMN_RENAMING_CUSTOM = 2; /** * Incremental renaming uses a result set mapping internal counter to add a * number to each column result, leading to uniqueness. This only works if * you use {@see generateSelectClause()} to generate the SELECT clause for * you. * * @var int */ const COLUMN_RENAMING_INCREMENT = 3; /** * @var int */ private $sqlCounter = 0; /** * @var EntityManager */ private $em; /** * Default column renaming mode. * * @var int */ private $defaultRenameMode; /** * @param EntityManager $em * @param integer $defaultRenameMode */ public function __construct(EntityManager $em, $defaultRenameMode = self::COLUMN_RENAMING_NONE) { $this->em = $em; $this->defaultRenameMode = $defaultRenameMode; } /** * Adds a root entity and all of its fields to the result set. * * @param string $class The class name of the root entity. * @param string $alias The unique alias to use for the root entity. * @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName). * @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM). * * @return void */ public function addRootEntityFromClassMetadata($class, $alias, $renamedColumns = array(), $renameMode = null) { $renameMode = $renameMode ?: $this->defaultRenameMode; $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns); $this->addEntityResult($class, $alias); $this->addAllClassFields($class, $alias, $columnAliasMap); } /** * Adds a joined entity and all of its fields to the result set. * * @param string $class The class name of the joined entity. * @param string $alias The unique alias to use for the joined entity. * @param string $parentAlias The alias of the entity result that is the parent of this joined result. * @param object $relation The association field that connects the parent entity result * with the joined entity result. * @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName). * @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM). * * @return void */ public function addJoinedEntityFromClassMetadata($class, $alias, $parentAlias, $relation, $renamedColumns = array(), $renameMode = null) { $renameMode = $renameMode ?: $this->defaultRenameMode; $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns); $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation); $this->addAllClassFields($class, $alias, $columnAliasMap); } /** * Adds all fields of the given class to the result set mapping (columns and meta fields). * * @param string $class * @param string $alias * @param array $columnAliasMap * * @return void * * @throws \InvalidArgumentException */ protected function addAllClassFields($class, $alias, $columnAliasMap = array()) { $classMetadata = $this->em->getClassMetadata($class); $platform = $this->em->getConnection()->getDatabasePlatform(); if ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()) { throw new \InvalidArgumentException('ResultSetMapping builder does not currently support inheritance.'); } foreach ($classMetadata->getColumnNames() as $columnName) { $propertyName = $classMetadata->getFieldName($columnName); $columnAlias = $platform->getSQLResultCasing($columnAliasMap[$columnName]); if (isset($this->fieldMappings[$columnAlias])) { throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper."); } $this->addFieldResult($alias, $columnAlias, $propertyName); } foreach ($classMetadata->associationMappings as $associationMapping) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { foreach ($associationMapping['joinColumns'] as $joinColumn) { $columnName = $joinColumn['name']; $columnAlias = $platform->getSQLResultCasing($columnAliasMap[$columnName]); if (isset($this->metaMappings[$columnAlias])) { throw new \InvalidArgumentException("The column '$columnAlias' conflicts with another column in the mapper."); } $this->addMetaResult( $alias, $columnAlias, $columnName, (isset($associationMapping['id']) && $associationMapping['id'] === true) ); } } } } /** * Gets column alias for a given column. * * @param string $columnName * @param int $mode * @param array $customRenameColumns * * @return string */ private function getColumnAlias($columnName, $mode, array $customRenameColumns) { switch ($mode) { case self::COLUMN_RENAMING_INCREMENT: return $columnName . $this->sqlCounter++; case self::COLUMN_RENAMING_CUSTOM: return isset($customRenameColumns[$columnName]) ? $customRenameColumns[$columnName] : $columnName; case self::COLUMN_RENAMING_NONE: return $columnName; } } /** * Retrieves a class columns and join columns aliases that are used in the SELECT clause. * * This depends on the renaming mode selected by the user. * * @param string $className * @param int $mode * @param array $customRenameColumns * * @return array */ private function getColumnAliasMap($className, $mode, array $customRenameColumns) { if ($customRenameColumns) { // for BC with 2.2-2.3 API $mode = self::COLUMN_RENAMING_CUSTOM; } $columnAlias = array(); $class = $this->em->getClassMetadata($className); foreach ($class->getColumnNames() as $columnName) { $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns); } foreach ($class->associationMappings as $associationMapping) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { foreach ($associationMapping['joinColumns'] as $joinColumn) { $columnName = $joinColumn['name']; $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns); } } } return $columnAlias; } /** * Adds the mappings of the results of native SQL queries to the result set. * * @param ClassMetadataInfo $class * @param array $queryMapping * * @return ResultSetMappingBuilder */ public function addNamedNativeQueryMapping(ClassMetadataInfo $class, array $queryMapping) { if (isset($queryMapping['resultClass'])) { return $this->addNamedNativeQueryResultClassMapping($class, $queryMapping['resultClass']); } return $this->addNamedNativeQueryResultSetMapping($class, $queryMapping['resultSetMapping']); } /** * Adds the class mapping of the results of native SQL queries to the result set. * * @param ClassMetadataInfo $class * @param string $resultClassName * * @return ResultSetMappingBuilder */ public function addNamedNativeQueryResultClassMapping(ClassMetadataInfo $class, $resultClassName) { $classMetadata = $this->em->getClassMetadata($resultClassName); $shortName = $classMetadata->reflClass->getShortName(); $alias = strtolower($shortName[0]).'0'; $this->addEntityResult($class->name, $alias); if ($classMetadata->discriminatorColumn) { $discriminatorColumn = $classMetadata->discriminatorColumn; $this->setDiscriminatorColumn($alias, $discriminatorColumn['name']); $this->addMetaResult($alias, $discriminatorColumn['name'], $discriminatorColumn['fieldName']); } foreach ($classMetadata->getColumnNames() as $key => $columnName) { $propertyName = $classMetadata->getFieldName($columnName); $this->addFieldResult($alias, $columnName, $propertyName); } foreach ($classMetadata->associationMappings as $associationMapping) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { foreach ($associationMapping['joinColumns'] as $joinColumn) { $columnName = $joinColumn['name']; $this->addMetaResult($alias, $columnName, $columnName, $classMetadata->isIdentifier($columnName)); } } } return $this; } /** * Adds the result set mapping of the results of native SQL queries to the result set. * * @param ClassMetadataInfo $class * @param string $resultSetMappingName * * @return ResultSetMappingBuilder */ public function addNamedNativeQueryResultSetMapping(ClassMetadataInfo $class, $resultSetMappingName) { $counter = 0; $resultMapping = $class->getSqlResultSetMapping($resultSetMappingName); $rooShortName = $class->reflClass->getShortName(); $rootAlias = strtolower($rooShortName[0]) . $counter; if (isset($resultMapping['entities'])) { foreach ($resultMapping['entities'] as $key => $entityMapping) { $classMetadata = $this->em->getClassMetadata($entityMapping['entityClass']); if ($class->reflClass->name == $classMetadata->reflClass->name) { $this->addEntityResult($classMetadata->name, $rootAlias); $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $rootAlias); } else { $shortName = $classMetadata->reflClass->getShortName(); $joinAlias = strtolower($shortName[0]) . ++ $counter; $associations = $class->getAssociationsByTargetClass($classMetadata->name); foreach ($associations as $relation => $mapping) { $this->addJoinedEntityResult($mapping['targetEntity'], $joinAlias, $rootAlias, $relation); $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $joinAlias); } } } } if (isset($resultMapping['columns'])) { foreach ($resultMapping['columns'] as $entityMapping) { $this->addScalarResult($entityMapping['name'], $entityMapping['name']); } } return $this; } /** * Adds the entity result mapping of the results of native SQL queries to the result set. * * @param ClassMetadataInfo $classMetadata * @param array $entityMapping * @param string $alias * * @return ResultSetMappingBuilder * * @throws \InvalidArgumentException */ public function addNamedNativeQueryEntityResultMapping(ClassMetadataInfo $classMetadata, array $entityMapping, $alias) { if (isset($entityMapping['discriminatorColumn']) && $entityMapping['discriminatorColumn']) { $discriminatorColumn = $entityMapping['discriminatorColumn']; $this->setDiscriminatorColumn($alias, $discriminatorColumn); $this->addMetaResult($alias, $discriminatorColumn, $discriminatorColumn); } if (isset($entityMapping['fields']) && !empty($entityMapping['fields'])) { foreach ($entityMapping['fields'] as $field) { $fieldName = $field['name']; $relation = null; if(strpos($fieldName, '.')){ list($relation, $fieldName) = explode('.', $fieldName); } if (isset($classMetadata->associationMappings[$relation])) { if($relation) { $associationMapping = $classMetadata->associationMappings[$relation]; $joinAlias = $alias.$relation; $parentAlias = $alias; $this->addJoinedEntityResult($associationMapping['targetEntity'], $joinAlias, $parentAlias, $relation); $this->addFieldResult($joinAlias, $field['column'], $fieldName); }else { $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); } } else { if(!isset($classMetadata->fieldMappings[$fieldName])) { throw new \InvalidArgumentException("Entity '".$classMetadata->name."' has no field '".$fieldName."'. "); } $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); } } } else { foreach ($classMetadata->getColumnNames() as $columnName) { $propertyName = $classMetadata->getFieldName($columnName); $this->addFieldResult($alias, $columnName, $propertyName); } } return $this; } /** * Generates the Select clause from this ResultSetMappingBuilder. * * Works only for all the entity results. The select parts for scalar * expressions have to be written manually. * * @param array $tableAliases * * @return string */ public function generateSelectClause($tableAliases = array()) { $sql = ""; foreach ($this->columnOwnerMap as $columnName => $dqlAlias) { $tableAlias = isset($tableAliases[$dqlAlias]) ? $tableAliases[$dqlAlias] : $dqlAlias; if ($sql) { $sql .= ", "; } $sql .= $tableAlias . "."; if (isset($this->fieldMappings[$columnName])) { $class = $this->em->getClassMetadata($this->declaringClasses[$columnName]); $sql .= $class->fieldMappings[$this->fieldMappings[$columnName]]['columnName']; } else if (isset($this->metaMappings[$columnName])) { $sql .= $this->metaMappings[$columnName]; } else if (isset($this->discriminatorColumn[$columnName])) { $sql .= $this->discriminatorColumn[$columnName]; } $sql .= " AS " . $columnName; } return $sql; } /** * @return string */ public function __toString() { return $this->generateSelectClause(array()); } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/SqlWalker.php000066400000000000000000002372571257105210500221310ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs * the corresponding SQL. * * @author Guilherme Blanco * @author Roman Borschel * @author Benjamin Eberlei * @author Alexander * @author Fabio B. Silva * @since 2.0 * @todo Rename: SQLWalker */ class SqlWalker implements TreeWalker { /** * @var string */ const HINT_DISTINCT = 'doctrine.distinct'; /** * @var ResultSetMapping */ private $rsm; /** * Counter for generating unique column aliases. * * @var integer */ private $aliasCounter = 0; /** * Counter for generating unique table aliases. * * @var integer */ private $tableAliasCounter = 0; /** * Counter for generating unique scalar result. * * @var integer */ private $scalarResultCounter = 1; /** * Counter for generating unique parameter indexes. * * @var integer */ private $sqlParamIndex = 0; /** * Counter for generating indexes. * * @var integer */ private $newObjectCounter = 0; /** * @var ParserResult */ private $parserResult; /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * @var \Doctrine\DBAL\Connection */ private $conn; /** * @var \Doctrine\ORM\AbstractQuery */ private $query; /** * @var array */ private $tableAliasMap = array(); /** * Map from result variable names to their SQL column alias names. * * @var array */ private $scalarResultAliasMap = array(); /** * Map from Table-Alias + Column-Name to OrderBy-Direction. * * @var array */ private $orderedColumnsMap = array(); /** * Map from DQL-Alias + Field-Name to SQL Column Alias. * * @var array */ private $scalarFields = array(); /** * Map of all components/classes that appear in the DQL query. * * @var array */ private $queryComponents; /** * A list of classes that appear in non-scalar SelectExpressions. * * @var array */ private $selectedClasses = array(); /** * The DQL alias of the root class of the currently traversed query. * * @var array */ private $rootAliases = array(); /** * Flag that indicates whether to generate SQL table aliases in the SQL. * These should only be generated for SELECT queries, not for UPDATE/DELETE. * * @var boolean */ private $useSqlTableAliases = true; /** * The database platform abstraction. * * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ private $platform; /** * The quote strategy. * * @var \Doctrine\ORM\Mapping\QuoteStrategy */ private $quoteStrategy; /** * {@inheritDoc} */ public function __construct($query, $parserResult, array $queryComponents) { $this->query = $query; $this->parserResult = $parserResult; $this->queryComponents = $queryComponents; $this->rsm = $parserResult->getResultSetMapping(); $this->em = $query->getEntityManager(); $this->conn = $this->em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy(); } /** * Gets the Query instance used by the walker. * * @return Query. */ public function getQuery() { return $this->query; } /** * Gets the Connection used by the walker. * * @return \Doctrine\DBAL\Connection */ public function getConnection() { return $this->conn; } /** * Gets the EntityManager used by the walker. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } /** * Gets the information about a single query component. * * @param string $dqlAlias The DQL alias. * * @return array */ public function getQueryComponent($dqlAlias) { return $this->queryComponents[$dqlAlias]; } /** * {@inheritdoc} */ public function getQueryComponents() { return $this->queryComponents; } /** * {@inheritdoc} */ public function setQueryComponent($dqlAlias, array $queryComponent) { $requiredKeys = array('metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'); if (array_diff($requiredKeys, array_keys($queryComponent))) { throw QueryException::invalidQueryComponent($dqlAlias); } $this->queryComponents[$dqlAlias] = $queryComponent; } /** * {@inheritdoc} */ public function getExecutor($AST) { switch (true) { case ($AST instanceof AST\DeleteStatement): $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName); return ($primaryClass->isInheritanceTypeJoined()) ? new Exec\MultiTableDeleteExecutor($AST, $this) : new Exec\SingleTableDeleteUpdateExecutor($AST, $this); case ($AST instanceof AST\UpdateStatement): $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName); return ($primaryClass->isInheritanceTypeJoined()) ? new Exec\MultiTableUpdateExecutor($AST, $this) : new Exec\SingleTableDeleteUpdateExecutor($AST, $this); default: return new Exec\SingleSelectExecutor($AST, $this); } } /** * Generates a unique, short SQL table alias. * * @param string $tableName Table name * @param string $dqlAlias The DQL alias. * * @return string Generated table alias. */ public function getSQLTableAlias($tableName, $dqlAlias = '') { $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : ''; if ( ! isset($this->tableAliasMap[$tableName])) { $this->tableAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->tableAliasCounter++ . '_'; } return $this->tableAliasMap[$tableName]; } /** * Forces the SqlWalker to use a specific alias for a table name, rather than * generating an alias on its own. * * @param string $tableName * @param string $alias * @param string $dqlAlias * * @return string */ public function setSQLTableAlias($tableName, $alias, $dqlAlias = '') { $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : ''; $this->tableAliasMap[$tableName] = $alias; return $alias; } /** * Gets an SQL column alias for a column name. * * @param string $columnName * * @return string */ public function getSQLColumnAlias($columnName) { return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform); } /** * Generates the SQL JOINs that are necessary for Class Table Inheritance * for the given class. * * @param ClassMetadata $class The class for which to generate the joins. * @param string $dqlAlias The DQL alias of the class. * * @return string The SQL. */ private function _generateClassTableInheritanceJoins($class, $dqlAlias) { $sql = ''; $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->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 ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; $sqlParts = array(); foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) { $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } // Add filters on the root class if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) { $sqlParts[] = $filterSql; } $sql .= implode(' AND ', $sqlParts); } // Ignore subclassing inclusion if partial objects is disallowed if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { return $sql; } // LEFT JOIN child class tables foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON '; $sqlParts = array(); foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) { $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } $sql .= implode(' AND ', $sqlParts); } return $sql; } /** * @return string */ private function _generateOrderedCollectionOrderByItems() { $orderedColumns = array(); foreach ($this->selectedClasses as $selectedClass) { $dqlAlias = $selectedClass['dqlAlias']; $qComp = $this->queryComponents[$dqlAlias]; $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name); if ( ! isset($qComp['relation']['orderBy'])) { continue; } foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) { $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform); $tableName = ($qComp['metadata']->isInheritanceTypeJoined()) ? $persister->getOwningTable($fieldName) : $qComp['metadata']->getTableName(); $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName; // OrderByClause should replace an ordered relation. see - DDC-2475 if (isset($this->orderedColumnsMap[$orderedColumn])) { continue; } $this->orderedColumnsMap[$orderedColumn] = $orientation; $orderedColumns[] = $orderedColumn . ' ' . $orientation; } } return implode(', ', $orderedColumns); } /** * Generates a discriminator column SQL condition for the class with the given DQL alias. * * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions. * * @return string */ private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases) { $sqlParts = array(); foreach ($dqlAliases as $dqlAlias) { $class = $this->queryComponents[$dqlAlias]['metadata']; if ( ! $class->isInheritanceTypeSingleTable()) continue; $conn = $this->em->getConnection(); $values = array(); if ($class->discriminatorValue !== null) { // discriminators can be 0 $values[] = $conn->quote($class->discriminatorValue); } foreach ($class->subClasses as $subclassName) { $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue); } $sqlParts[] = (($this->useSqlTableAliases) ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' : '') . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')'; } $sql = implode(' AND ', $sqlParts); return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql; } /** * Generates the filter SQL for a given entity and table alias. * * @param ClassMetadata $targetEntity Metadata of the target entity. * @param string $targetTableAlias The table alias of the joined/selected table. * * @return string The SQL query part to add to a query. */ private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { if (!$this->em->hasFilters()) { return ''; } switch($targetEntity->inheritanceType) { case ClassMetadata::INHERITANCE_TYPE_NONE: break; case ClassMetadata::INHERITANCE_TYPE_JOINED: // The classes in the inheritance will be added to the query one by one, // but only the root node is getting filtered if ($targetEntity->name !== $targetEntity->rootEntityName) { return ''; } break; case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE: // With STI the table will only be queried once, make sure that the filters // are added to the root entity $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName); break; default: //@todo: throw exception? return ''; break; } $filterClauses = array(); foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { $filterClauses[] = '(' . $filterExpr . ')'; } } return implode(' AND ', $filterClauses); } /** * {@inheritdoc} */ public function walkSelectStatement(AST\SelectStatement $AST) { $limit = $this->query->getMaxResults(); $offset = $this->query->getFirstResult(); $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE); $sql = $this->walkSelectClause($AST->selectClause) . $this->walkFromClause($AST->fromClause) . $this->walkWhereClause($AST->whereClause); if ($AST->groupByClause) { $sql .= $this->walkGroupByClause($AST->groupByClause); } if ($AST->havingClause) { $sql .= $this->walkHavingClause($AST->havingClause); } if ($AST->orderByClause) { $sql .= $this->walkOrderByClause($AST->orderByClause); } if ( ! $AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) { $sql .= ' ORDER BY ' . $orderBySql; } if ($limit !== null || $offset !== null) { $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset); } if ($lockMode === false || $lockMode === LockMode::NONE) { return $sql; } if ($lockMode === LockMode::PESSIMISTIC_READ) { return $sql . ' ' . $this->platform->getReadLockSQL(); } if ($lockMode === LockMode::PESSIMISTIC_WRITE) { return $sql . ' ' . $this->platform->getWriteLockSQL(); } if ($lockMode !== LockMode::OPTIMISTIC) { throw QueryException::invalidLockMode(); } foreach ($this->selectedClasses as $selectedClass) { if ( ! $selectedClass['class']->isVersioned) { throw OptimisticLockException::lockFailed($selectedClass['class']->name); } } return $sql; } /** * {@inheritdoc} */ public function walkUpdateStatement(AST\UpdateStatement $AST) { $this->useSqlTableAliases = false; return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause); } /** * {@inheritdoc} */ public function walkDeleteStatement(AST\DeleteStatement $AST) { $this->useSqlTableAliases = false; return $this->walkDeleteClause($AST->deleteClause) . $this->walkWhereClause($AST->whereClause); } /** * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL. * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers. * * @param string $identVariable * * @return string */ public function walkEntityIdentificationVariable($identVariable) { $class = $this->queryComponents[$identVariable]['metadata']; $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable); $sqlParts = array(); foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) { $sqlParts[] = $tableAlias . '.' . $columnName; } return implode(', ', $sqlParts); } /** * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL. * * @param string $identificationVariable * @param string $fieldName * * @return string The SQL. */ public function walkIdentificationVariable($identificationVariable, $fieldName = null) { $class = $this->queryComponents[$identificationVariable]['metadata']; if ( $fieldName !== null && $class->isInheritanceTypeJoined() && isset($class->fieldMappings[$fieldName]['inherited']) ) { $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']); } return $this->getSQLTableAlias($class->getTableName(), $identificationVariable); } /** * {@inheritdoc} */ public function walkPathExpression($pathExpr) { $sql = ''; switch ($pathExpr->type) { case AST\PathExpression::TYPE_STATE_FIELD: $fieldName = $pathExpr->field; $dqlAlias = $pathExpr->identificationVariable; $class = $this->queryComponents[$dqlAlias]['metadata']; if ($this->useSqlTableAliases) { $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.'; } $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); break; case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION: // 1- the owning side: // Just use the foreign key, i.e. u.group_id $fieldName = $pathExpr->field; $dqlAlias = $pathExpr->identificationVariable; $class = $this->queryComponents[$dqlAlias]['metadata']; if (isset($class->associationMappings[$fieldName]['inherited'])) { $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']); } $assoc = $class->associationMappings[$fieldName]; 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: throw QueryException::invalidPathExpression($pathExpr); } return $sql; } /** * {@inheritdoc} */ public function walkSelectClause($selectClause) { $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : ''); $sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions)); if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) { $this->query->setHint(self::HINT_DISTINCT, true); } $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) && $this->query->getHydrationMode() == Query::HYDRATE_OBJECT || $this->query->getHydrationMode() != Query::HYDRATE_OBJECT && $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS); foreach ($this->selectedClasses as $selectedClass) { $class = $selectedClass['class']; $dqlAlias = $selectedClass['dqlAlias']; $resultAlias = $selectedClass['resultAlias']; // Register as entity or joined entity result if ($this->queryComponents[$dqlAlias]['relation'] === null) { $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias); } else { $this->rsm->addJoinedEntityResult( $class->name, $dqlAlias, $this->queryComponents[$dqlAlias]['parent'], $this->queryComponents[$dqlAlias]['relation']['fieldName'] ); } if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { // Add discriminator columns to SQL $rootClass = $this->em->getClassMetadata($class->rootEntityName); $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias); $discrColumn = $rootClass->discriminatorColumn; $columnAlias = $this->getSQLColumnAlias($discrColumn['name']); $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias; $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); } // Add foreign key columns to SQL, if necessary if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) { continue; } // Add foreign key columns of class and also parent classes foreach ($class->associationMappings as $assoc) { if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { continue; } else if ( !$addMetaColumns && !isset($assoc['id'])) { continue; } $owningClass = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class; $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias); foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { $columnAlias = $this->getSQLColumnAlias($srcColumn); $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; $this->rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); } } // Add foreign key columns to SQL, if necessary if ( ! $addMetaColumns) { continue; } // Add foreign key columns of subclasses foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); foreach ($subClass->associationMappings as $assoc) { // Skip if association is inherited if (isset($assoc['inherited'])) continue; if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { $columnAlias = $this->getSQLColumnAlias($srcColumn); $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; $this->rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); } } } } $sql .= implode(', ', $sqlSelectExpressions); return $sql; } /** * {@inheritdoc} */ public function walkFromClause($fromClause) { $identificationVarDecls = $fromClause->identificationVariableDeclarations; $sqlParts = array(); foreach ($identificationVarDecls as $identificationVariableDecl) { $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration); foreach ($identificationVariableDecl->joins as $join) { $sql .= $this->walkJoin($join); } if ($identificationVariableDecl->indexBy) { $alias = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable; $field = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field; if (isset($this->scalarFields[$alias][$field])) { $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]); } else { $this->rsm->addIndexBy( $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field ); } } $sqlParts[] = $sql; } return ' FROM ' . implode(', ', $sqlParts); } /** * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL. * * @param AST\RangeVariableDeclaration $rangeVariableDeclaration * * @return string */ public function walkRangeVariableDeclaration($rangeVariableDeclaration) { $class = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName); $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable; if ($rangeVariableDeclaration->isRoot) { $this->rootAliases[] = $dqlAlias; } $sql = $this->platform->appendLockHint( $this->quoteStrategy->getTableName($class, $this->platform) . ' ' . $this->getSQLTableAlias($class->getTableName(), $dqlAlias), $this->query->getHint(Query::HINT_LOCK_MODE) ); if ($class->isInheritanceTypeJoined()) { $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias); } return $sql; } /** * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL. * * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration * @param int $joinType * @param AST\ConditionalExpression $condExpr * * @return string * * @throws QueryException */ public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null) { $sql = ''; $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression; $joinedDqlAlias = $joinAssociationDeclaration->aliasIdentificationVariable; $indexBy = $joinAssociationDeclaration->indexBy; $relation = $this->queryComponents[$joinedDqlAlias]['relation']; $targetClass = $this->em->getClassMetadata($relation['targetEntity']); $sourceClass = $this->em->getClassMetadata($relation['sourceEntity']); $targetTableName = $this->quoteStrategy->getTableName($targetClass,$this->platform); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias); $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable); // Ensure we got the owning side, since it has all mapping info $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation; if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) { if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) { throw QueryException::iterateWithFetchJoinNotAllowed($assoc); } } $targetTableJoin = null; // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot // be the owning side and previously we ensured that $assoc is always the owning side of the associations. // The owning side is necessary at this point because only it contains the JoinColumn information. switch (true) { case ($assoc['type'] & ClassMetadata::TO_ONE): $conditions = array(); foreach ($assoc['joinColumns'] as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); if ($relation['isOwningSide']) { $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn; continue; } $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn; } // Apply remaining inheritance restrictions $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias)); if ($discrSql) { $conditions[] = $discrSql; } // Apply the filters $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias); if ($filterExpr) { $conditions[] = $filterExpr; } $targetTableJoin = array( 'table' => $targetTableName . ' ' . $targetTableAlias, 'condition' => implode(' AND ', $conditions), ); break; case ($assoc['type'] == ClassMetadata::MANY_TO_MANY): // Join relation table $joinTable = $assoc['joinTable']; $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias); $joinTableName = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform); $conditions = array(); $relationColumns = ($relation['isOwningSide']) ? $assoc['joinTable']['joinColumns'] : $assoc['joinTable']['inverseJoinColumns']; foreach ($relationColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn; } $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions); // Join target table $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN '; $conditions = array(); $relationColumns = ($relation['isOwningSide']) ? $assoc['joinTable']['inverseJoinColumns'] : $assoc['joinTable']['joinColumns']; foreach ($relationColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn; } // Apply remaining inheritance restrictions $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias)); if ($discrSql) { $conditions[] = $discrSql; } // Apply the filters $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias); if ($filterExpr) { $conditions[] = $filterExpr; } $targetTableJoin = array( 'table' => $targetTableName . ' ' . $targetTableAlias, 'condition' => implode(' AND ', $conditions), ); break; } // Handle WITH clause $withCondition = (null === $condExpr) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')'); if ($targetClass->isInheritanceTypeJoined()) { $ctiJoins = $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias); // If we have WITH condition, we need to build nested joins for target class table and cti joins if ($withCondition) { $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition']; } else { $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins; } } else { $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition']; } if ($withCondition) { $sql .= ' AND ' . $withCondition; } // Apply the indexes if ($indexBy) { // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. $this->rsm->addIndexBy( $indexBy->simpleStateFieldPathExpression->identificationVariable, $indexBy->simpleStateFieldPathExpression->field ); } else if (isset($relation['indexBy'])) { $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']); } return $sql; } /** * {@inheritdoc} */ public function walkFunction($function) { return $function->getSql($this); } /** * {@inheritdoc} */ public function walkOrderByClause($orderByClause) { $orderByItems = array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems); if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') { $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems); } return ' ORDER BY ' . implode(', ', $orderByItems); } /** * {@inheritdoc} */ public function walkOrderByItem($orderByItem) { $type = strtoupper($orderByItem->type); $expr = $orderByItem->expression; $sql = ($expr instanceof AST\Node) ? $expr->dispatch($this) : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']); $this->orderedColumnsMap[$sql] = $type; return $sql . ' ' . $type; } /** * {@inheritdoc} */ public function walkHavingClause($havingClause) { return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression); } /** * {@inheritdoc} */ public function walkJoin($join) { $joinType = $join->joinType; $joinDeclaration = $join->joinAssociationDeclaration; $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN '; switch (true) { case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\RangeVariableDeclaration): $class = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName); $dqlAlias = $joinDeclaration->aliasIdentificationVariable; $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); $condition = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')'; $condExprConjunction = ($class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER) ? ' AND ' : ' ON '; $sql .= $this->walkRangeVariableDeclaration($joinDeclaration); $conditions = array($condition); // Apply remaining inheritance restrictions $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($dqlAlias)); if ($discrSql) { $conditions[] = $discrSql; } // Apply the filters $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias); if ($filterExpr) { $conditions[] = $filterExpr; } $sql .= $condExprConjunction . implode(' AND ', $conditions); break; case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration): $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression); break; } return $sql; } /** * Walks down a CaseExpression AST node and generates the corresponding SQL. * * @param AST\CoalesceExpression|AST\NullIfExpression|AST\GeneralCaseExpression|AST\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 AST\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 AST\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 AST\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 AST\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; } /** * {@inheritdoc} */ public function walkSelectExpression($selectExpression) { $sql = ''; $expr = $selectExpression->expression; $hidden = $selectExpression->hiddenAliasResultVariable; switch (true) { case ($expr instanceof AST\PathExpression): if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { throw QueryException::invalidPathExpression($expr); } $fieldName = $expr->field; $dqlAlias = $expr->identificationVariable; $qComp = $this->queryComponents[$dqlAlias]; $class = $qComp['metadata']; $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName; $tableName = ($class->isInheritanceTypeJoined()) ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) : $class->getTableName(); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $columnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); $columnAlias = $this->getSQLColumnAlias($class->fieldMappings[$fieldName]['columnName']); $col = $sqlTableAlias . '.' . $columnName; $fieldType = $class->getTypeOfField($fieldName); if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) { $type = Type::getType($fieldType); $col = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform()); } $sql .= $col . ' AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType); $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias; } break; case ($expr instanceof AST\AggregateExpression): case ($expr instanceof AST\Functions\FunctionNode): case ($expr instanceof AST\SimpleArithmeticExpression): case ($expr instanceof AST\ArithmeticTerm): case ($expr instanceof AST\ArithmeticFactor): case ($expr instanceof AST\ParenthesisExpression): case ($expr instanceof AST\Literal): case ($expr instanceof AST\NullIfExpression): case ($expr instanceof AST\CoalesceExpression): case ($expr instanceof AST\GeneralCaseExpression): case ($expr instanceof AST\SimpleCaseExpression): $columnAlias = $this->getSQLColumnAlias('sclr'); $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { // We cannot resolve field type here; assume 'string'. $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string'); } break; case ($expr instanceof AST\Subselect): $columnAlias = $this->getSQLColumnAlias('sclr'); $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { // We cannot resolve field type here; assume 'string'. $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string'); } break; case ($expr instanceof AST\NewObjectExpression): $sql .= $this->walkNewObject($expr); break; default: // IdentificationVariable or PartialObjectExpression if ($expr instanceof AST\PartialObjectExpression) { $dqlAlias = $expr->identificationVariable; $partialFieldSet = $expr->partialFieldSet; } else { $dqlAlias = $expr; $partialFieldSet = array(); } $queryComp = $this->queryComponents[$dqlAlias]; $class = $queryComp['metadata']; $resultAlias = $selectExpression->fieldIdentificationVariable ?: null; if ( ! isset($this->selectedClasses[$dqlAlias])) { $this->selectedClasses[$dqlAlias] = array( 'class' => $class, 'dqlAlias' => $dqlAlias, 'resultAlias' => $resultAlias ); } $sqlParts = array(); // Select all fields from the queried class foreach ($class->fieldMappings as $fieldName => $mapping) { if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) { continue; } $tableName = (isset($mapping['inherited'])) ? $this->em->getClassMetadata($mapping['inherited'])->getTableName() : $class->getTableName(); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); $col = $sqlTableAlias . '.' . $quotedColumnName; if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) { $type = Type::getType($class->getTypeOfField($fieldName)); $col = $type->convertToPHPValueSQL($col, $this->platform); } $sqlParts[] = $col . ' AS '. $columnAlias; $this->scalarResultAliasMap[$resultAlias][] = $columnAlias; $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); } // Add any additional fields of subclasses (excluding inherited fields) // 1) on Single Table Inheritance: always, since its marginal overhead // 2) on Class Table Inheritance only if partial objects are disallowed, // since it requires outer joining subtables. 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->getTableName(), $dqlAlias); foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { continue; } $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform); $col = $sqlTableAlias . '.' . $quotedColumnName; if (isset($subClass->fieldMappings[$fieldName]['requireSQLConversion'])) { $type = Type::getType($subClass->getTypeOfField($fieldName)); $col = $type->convertToPHPValueSQL($col, $this->platform); } $sqlParts[] = $col . ' AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias][] = $columnAlias; $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); } } } $sql .= implode(', ', $sqlParts); } return $sql; } /** * {@inheritdoc} */ public function walkQuantifiedExpression($qExpr) { return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')'; } /** * {@inheritdoc} */ 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 .= $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; } /** * {@inheritdoc} */ public function walkSubselectFromClause($subselectFromClause) { $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations; $sqlParts = array (); foreach ($identificationVarDecls as $subselectIdVarDecl) { $sql = $this->walkRangeVariableDeclaration($subselectIdVarDecl->rangeVariableDeclaration); foreach ($subselectIdVarDecl->joins as $join) { $sql .= $this->walkJoin($join); } $sqlParts[] = $sql; } return ' FROM ' . implode(', ', $sqlParts); } /** * {@inheritdoc} */ public function walkSimpleSelectClause($simpleSelectClause) { return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '') . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression); } /** * @param \Doctrine\ORM\Query\AST\ParenthesisExpression $parenthesisExpression * * @return string. */ public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression) { return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this)); } /** * @param AST\NewObjectExpression $newObjectExpression * * @return string The SQL. */ public function walkNewObject($newObjectExpression) { $sqlSelectExpressions = array(); $objIndex = $this->newObjectCounter++; foreach ($newObjectExpression->args as $argIndex => $e) { $resultAlias = $this->scalarResultCounter++; $columnAlias = $this->getSQLColumnAlias('sclr'); switch (true) { case ($e instanceof AST\NewObjectExpression): $sqlSelectExpressions[] = $e->dispatch($this); break; default: $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias; break; } $fieldType = 'string'; switch (true) { case ($e instanceof AST\PathExpression): $fieldName = $e->field; $dqlAlias = $e->identificationVariable; $qComp = $this->queryComponents[$dqlAlias]; $class = $qComp['metadata']; $fieldType = $class->getTypeOfField($fieldName); break; case ($e instanceof AST\Literal): switch ($e->type) { case AST\Literal::BOOLEAN: $fieldType = 'boolean'; break; case AST\Literal::NUMERIC: $fieldType = is_float($e->value) ? 'float' : 'integer'; break; } break; } $this->scalarResultAliasMap[$resultAlias] = $columnAlias; $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType); $this->rsm->newObjectMappings[$columnAlias] = array( 'className' => $newObjectExpression->className, 'objIndex' => $objIndex, 'argIndex' => $argIndex ); } return implode(', ', $sqlSelectExpressions); } /** * {@inheritdoc} */ public function walkSimpleSelectExpression($simpleSelectExpression) { $expr = $simpleSelectExpression->expression; $sql = ' '; switch (true) { case ($expr instanceof AST\PathExpression): $sql .= $this->walkPathExpression($expr); break; case ($expr instanceof AST\AggregateExpression): $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; break; case ($expr instanceof AST\Subselect): $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $columnAlias = 'sclr' . $this->aliasCounter++; $this->scalarResultAliasMap[$alias] = $columnAlias; $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; break; case ($expr instanceof AST\Functions\FunctionNode): case ($expr instanceof AST\SimpleArithmeticExpression): case ($expr instanceof AST\ArithmeticTerm): case ($expr instanceof AST\ArithmeticFactor): case ($expr instanceof AST\Literal): case ($expr instanceof AST\NullIfExpression): case ($expr instanceof AST\CoalesceExpression): case ($expr instanceof AST\GeneralCaseExpression): case ($expr instanceof AST\SimpleCaseExpression): $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $columnAlias = $this->getSQLColumnAlias('sclr'); $this->scalarResultAliasMap[$alias] = $columnAlias; $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; break; default: // IdentificationVariable $sql .= $this->walkEntityIdentificationVariable($expr); break; } return $sql; } /** * {@inheritdoc} */ public function walkAggregateExpression($aggExpression) { return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '') . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')'; } /** * {@inheritdoc} */ public function walkGroupByClause($groupByClause) { $sqlParts = array(); foreach ($groupByClause->groupByItems as $groupByItem) { $sqlParts[] = $this->walkGroupByItem($groupByItem); } return ' GROUP BY ' . implode(', ', $sqlParts); } /** * {@inheritdoc} */ public function walkGroupByItem($groupByItem) { // StateFieldPathExpression if ( ! is_string($groupByItem)) { return $this->walkPathExpression($groupByItem); } // ResultVariable if (isset($this->queryComponents[$groupByItem]['resultVariable'])) { return $this->walkResultVariable($groupByItem); } // IdentificationVariable $sqlParts = array(); foreach ($this->queryComponents[$groupByItem]['metadata']->fieldNames as $field) { $item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field); $item->type = AST\PathExpression::TYPE_STATE_FIELD; $sqlParts[] = $this->walkPathExpression($item); } foreach ($this->queryComponents[$groupByItem]['metadata']->associationMappings as $mapping) { if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) { $item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']); $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; $sqlParts[] = $this->walkPathExpression($item); } } return implode(', ', $sqlParts); } /** * {@inheritdoc} */ public function walkDeleteClause(AST\DeleteClause $deleteClause) { $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName); $tableName = $class->getTableName(); $sql = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform); $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable); $this->rootAliases[] = $deleteClause->aliasIdentificationVariable; return $sql; } /** * {@inheritdoc} */ public function walkUpdateClause($updateClause) { $class = $this->em->getClassMetadata($updateClause->abstractSchemaName); $tableName = $class->getTableName(); $sql = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform); $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable); $this->rootAliases[] = $updateClause->aliasIdentificationVariable; $sql .= ' SET ' . implode(', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems)); return $sql; } /** * {@inheritdoc} */ public function walkUpdateItem($updateItem) { $useTableAliasesBefore = $this->useSqlTableAliases; $this->useSqlTableAliases = false; $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; $newValue = $updateItem->newValue; switch (true) { case ($newValue instanceof AST\Node): $sql .= $newValue->dispatch($this); break; case ($newValue === null): $sql .= 'NULL'; break; default: $sql .= $this->conn->quote($newValue); break; } $this->useSqlTableAliases = $useTableAliasesBefore; return $sql; } /** * {@inheritdoc} */ public function walkWhereClause($whereClause) { $condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : ''; $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->rootAliases); if ($this->em->hasFilters()) { $filterClauses = array(); foreach ($this->rootAliases as $dqlAlias) { $class = $this->queryComponents[$dqlAlias]['metadata']; $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) { $filterClauses[] = $filterExpr; } } if (count($filterClauses)) { if ($condSql) { $condSql = '(' . $condSql . ') AND '; } $condSql .= implode(' AND ', $filterClauses); } } if ($condSql) { return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql); } if ($discrSql) { return ' WHERE ' . $discrSql; } return ''; } /** * {@inheritdoc} */ public function walkConditionalExpression($condExpr) { // Phase 2 AST optimization: Skip processing of ConditionalExpression // if only one ConditionalTerm is defined if ( ! ($condExpr instanceof AST\ConditionalExpression)) { return $this->walkConditionalTerm($condExpr); } return implode(' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms)); } /** * {@inheritdoc} */ public function walkConditionalTerm($condTerm) { // Phase 2 AST optimization: Skip processing of ConditionalTerm // if only one ConditionalFactor is defined if ( ! ($condTerm instanceof AST\ConditionalTerm)) { return $this->walkConditionalFactor($condTerm); } return implode(' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors)); } /** * {@inheritdoc} */ public function walkConditionalFactor($factor) { // Phase 2 AST optimization: Skip processing of ConditionalFactor // if only one ConditionalPrimary is defined return ( ! ($factor instanceof AST\ConditionalFactor)) ? $this->walkConditionalPrimary($factor) : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary); } /** * {@inheritdoc} */ public function walkConditionalPrimary($primary) { if ($primary->isSimpleConditionalExpression()) { return $primary->simpleConditionalExpression->dispatch($this); } if ($primary->isConditionalExpression()) { $condExpr = $primary->conditionalExpression; return '(' . $this->walkConditionalExpression($condExpr) . ')'; } } /** * {@inheritdoc} */ public function walkExistsExpression($existsExpr) { $sql = ($existsExpr->not) ? 'NOT ' : ''; $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')'; return $sql; } /** * {@inheritdoc} */ public function walkCollectionMemberExpression($collMemberExpr) { $sql = $collMemberExpr->not ? 'NOT ' : ''; $sql .= 'EXISTS (SELECT 1 FROM '; $entityExpr = $collMemberExpr->entityExpression; $collPathExpr = $collMemberExpr->collectionValuedPathExpression; $fieldName = $collPathExpr->field; $dqlAlias = $collPathExpr->identificationVariable; $class = $this->queryComponents[$dqlAlias]['metadata']; switch (true) { // InputParameter case ($entityExpr instanceof AST\InputParameter): $dqlParamKey = $entityExpr->name; $entitySql = '?'; break; // SingleValuedAssociationPathExpression | IdentificationVariable case ($entityExpr instanceof AST\PathExpression): $entitySql = $this->walkPathExpression($entityExpr); break; default: throw new \BadMethodCallException("Not implemented"); } $assoc = $class->associationMappings[$fieldName]; if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE '; $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; $sqlParts = array(); foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform); $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; } foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) { if (isset($dqlParamKey)) { $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); } $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql; } $sql .= implode(' AND ', $sqlParts); } else { // many-to-many $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']]; $joinTable = $owningAssoc['joinTable']; // SQL table aliases $joinTableAlias = $this->getSQLTableAlias($joinTable['name']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); // join to target table $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias . ' INNER JOIN ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' ON '; // join conditions $joinColumns = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns']; $joinSqlParts = array(); foreach ($joinColumns as $joinColumn) { $targetColumn = $this->quoteStrategy->getColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $targetClass, $this->platform); $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn; } $sql .= implode(' AND ', $joinSqlParts); $sql .= ' WHERE '; $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; $sqlParts = array(); foreach ($joinColumns as $joinColumn) { $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform); $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn; } foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) { if (isset($dqlParamKey)) { $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); } $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql; } $sql .= implode(' AND ', $sqlParts); } return $sql . ')'; } /** * {@inheritdoc} */ public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) { $sizeFunc = new AST\Functions\SizeFunction('size'); $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression; return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0'); } /** * {@inheritdoc} */ public function walkNullComparisonExpression($nullCompExpr) { $expression = $nullCompExpr->expression; $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL'; if ($expression instanceof AST\InputParameter) { $this->parserResult->addParameterMapping($expression->name, $this->sqlParamIndex++); return '?' . $comparison; } return $expression->dispatch($this) . $comparison; } /** * {@inheritdoc} */ public function walkInExpression($inExpr) { $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN ('; $sql .= ($inExpr->subselect) ? $this->walkSubselect($inExpr->subselect) : implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals)); $sql .= ')'; return $sql; } /** * {@inheritdoc} */ public function walkInstanceOfExpression($instanceOfExpr) { $sql = ''; $dqlAlias = $instanceOfExpr->identificationVariable; $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata']; if ($class->discriminatorColumn) { $discrClass = $this->em->getClassMetadata($class->rootEntityName); } if ($this->useSqlTableAliases) { $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) { $this->rsm->addMetadataParameterMapping($parameter->name, 'discriminatorValue'); $sqlParameterList[] = $this->walkInputParameter($parameter); continue; } // Get name from ClassMetadata to resolve aliases. $entityClassName = $this->em->getClassMetadata($parameter)->name; $discriminatorValue = $class->discriminatorValue; if ($entityClassName !== $class->name) { $discrMap = array_flip($class->discriminatorMap); if ( ! isset($discrMap[$entityClassName])) { throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName); } $discriminatorValue = $discrMap[$entityClassName]; } $sqlParameterList[] = $this->conn->quote($discriminatorValue); } $sql .= '(' . implode(', ', $sqlParameterList) . ')'; return $sql; } /** * {@inheritdoc} */ public function walkInParameter($inParam) { return $inParam instanceof AST\InputParameter ? $this->walkInputParameter($inParam) : $this->walkLiteral($inParam); } /** * {@inheritdoc} */ public function walkLiteral($literal) { 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 $boolVal; case AST\Literal::NUMERIC: return $literal->value; default: throw QueryException::invalidLiteral($literal); } } /** * {@inheritdoc} */ public function walkBetweenExpression($betweenExpr) { $sql = $this->walkArithmeticExpression($betweenExpr->expression); if ($betweenExpr->not) $sql .= ' NOT'; $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression) . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression); return $sql; } /** * {@inheritdoc} */ public function walkLikeExpression($likeExpr) { $stringExpr = $likeExpr->stringExpression; $sql = $stringExpr->dispatch($this) . ($likeExpr->not ? ' NOT' : '') . ' LIKE '; if ($likeExpr->stringPattern instanceof AST\InputParameter) { $inputParam = $likeExpr->stringPattern; $dqlParamKey = $inputParam->name; $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); $sql .= '?'; } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode ) { $sql .= $this->walkFunction($likeExpr->stringPattern); } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) { $sql .= $this->walkPathExpression($likeExpr->stringPattern); } else { $sql .= $this->walkLiteral($likeExpr->stringPattern); } if ($likeExpr->escapeChar) { $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar); } return $sql; } /** * {@inheritdoc} */ public function walkStateFieldPathExpression($stateFieldPathExpression) { return $this->walkPathExpression($stateFieldPathExpression); } /** * {@inheritdoc} */ public function walkComparisonExpression($compExpr) { $leftExpr = $compExpr->leftExpression; $rightExpr = $compExpr->rightExpression; $sql = ''; $sql .= ($leftExpr instanceof AST\Node) ? $leftExpr->dispatch($this) : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr)); $sql .= ' ' . $compExpr->operator . ' '; $sql .= ($rightExpr instanceof AST\Node) ? $rightExpr->dispatch($this) : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr)); return $sql; } /** * {@inheritdoc} */ public function walkInputParameter($inputParam) { $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++); return '?'; } /** * {@inheritdoc} */ public function walkArithmeticExpression($arithmeticExpr) { return ($arithmeticExpr->isSimpleArithmeticExpression()) ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression) : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')'; } /** * {@inheritdoc} */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) { if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) { return $this->walkArithmeticTerm($simpleArithmeticExpr); } return implode(' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms)); } /** * {@inheritdoc} */ public function walkArithmeticTerm($term) { if (is_string($term)) { return (isset($this->queryComponents[$term])) ? $this->walkResultVariable($this->queryComponents[$term]['token']['value']) : $term; } // Phase 2 AST optimization: Skip processing of ArithmeticTerm // if only one ArithmeticFactor is defined if ( ! ($term instanceof AST\ArithmeticTerm)) { return $this->walkArithmeticFactor($term); } return implode(' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors)); } /** * {@inheritdoc} */ public function walkArithmeticFactor($factor) { if (is_string($factor)) { return $factor; } // Phase 2 AST optimization: Skip processing of ArithmeticFactor // if only one ArithmeticPrimary is defined if ( ! ($factor instanceof AST\ArithmeticFactor)) { return $this->walkArithmeticPrimary($factor); } $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : ''); return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary); } /** * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL. * * @param mixed $primary * * @return string The SQL. */ public function walkArithmeticPrimary($primary) { if ($primary instanceof AST\SimpleArithmeticExpression) { return '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; } if ($primary instanceof AST\Node) { return $primary->dispatch($this); } return $this->walkEntityIdentificationVariable($primary); } /** * {@inheritdoc} */ public function walkStringPrimary($stringPrimary) { return (is_string($stringPrimary)) ? $this->conn->quote($stringPrimary) : $stringPrimary->dispatch($this); } /** * {@inheritdoc} */ public function walkResultVariable($resultVariable) { $resultAlias = $this->scalarResultAliasMap[$resultVariable]; if (is_array($resultAlias)) { return implode(', ', $resultAlias); } return $resultAlias; } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/TreeWalker.php000066400000000000000000000327111257105210500222550ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; /** * Interface for walkers of DQL ASTs (abstract syntax trees). * * @author Roman Borschel * @since 2.0 */ interface TreeWalker { /** * Initializes TreeWalker with important information about the ASTs to be walked. * * @param \Doctrine\ORM\AbstractQuery $query The parsed Query. * @param \Doctrine\ORM\Query\ParserResult $parserResult The result of the parsing process. * @param array $queryComponents The query components (symbol table). */ public function __construct($query, $parserResult, array $queryComponents); /** * Returns internal queryComponents array. * * @return array */ public function getQueryComponents(); /** * Sets or overrides a query component for a given dql alias. * * @param string $dqlAlias The DQL alias. * @param array $queryComponent * * @return void */ public function setQueryComponent($dqlAlias, array $queryComponent); /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * * @param AST\SelectStatement $AST * * @return string The SQL. */ function walkSelectStatement(AST\SelectStatement $AST); /** * Walks down a SelectClause AST node, thereby generating the appropriate SQL. * * @param AST\SelectClause $selectClause * * @return string The SQL. */ function walkSelectClause($selectClause); /** * Walks down a FromClause AST node, thereby generating the appropriate SQL. * * @param AST\FromClause $fromClause * * @return string The SQL. */ function walkFromClause($fromClause); /** * Walks down a FunctionNode AST node, thereby generating the appropriate SQL. * * @param AST\Functions\FunctionNode $function * * @return string The SQL. */ function walkFunction($function); /** * Walks down an OrderByClause AST node, thereby generating the appropriate SQL. * * @param AST\OrderByClause $orderByClause * * @return string The SQL. */ function walkOrderByClause($orderByClause); /** * Walks down an OrderByItem AST node, thereby generating the appropriate SQL. * * @param AST\OrderByItem $orderByItem * * @return string The SQL. */ function walkOrderByItem($orderByItem); /** * Walks down a HavingClause AST node, thereby generating the appropriate SQL. * * @param AST\HavingClause $havingClause * * @return string The SQL. */ function walkHavingClause($havingClause); /** * Walks down a Join AST node and creates the corresponding SQL. * * @param AST\Join $join * * @return string The SQL. */ function walkJoin($join); /** * Walks down a SelectExpression AST node and generates the corresponding SQL. * * @param AST\SelectExpression $selectExpression * * @return string The SQL. */ function walkSelectExpression($selectExpression); /** * Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL. * * @param AST\QuantifiedExpression $qExpr * * @return string The SQL. */ function walkQuantifiedExpression($qExpr); /** * Walks down a Subselect AST node, thereby generating the appropriate SQL. * * @param AST\Subselect $subselect * * @return string The SQL. */ function walkSubselect($subselect); /** * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL. * * @param AST\SubselectFromClause $subselectFromClause * * @return string The SQL. */ function walkSubselectFromClause($subselectFromClause); /** * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL. * * @param AST\SimpleSelectClause $simpleSelectClause * * @return string The SQL. */ function walkSimpleSelectClause($simpleSelectClause); /** * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL. * * @param AST\SimpleSelectExpression $simpleSelectExpression * * @return string The SQL. */ function walkSimpleSelectExpression($simpleSelectExpression); /** * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL. * * @param AST\AggregateExpression $aggExpression * * @return string The SQL. */ function walkAggregateExpression($aggExpression); /** * Walks down a GroupByClause AST node, thereby generating the appropriate SQL. * * @param AST\GroupByClause $groupByClause * * @return string The SQL. */ function walkGroupByClause($groupByClause); /** * Walks down a GroupByItem AST node, thereby generating the appropriate SQL. * * @param AST\PathExpression|string $groupByItem * * @return string The SQL. */ function walkGroupByItem($groupByItem); /** * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. * * @param AST\UpdateStatement $AST * * @return string The SQL. */ function walkUpdateStatement(AST\UpdateStatement $AST); /** * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL. * * @param AST\DeleteStatement $AST * * @return string The SQL. */ function walkDeleteStatement(AST\DeleteStatement $AST); /** * Walks down a DeleteClause AST node, thereby generating the appropriate SQL. * * @param AST\DeleteClause $deleteClause * * @return string The SQL. */ function walkDeleteClause(AST\DeleteClause $deleteClause); /** * Walks down an UpdateClause AST node, thereby generating the appropriate SQL. * * @param AST\UpdateClause $updateClause * * @return string The SQL. */ function walkUpdateClause($updateClause); /** * Walks down an UpdateItem AST node, thereby generating the appropriate SQL. * * @param AST\UpdateItem $updateItem * * @return string The SQL. */ function walkUpdateItem($updateItem); /** * Walks down a WhereClause AST node, thereby generating the appropriate SQL. * WhereClause or not, the appropriate discriminator sql is added. * * @param AST\WhereClause $whereClause * * @return string The SQL. */ function walkWhereClause($whereClause); /** * Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL. * * @param AST\ConditionalExpression $condExpr * * @return string The SQL. */ function walkConditionalExpression($condExpr); /** * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. * * @param AST\ConditionalTerm $condTerm * * @return string The SQL. */ function walkConditionalTerm($condTerm); /** * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL. * * @param AST\ConditionalFactor $factor * * @return string The SQL. */ function walkConditionalFactor($factor); /** * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. * * @param AST\ConditionalPrimary $primary * * @return string The SQL. */ function walkConditionalPrimary($primary); /** * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. * * @param AST\ExistsExpression $existsExpr * * @return string The SQL. */ function walkExistsExpression($existsExpr); /** * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL. * * @param AST\CollectionMemberExpression $collMemberExpr * * @return string The SQL. */ function walkCollectionMemberExpression($collMemberExpr); /** * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. * * @param AST\EmptyCollectionComparisonExpression $emptyCollCompExpr * * @return string The SQL. */ function walkEmptyCollectionComparisonExpression($emptyCollCompExpr); /** * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. * * @param AST\NullComparisonExpression $nullCompExpr * * @return string The SQL. */ function walkNullComparisonExpression($nullCompExpr); /** * Walks down an InExpression AST node, thereby generating the appropriate SQL. * * @param AST\InExpression $inExpr * * @return string The SQL. */ function walkInExpression($inExpr); /** * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL. * * @param AST\InstanceOfExpression $instanceOfExpr * * @return string The SQL. */ function walkInstanceOfExpression($instanceOfExpr); /** * Walks down a literal that represents an AST node, thereby generating the appropriate SQL. * * @param mixed $literal * * @return string The SQL. */ function walkLiteral($literal); /** * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL. * * @param AST\BetweenExpression $betweenExpr * * @return string The SQL. */ function walkBetweenExpression($betweenExpr); /** * Walks down a LikeExpression AST node, thereby generating the appropriate SQL. * * @param AST\LikeExpression $likeExpr * * @return string The SQL. */ function walkLikeExpression($likeExpr); /** * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL. * * @param AST\PathExpression $stateFieldPathExpression * * @return string The SQL. */ function walkStateFieldPathExpression($stateFieldPathExpression); /** * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL. * * @param AST\ComparisonExpression $compExpr * * @return string The SQL. */ function walkComparisonExpression($compExpr); /** * Walks down an InputParameter AST node, thereby generating the appropriate SQL. * * @param AST\InputParameter $inputParam * * @return string The SQL. */ function walkInputParameter($inputParam); /** * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL. * * @param AST\ArithmeticExpression $arithmeticExpr * * @return string The SQL. */ function walkArithmeticExpression($arithmeticExpr); /** * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL. * * @param mixed $term * * @return string The SQL. */ function walkArithmeticTerm($term); /** * Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL. * * @param mixed $stringPrimary * * @return string The SQL. */ function walkStringPrimary($stringPrimary); /** * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL. * * @param mixed $factor * * @return string The SQL. */ function walkArithmeticFactor($factor); /** * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL. * * @param AST\SimpleArithmeticExpression $simpleArithmeticExpr * * @return string The SQL. */ function walkSimpleArithmeticExpression($simpleArithmeticExpr); /** * Walks down a PathExpression AST node, thereby generating the appropriate SQL. * * @param mixed $pathExpr * * @return string The SQL. */ function walkPathExpression($pathExpr); /** * Walks down a ResultVariable that represents an AST node, thereby generating the appropriate SQL. * * @param string $resultVariable * * @return string The SQL. */ function walkResultVariable($resultVariable); /** * Gets an executor that can be used to execute the result of this walker. * * @param AST\DeleteStatement|AST\UpdateStatement|AST\SelectStatement $AST * * @return Exec\AbstractSqlExecutor */ function getExecutor($AST); } doctrine2-2.4.8/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php000066400000000000000000000177021257105210500235610ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; /** * An adapter implementation of the TreeWalker interface. The methods in this class * are empty. This class exists as convenience for creating tree walkers. * * @author Roman Borschel * @since 2.0 */ abstract class TreeWalkerAdapter implements TreeWalker { /** * The original Query. * * @var \Doctrine\ORM\AbstractQuery */ private $_query; /** * The ParserResult of the original query that was produced by the Parser. * * @var \Doctrine\ORM\Query\ParserResult */ private $_parserResult; /** * The query components of the original query (the "symbol table") that was produced by the Parser. * * @var array */ private $_queryComponents; /** * {@inheritdoc} */ public function __construct($query, $parserResult, array $queryComponents) { $this->_query = $query; $this->_parserResult = $parserResult; $this->_queryComponents = $queryComponents; } /** * {@inheritdoc} */ public function getQueryComponents() { return $this->_queryComponents; } /** * {@inheritdoc} */ public function setQueryComponent($dqlAlias, array $queryComponent) { $requiredKeys = array('metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'); if (array_diff($requiredKeys, array_keys($queryComponent))) { throw QueryException::invalidQueryComponent($dqlAlias); } $this->_queryComponents[$dqlAlias] = $queryComponent; } /** * @return array */ protected function _getQueryComponents() { return $this->_queryComponents; } /** * Retrieves the Query Instance responsible for the current walkers execution. * * @return \Doctrine\ORM\AbstractQuery */ protected function _getQuery() { return $this->_query; } /** * Retrieves the ParserResult. * * @return \Doctrine\ORM\Query\ParserResult */ protected function _getParserResult() { return $this->_parserResult; } /** * {@inheritdoc} */ public function walkSelectStatement(AST\SelectStatement $AST) { } /** * {@inheritdoc} */ public function walkSelectClause($selectClause) { } /** * {@inheritdoc} */ public function walkFromClause($fromClause) { } /** * {@inheritdoc} */ public function walkFunction($function) { } /** * {@inheritdoc} */ public function walkOrderByClause($orderByClause) { } /** * {@inheritdoc} */ public function walkOrderByItem($orderByItem) { } /** * {@inheritdoc} */ public function walkHavingClause($havingClause) { } /** * {@inheritdoc} */ public function walkJoin($join) { } /** * {@inheritdoc} */ public function walkSelectExpression($selectExpression) { } /** * {@inheritdoc} */ public function walkQuantifiedExpression($qExpr) { } /** * {@inheritdoc} */ public function walkSubselect($subselect) { } /** * {@inheritdoc} */ public function walkSubselectFromClause($subselectFromClause) { } /** * {@inheritdoc} */ public function walkSimpleSelectClause($simpleSelectClause) { } /** * {@inheritdoc} */ public function walkSimpleSelectExpression($simpleSelectExpression) { } /** * {@inheritdoc} */ public function walkAggregateExpression($aggExpression) { } /** * {@inheritdoc} */ public function walkGroupByClause($groupByClause) { } /** * {@inheritdoc} */ public function walkGroupByItem($groupByItem) { } /** * {@inheritdoc} */ public function walkUpdateStatement(AST\UpdateStatement $AST) { } /** * {@inheritdoc} */ public function walkDeleteStatement(AST\DeleteStatement $AST) { } /** * {@inheritdoc} */ public function walkDeleteClause(AST\DeleteClause $deleteClause) { } /** * {@inheritdoc} */ public function walkUpdateClause($updateClause) { } /** * {@inheritdoc} */ public function walkUpdateItem($updateItem) { } /** * {@inheritdoc} */ public function walkWhereClause($whereClause) { } /** * {@inheritdoc} */ public function walkConditionalExpression($condExpr) { } /** * {@inheritdoc} */ public function walkConditionalTerm($condTerm) { } /** * {@inheritdoc} */ public function walkConditionalFactor($factor) { } /** * {@inheritdoc} */ public function walkConditionalPrimary($primary) { } /** * {@inheritdoc} */ public function walkExistsExpression($existsExpr) { } /** * {@inheritdoc} */ public function walkCollectionMemberExpression($collMemberExpr) { } /** * {@inheritdoc} */ public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) { } /** * {@inheritdoc} */ public function walkNullComparisonExpression($nullCompExpr) { } /** * {@inheritdoc} */ public function walkInExpression($inExpr) { } /** * {@inheritdoc} */ function walkInstanceOfExpression($instanceOfExpr) { } /** * {@inheritdoc} */ public function walkLiteral($literal) { } /** * {@inheritdoc} */ public function walkBetweenExpression($betweenExpr) { } /** * {@inheritdoc} */ public function walkLikeExpression($likeExpr) { } /** * {@inheritdoc} */ public function walkStateFieldPathExpression($stateFieldPathExpression) { } /** * {@inheritdoc} */ public function walkComparisonExpression($compExpr) { } /** * {@inheritdoc} */ public function walkInputParameter($inputParam) { } /** * {@inheritdoc} */ public function walkArithmeticExpression($arithmeticExpr) { } /** * {@inheritdoc} */ public function walkArithmeticTerm($term) { } /** * {@inheritdoc} */ public function walkStringPrimary($stringPrimary) { } /** * {@inheritdoc} */ public function walkArithmeticFactor($factor) { } /** * {@inheritdoc} */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) { } /** * {@inheritdoc} */ public function walkPathExpression($pathExpr) { } /** * {@inheritdoc} */ public function walkResultVariable($resultVariable) { } /** * {@inheritdoc} */ public function getExecutor($AST) { } } doctrine2-2.4.8/lib/Doctrine/ORM/Query/TreeWalkerChain.php000066400000000000000000000321751257105210500232240ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Query; /** * Represents a chain of tree walkers that modify an AST and finally emit output. * Only the last walker in the chain can emit output. Any previous walkers can modify * the AST to influence the final output produced by the last walker. * * @author Roman Borschel * @since 2.0 */ class TreeWalkerChain implements TreeWalker { /** * The tree walkers. * * @var TreeWalker[] */ private $_walkers = array(); /** * The original Query. * * @var \Doctrine\ORM\AbstractQuery */ private $_query; /** * The ParserResult of the original query that was produced by the Parser. * * @var \Doctrine\ORM\Query\ParserResult */ private $_parserResult; /** * The query components of the original query (the "symbol table") that was produced by the Parser. * * @var array */ private $_queryComponents; /** * Returns the internal queryComponents array. * * @return array */ public function getQueryComponents() { return $this->_queryComponents; } /** * {@inheritdoc} */ public function setQueryComponent($dqlAlias, array $queryComponent) { $requiredKeys = array('metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'); if (array_diff($requiredKeys, array_keys($queryComponent))) { throw QueryException::invalidQueryComponent($dqlAlias); } $this->_queryComponents[$dqlAlias] = $queryComponent; } /** * {@inheritdoc} */ public function __construct($query, $parserResult, array $queryComponents) { $this->_query = $query; $this->_parserResult = $parserResult; $this->_queryComponents = $queryComponents; } /** * Adds a tree walker to the chain. * * @param string $walkerClass The class of the walker to instantiate. * * @return void */ public function addTreeWalker($walkerClass) { $this->_walkers[] = new $walkerClass($this->_query, $this->_parserResult, $this->_queryComponents); } /** * {@inheritdoc} */ public function walkSelectStatement(AST\SelectStatement $AST) { foreach ($this->_walkers as $walker) { $walker->walkSelectStatement($AST); $this->_queryComponents = $walker->getQueryComponents(); } } /** * {@inheritdoc} */ public function walkSelectClause($selectClause) { foreach ($this->_walkers as $walker) { $walker->walkSelectClause($selectClause); } } /** * {@inheritdoc} */ public function walkFromClause($fromClause) { foreach ($this->_walkers as $walker) { $walker->walkFromClause($fromClause); } } /** * {@inheritdoc} */ public function walkFunction($function) { foreach ($this->_walkers as $walker) { $walker->walkFunction($function); } } /** * {@inheritdoc} */ public function walkOrderByClause($orderByClause) { foreach ($this->_walkers as $walker) { $walker->walkOrderByClause($orderByClause); } } /** * {@inheritdoc} */ public function walkOrderByItem($orderByItem) { foreach ($this->_walkers as $walker) { $walker->walkOrderByItem($orderByItem); } } /** * {@inheritdoc} */ public function walkHavingClause($havingClause) { foreach ($this->_walkers as $walker) { $walker->walkHavingClause($havingClause); } } /** * {@inheritdoc} */ public function walkJoin($join) { foreach ($this->_walkers as $walker) { $walker->walkJoin($join); } } /** * {@inheritdoc} */ public function walkSelectExpression($selectExpression) { foreach ($this->_walkers as $walker) { $walker->walkSelectExpression($selectExpression); } } /** * {@inheritdoc} */ public function walkQuantifiedExpression($qExpr) { foreach ($this->_walkers as $walker) { $walker->walkQuantifiedExpression($qExpr); } } /** * {@inheritdoc} */ public function walkSubselect($subselect) { foreach ($this->_walkers as $walker) { $walker->walkSubselect($subselect); } } /** * {@inheritdoc} */ public function walkSubselectFromClause($subselectFromClause) { foreach ($this->_walkers as $walker) { $walker->walkSubselectFromClause($subselectFromClause); } } /** * {@inheritdoc} */ public function walkSimpleSelectClause($simpleSelectClause) { foreach ($this->_walkers as $walker) { $walker->walkSimpleSelectClause($simpleSelectClause); } } /** * {@inheritdoc} */ public function walkSimpleSelectExpression($simpleSelectExpression) { foreach ($this->_walkers as $walker) { $walker->walkSimpleSelectExpression($simpleSelectExpression); } } /** * {@inheritdoc} */ public function walkAggregateExpression($aggExpression) { foreach ($this->_walkers as $walker) { $walker->walkAggregateExpression($aggExpression); } } /** * {@inheritdoc} */ public function walkGroupByClause($groupByClause) { foreach ($this->_walkers as $walker) { $walker->walkGroupByClause($groupByClause); } } /** * {@inheritdoc} */ public function walkGroupByItem($groupByItem) { foreach ($this->_walkers as $walker) { $walker->walkGroupByItem($groupByItem); } } /** * {@inheritdoc} */ public function walkUpdateStatement(AST\UpdateStatement $AST) { foreach ($this->_walkers as $walker) { $walker->walkUpdateStatement($AST); } } /** * {@inheritdoc} */ public function walkDeleteStatement(AST\DeleteStatement $AST) { foreach ($this->_walkers as $walker) { $walker->walkDeleteStatement($AST); } } /** * {@inheritdoc} */ public function walkDeleteClause(AST\DeleteClause $deleteClause) { foreach ($this->_walkers as $walker) { $walker->walkDeleteClause($deleteClause); } } /** * {@inheritdoc} */ public function walkUpdateClause($updateClause) { foreach ($this->_walkers as $walker) { $walker->walkUpdateClause($updateClause); } } /** * {@inheritdoc} */ public function walkUpdateItem($updateItem) { foreach ($this->_walkers as $walker) { $walker->walkUpdateItem($updateItem); } } /** * {@inheritdoc} */ public function walkWhereClause($whereClause) { foreach ($this->_walkers as $walker) { $walker->walkWhereClause($whereClause); } } /** * {@inheritdoc} */ public function walkConditionalExpression($condExpr) { foreach ($this->_walkers as $walker) { $walker->walkConditionalExpression($condExpr); } } /** * {@inheritdoc} */ public function walkConditionalTerm($condTerm) { foreach ($this->_walkers as $walker) { $walker->walkConditionalTerm($condTerm); } } /** * {@inheritdoc} */ public function walkConditionalFactor($factor) { foreach ($this->_walkers as $walker) { $walker->walkConditionalFactor($factor); } } /** * {@inheritdoc} */ public function walkConditionalPrimary($condPrimary) { foreach ($this->_walkers as $walker) { $walker->walkConditionalPrimary($condPrimary); } } /** * {@inheritdoc} */ public function walkExistsExpression($existsExpr) { foreach ($this->_walkers as $walker) { $walker->walkExistsExpression($existsExpr); } } /** * {@inheritdoc} */ public function walkCollectionMemberExpression($collMemberExpr) { foreach ($this->_walkers as $walker) { $walker->walkCollectionMemberExpression($collMemberExpr); } } /** * {@inheritdoc} */ public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) { foreach ($this->_walkers as $walker) { $walker->walkEmptyCollectionComparisonExpression($emptyCollCompExpr); } } /** * {@inheritdoc} */ public function walkNullComparisonExpression($nullCompExpr) { foreach ($this->_walkers as $walker) { $walker->walkNullComparisonExpression($nullCompExpr); } } /** * {@inheritdoc} */ public function walkInExpression($inExpr) { foreach ($this->_walkers as $walker) { $walker->walkInExpression($inExpr); } } /** * {@inheritdoc} */ function walkInstanceOfExpression($instanceOfExpr) { foreach ($this->_walkers as $walker) { $walker->walkInstanceOfExpression($instanceOfExpr); } } /** * {@inheritdoc} */ public function walkLiteral($literal) { foreach ($this->_walkers as $walker) { $walker->walkLiteral($literal); } } /** * {@inheritdoc} */ public function walkBetweenExpression($betweenExpr) { foreach ($this->_walkers as $walker) { $walker->walkBetweenExpression($betweenExpr); } } /** * {@inheritdoc} */ public function walkLikeExpression($likeExpr) { foreach ($this->_walkers as $walker) { $walker->walkLikeExpression($likeExpr); } } /** * {@inheritdoc} */ public function walkStateFieldPathExpression($stateFieldPathExpression) { foreach ($this->_walkers as $walker) { $walker->walkStateFieldPathExpression($stateFieldPathExpression); } } /** * {@inheritdoc} */ public function walkComparisonExpression($compExpr) { foreach ($this->_walkers as $walker) { $walker->walkComparisonExpression($compExpr); } } /** * {@inheritdoc} */ public function walkInputParameter($inputParam) { foreach ($this->_walkers as $walker) { $walker->walkInputParameter($inputParam); } } /** * {@inheritdoc} */ public function walkArithmeticExpression($arithmeticExpr) { foreach ($this->_walkers as $walker) { $walker->walkArithmeticExpression($arithmeticExpr); } } /** * {@inheritdoc} */ public function walkArithmeticTerm($term) { foreach ($this->_walkers as $walker) { $walker->walkArithmeticTerm($term); } } /** * {@inheritdoc} */ public function walkStringPrimary($stringPrimary) { foreach ($this->_walkers as $walker) { $walker->walkStringPrimary($stringPrimary); } } /** * {@inheritdoc} */ public function walkArithmeticFactor($factor) { foreach ($this->_walkers as $walker) { $walker->walkArithmeticFactor($factor); } } /** * {@inheritdoc} */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) { foreach ($this->_walkers as $walker) { $walker->walkSimpleArithmeticExpression($simpleArithmeticExpr); } } /** * {@inheritdoc} */ public function walkPathExpression($pathExpr) { foreach ($this->_walkers as $walker) { $walker->walkPathExpression($pathExpr); } } /** * {@inheritdoc} */ public function walkResultVariable($resultVariable) { foreach ($this->_walkers as $walker) { $walker->walkResultVariable($resultVariable); } } /** * {@inheritdoc} */ public function getExecutor($AST) { } } doctrine2-2.4.8/lib/Doctrine/ORM/QueryBuilder.php000066400000000000000000001122071257105210500215160ustar00rootroot00000000000000. */ namespace Doctrine\ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\QueryExpressionVisitor; /** * This class is responsible for building DQL query strings via an object oriented * PHP interface. * * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class QueryBuilder { /* The query types. */ const SELECT = 0; const DELETE = 1; const UPDATE = 2; /* The builder states. */ const STATE_DIRTY = 0; const STATE_CLEAN = 1; /** * The EntityManager used by this QueryBuilder. * * @var EntityManager */ private $_em; /** * The array of DQL parts collected. * * @var array */ private $_dqlParts = array( 'distinct' => false, 'select' => array(), 'from' => array(), 'join' => array(), 'set' => array(), 'where' => null, 'groupBy' => array(), 'having' => null, 'orderBy' => array() ); /** * The type of query this is. Can be select, update or delete. * * @var integer */ private $_type = self::SELECT; /** * The state of the query object. Can be dirty or clean. * * @var integer */ private $_state = self::STATE_CLEAN; /** * The complete DQL string for this query. * * @var string */ private $_dql; /** * The query parameters. * * @var \Doctrine\Common\Collections\ArrayCollection */ private $parameters = array(); /** * The index of the first result to retrieve. * * @var integer */ private $_firstResult = null; /** * The maximum number of results to retrieve. * * @var integer */ private $_maxResults = null; /** * Keeps root entity alias names for join entities. * * @var array */ private $joinRootAliases = array(); /** * Initializes a new QueryBuilder that uses the given EntityManager. * * @param EntityManager $em The EntityManager to use. */ public function __construct(EntityManager $em) { $this->_em = $em; $this->parameters = new ArrayCollection(); } /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * This producer method is intended for convenient inline usage. Example: * * * $qb = $em->createQueryBuilder(); * $qb * ->select('u') * ->from('User', 'u') * ->where($qb->expr()->eq('u.id', 1)); * * * For more complex expression construction, consider storing the expression * builder object in a local variable. * * @return Query\Expr */ public function expr() { return $this->_em->getExpressionBuilder(); } /** * Gets the type of the currently built query. * * @return integer */ public function getType() { return $this->_type; } /** * Gets the associated EntityManager for this query builder. * * @return EntityManager */ public function getEntityManager() { return $this->_em; } /** * Gets the state of this query builder instance. * * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. */ public function getState() { return $this->_state; } /** * Gets the complete DQL string formed by the current specifications of this QueryBuilder. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * echo $qb->getDql(); // SELECT u FROM User u * * * @return string The DQL query string. */ public function getDQL() { if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) { return $this->_dql; } switch ($this->_type) { case self::DELETE: $dql = $this->_getDQLForDelete(); break; case self::UPDATE: $dql = $this->_getDQLForUpdate(); break; case self::SELECT: default: $dql = $this->_getDQLForSelect(); break; } $this->_state = self::STATE_CLEAN; $this->_dql = $dql; return $dql; } /** * Constructs a Query instance from the current specifications of the builder. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * $q = $qb->getQuery(); * $results = $q->execute(); * * * @return Query */ public function getQuery() { $parameters = clone $this->parameters; return $this->_em->createQuery($this->getDQL()) ->setParameters($parameters) ->setFirstResult($this->_firstResult) ->setMaxResults($this->_maxResults); } /** * Finds the root entity alias of the joined entity. * * @param string $alias The alias of the new join entity * @param string $parentAlias The parent entity alias of the join relationship * * @return string */ private function findRootAlias($alias, $parentAlias) { $rootAlias = null; if (in_array($parentAlias, $this->getRootAliases())) { $rootAlias = $parentAlias; } elseif (isset($this->joinRootAliases[$parentAlias])) { $rootAlias = $this->joinRootAliases[$parentAlias]; } else { // Should never happen with correct joining order. Might be // thoughtful to throw exception instead. $rootAlias = $this->getRootAlias(); } $this->joinRootAliases[$alias] = $rootAlias; return $rootAlias; } /** * Gets the FIRST root alias of the query. This is the first entity alias involved * in the construction of the query. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * * echo $qb->getRootAlias(); // u * * * @deprecated Please use $qb->getRootAliases() instead. * * @return string */ public function getRootAlias() { $aliases = $this->getRootAliases(); return $aliases[0]; } /** * Gets the root aliases of the query. This is the entity aliases involved * in the construction of the query. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * * $qb->getRootAliases(); // array('u') * * * @return array */ public function getRootAliases() { $aliases = 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); } $aliases[] = $fromClause->getAlias(); } return $aliases; } /** * Gets the root entities of the query. This is the entity aliases involved * in the construction of the query. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * * $qb->getRootEntities(); // array('User') * * * @return array */ 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. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = :user_id') * ->setParameter('user_id', 1); * * * @param string|integer $key The parameter position or name. * @param mixed $value The parameter value. * @param string|null $type PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant * * @return QueryBuilder This QueryBuilder instance. */ public function setParameter($key, $value, $type = null) { $filteredParameters = $this->parameters->filter( function ($parameter) use ($key) { // Must not be identical because of string to integer conversion return ($key == $parameter->getName()); } ); if (count($filteredParameters)) { $parameter = $filteredParameters->first(); $parameter->setValue($value, $type); return $this; } $parameter = new Query\Parameter($key, $value, $type); $this->parameters->add($parameter); return $this; } /** * Sets a collection of query parameters for the query being constructed. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = :user_id1 OR u.id = :user_id2') * ->setParameters(new ArrayCollection(array( * new Parameter('user_id1', 1), * new Parameter('user_id2', 2) * ))); * * * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters to set. * * @return QueryBuilder This QueryBuilder instance. */ public function setParameters($parameters) { // BC compatibility with 2.3- if (is_array($parameters)) { $parameterCollection = new ArrayCollection(); foreach ($parameters as $key => $value) { $parameter = new Query\Parameter($key, $value); $parameterCollection->add($parameter); } $parameters = $parameterCollection; } $this->parameters = $parameters; return $this; } /** * Gets all defined query parameters for the query being constructed. * * @return \Doctrine\Common\Collections\ArrayCollection The currently defined query parameters. */ public function getParameters() { return $this->parameters; } /** * Gets a (previously set) query parameter of the query being constructed. * * @param mixed $key The key (index or name) of the bound parameter. * * @return Query\Parameter|null The value of the bound parameter. */ public function getParameter($key) { $filteredParameters = $this->parameters->filter( function ($parameter) use ($key) { // Must not be identical because of string to integer conversion return ($key == $parameter->getName()); } ); return count($filteredParameters) ? $filteredParameters->first() : null; } /** * Sets the position of the first result to retrieve (the "offset"). * * @param integer $firstResult The first result to return. * * @return QueryBuilder This QueryBuilder instance. */ public function setFirstResult($firstResult) { $this->_firstResult = $firstResult; return $this; } /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. * * @return integer The position of the first result. */ public function getFirstResult() { return $this->_firstResult; } /** * Sets the maximum number of results to retrieve (the "limit"). * * @param integer $maxResults The maximum number of results to retrieve. * * @return QueryBuilder This QueryBuilder instance. */ public function setMaxResults($maxResults) { $this->_maxResults = $maxResults; return $this; } /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if {@link setMaxResults} was not applied to this query builder. * * @return integer Maximum number of results. */ public function getMaxResults() { return $this->_maxResults; } /** * Either appends to or replaces a single, generic query part. * * The available parts are: 'select', 'from', 'join', 'set', 'where', * 'groupBy', 'having' and 'orderBy'. * * @param string $dqlPartName * @param Expr\Base $dqlPart * @param bool $append * * @return QueryBuilder This QueryBuilder instance. */ public function add($dqlPartName, $dqlPart, $append = false) { if ($append && ($dqlPartName === "where" || $dqlPartName === "having")) { throw new \InvalidArgumentException( "Using \$append = true does not have an effect with 'where' or 'having' ". "parts. See QueryBuilder#andWhere() for an example for correct usage." ); } $isMultiple = is_array($this->_dqlParts[$dqlPartName]); // This is introduced for backwards compatibility reasons. // TODO: Remove for 3.0 if ($dqlPartName == 'join') { $newDqlPart = array(); foreach ($dqlPart as $k => $v) { $k = is_numeric($k) ? $this->getRootAlias() : $k; $newDqlPart[$k] = $v; } $dqlPart = $newDqlPart; } if ($append && $isMultiple) { if (is_array($dqlPart)) { $key = key($dqlPart); $this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key]; } else { $this->_dqlParts[$dqlPartName][] = $dqlPart; } } else { $this->_dqlParts[$dqlPartName] = ($isMultiple) ? array($dqlPart) : $dqlPart; } $this->_state = self::STATE_DIRTY; return $this; } /** * Specifies an item that is to be returned in the query result. * Replaces any previously specified selections, if any. * * * $qb = $em->createQueryBuilder() * ->select('u', 'p') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p'); * * * @param mixed $select The selection expressions. * * @return QueryBuilder This QueryBuilder instance. */ public function select($select = null) { $this->_type = self::SELECT; if (empty($select)) { return $this; } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', new Expr\Select($selects), false); } /** * Adds a DISTINCT flag to this query. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->distinct() * ->from('User', 'u'); * * * @param bool $flag * * @return QueryBuilder */ public function distinct($flag = true) { $this->_dqlParts['distinct'] = (bool) $flag; return $this; } /** * Adds an item that is to be returned in the query result. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->addSelect('p') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p'); * * * @param mixed $select The selection expression. * * @return QueryBuilder This QueryBuilder instance. */ public function addSelect($select = null) { $this->_type = self::SELECT; if (empty($select)) { return $this; } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', new Expr\Select($selects), true); } /** * Turns the query being built into a bulk delete query that ranges over * a certain entity type. * * * $qb = $em->createQueryBuilder() * ->delete('User', 'u') * ->where('u.id = :user_id'); * ->setParameter('user_id', 1); * * * @param string $delete The class/type whose instances are subject to the deletion. * @param string $alias The class/type alias used in the constructed query. * * @return QueryBuilder This QueryBuilder instance. */ public function delete($delete = null, $alias = null) { $this->_type = self::DELETE; if ( ! $delete) { return $this; } return $this->add('from', new Expr\From($delete, $alias)); } /** * Turns the query being built into a bulk update query that ranges over * a certain entity type. * * * $qb = $em->createQueryBuilder() * ->update('User', 'u') * ->set('u.password', md5('password')) * ->where('u.id = ?'); * * * @param string $update The class/type whose instances are subject to the update. * @param string $alias The class/type alias used in the constructed query. * * @return QueryBuilder This QueryBuilder instance. */ public function update($update = null, $alias = null) { $this->_type = self::UPDATE; if ( ! $update) { return $this; } return $this->add('from', new Expr\From($update, $alias)); } /** * Creates and adds a query root corresponding to the entity identified by the given alias, * forming a cartesian product with any existing query roots. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * * * @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, $indexBy = null) { return $this->add('from', new Expr\From($from, $alias, $indexBy), true); } /** * Creates and adds a join over an entity association to the query. * * The entities in the joined association will be fetched as part of the query * result if the alias used for the joined association is placed in the select * expressions. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); * * * @param string $join The relationship to join. * @param string $alias The alias of the join. * @param string|null $conditionType The condition type constant. Either ON or WITH. * @param string|null $condition The condition for the join. * @param string|null $indexBy The index for the join. * * @return QueryBuilder This QueryBuilder instance. */ public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy); } /** * Creates and adds a join over an entity association to the query. * * The entities in the joined association will be fetched as part of the query * result if the alias used for the joined association is placed in the select * expressions. * * [php] * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); * * @param string $join The relationship to join. * @param string $alias The alias of the join. * @param string|null $conditionType The condition type constant. Either ON or WITH. * @param string|null $condition The condition for the join. * @param string|null $indexBy The index for the join. * * @return QueryBuilder This QueryBuilder instance. */ public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { $parentAlias = substr($join, 0, strpos($join, '.')); $rootAlias = $this->findRootAlias($alias, $parentAlias); $join = new Expr\Join( Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy ); return $this->add('join', array($rootAlias => $join), true); } /** * Creates and adds a left join over an entity association to the query. * * The entities in the joined association will be fetched as part of the query * result if the alias used for the joined association is placed in the select * expressions. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); * * * @param string $join The relationship to join. * @param string $alias The alias of the join. * @param string|null $conditionType The condition type constant. Either ON or WITH. * @param string|null $condition The condition for the join. * @param string|null $indexBy The index for the join. * * @return QueryBuilder This QueryBuilder instance. */ public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { $parentAlias = substr($join, 0, strpos($join, '.')); $rootAlias = $this->findRootAlias($alias, $parentAlias); $join = new Expr\Join( Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy ); return $this->add('join', array($rootAlias => $join), true); } /** * Sets a new value for a field in a bulk update query. * * * $qb = $em->createQueryBuilder() * ->update('User', 'u') * ->set('u.password', md5('password')) * ->where('u.id = ?'); * * * @param string $key The key/field to set. * @param string $value The value, expression, placeholder, etc. * * @return QueryBuilder This QueryBuilder instance. */ public function set($key, $value) { return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true); } /** * Specifies one or more restrictions to the query result. * Replaces any previously specified restrictions, if any. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = ?'); * * // You can optionally programatically build and/or expressions * $qb = $em->createQueryBuilder(); * * $or = $qb->expr()->orx(); * $or->add($qb->expr()->eq('u.id', 1)); * $or->add($qb->expr()->eq('u.id', 2)); * * $qb->update('User', 'u') * ->set('u.password', md5('password')) * ->where($or); * * * @param mixed $predicates The restriction predicates. * * @return QueryBuilder This QueryBuilder instance. */ public function where($predicates) { if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) { $predicates = new Expr\Andx(func_get_args()); } return $this->add('where', $predicates); } /** * Adds one or more restrictions to the query results, forming a logical * conjunction with any previously specified restrictions. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.username LIKE ?') * ->andWhere('u.is_active = 1'); * * * @param mixed $where The query restrictions. * * @return QueryBuilder This QueryBuilder instance. * * @see where() */ public function andWhere($where) { $args = func_get_args(); $where = $this->getDQLPart('where'); if ($where instanceof Expr\Andx) { $where->addMultiple($args); } else { array_unshift($args, $where); $where = new Expr\Andx($args); } return $this->add('where', $where); } /** * Adds one or more restrictions to the query results, forming a logical * disjunction with any previously specified restrictions. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = 1') * ->orWhere('u.id = 2'); * * * @param mixed $where The WHERE statement. * * @return QueryBuilder * * @see where() */ public function orWhere($where) { $args = func_get_args(); $where = $this->getDqlPart('where'); if ($where instanceof Expr\Orx) { $where->addMultiple($args); } else { array_unshift($args, $where); $where = new Expr\Orx($args); } return $this->add('where', $where); } /** * Specifies a grouping over the results of the query. * Replaces any previously specified groupings, if any. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->groupBy('u.id'); * * * @param string $groupBy The grouping expression. * * @return QueryBuilder This QueryBuilder instance. */ public function groupBy($groupBy) { return $this->add('groupBy', new Expr\GroupBy(func_get_args())); } /** * Adds a grouping expression to the query. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->groupBy('u.lastLogin'); * ->addGroupBy('u.createdAt') * * * @param string $groupBy The grouping expression. * * @return QueryBuilder This QueryBuilder instance. */ public function addGroupBy($groupBy) { return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true); } /** * Specifies a restriction over the groups of the query. * Replaces any previous having restrictions, if any. * * @param mixed $having The restriction over the groups. * * @return QueryBuilder This QueryBuilder instance. */ public function having($having) { if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) { $having = new Expr\Andx(func_get_args()); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * conjunction with any existing having restrictions. * * @param mixed $having The restriction to append. * * @return QueryBuilder This QueryBuilder instance. */ public function andHaving($having) { $args = func_get_args(); $having = $this->getDqlPart('having'); if ($having instanceof Expr\Andx) { $having->addMultiple($args); } else { array_unshift($args, $having); $having = new Expr\Andx($args); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * disjunction with any existing having restrictions. * * @param mixed $having The restriction to add. * * @return QueryBuilder This QueryBuilder instance. */ public function orHaving($having) { $args = func_get_args(); $having = $this->getDqlPart('having'); if ($having instanceof Expr\Orx) { $having->addMultiple($args); } else { array_unshift($args, $having); $having = new Expr\Orx($args); } return $this->add('having', $having); } /** * Specifies an ordering for the query results. * Replaces any previously specified orderings, if any. * * @param string|Expr\OrderBy $sort The ordering expression. * @param string $order The ordering direction. * * @return QueryBuilder This QueryBuilder instance. */ public function orderBy($sort, $order = null) { $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order); return $this->add('orderBy', $orderBy); } /** * Adds an ordering to the query results. * * @param string|Expr\OrderBy $sort The ordering expression. * @param string $order The ordering direction. * * @return QueryBuilder This QueryBuilder instance. */ public function addOrderBy($sort, $order = null) { $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order); return $this->add('orderBy', $orderBy, true); } /** * Adds criteria to the query. * * Adds where expressions with AND operator. * Adds orderings. * Overrides firstResult and maxResults if they're set. * * @param Criteria $criteria * * @return QueryBuilder */ public function addCriteria(Criteria $criteria) { $rootAlias = $this->getRootAlias(); $visitor = new QueryExpressionVisitor($rootAlias); if ($whereExpression = $criteria->getWhereExpression()) { $this->andWhere($visitor->dispatch($whereExpression)); foreach ($visitor->getParameters() as $parameter) { $this->parameters->add($parameter); } } if ($criteria->getOrderings()) { foreach ($criteria->getOrderings() as $sort => $order) { $this->addOrderBy($rootAlias . '.' . $sort, $order); } } // Overwrite limits only if they was set in criteria if (($firstResult = $criteria->getFirstResult()) !== null) { $this->setFirstResult($firstResult); } if (($maxResults = $criteria->getMaxResults()) !== null) { $this->setMaxResults($maxResults); } return $this; } /** * Gets a query part by its name. * * @param string $queryPartName * * @return mixed $queryPart * * @todo Rename: getQueryPart (or remove?) */ public function getDQLPart($queryPartName) { return $this->_dqlParts[$queryPartName]; } /** * Gets all query parts. * * @return array $dqlParts * * @todo Rename: getQueryParts (or remove?) */ public function getDQLParts() { return $this->_dqlParts; } /** * @return string */ private function _getDQLForDelete() { return 'DELETE' . $this->_getReducedDQLQueryPart('from', array('pre' => ' ', 'separator' => ', ')) . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE ')) . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); } /** * @return string */ private function _getDQLForUpdate() { return 'UPDATE' . $this->_getReducedDQLQueryPart('from', array('pre' => ' ', 'separator' => ', ')) . $this->_getReducedDQLQueryPart('set', array('pre' => ' SET ', 'separator' => ', ')) . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE ')) . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); } /** * @return string */ private function _getDQLForSelect() { $dql = 'SELECT' . ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '') . $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', ')); $fromParts = $this->getDQLPart('from'); $joinParts = $this->getDQLPart('join'); $fromClauses = array(); // Loop through all FROM clauses if ( ! empty($fromParts)) { $dql .= ' FROM '; foreach ($fromParts as $from) { $fromClause = (string) $from; if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) { foreach ($joinParts[$from->getAlias()] as $join) { $fromClause .= ' ' . ((string) $join); } } $fromClauses[] = $fromClause; } } $dql .= implode(', ', $fromClauses) . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE ')) . $this->_getReducedDQLQueryPart('groupBy', array('pre' => ' GROUP BY ', 'separator' => ', ')) . $this->_getReducedDQLQueryPart('having', array('pre' => ' HAVING ')) . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); return $dql; } /** * @param string $queryPartName * @param array $options * * @return string */ private function _getReducedDQLQueryPart($queryPartName, $options = array()) { $queryPart = $this->getDQLPart($queryPartName); if (empty($queryPart)) { return (isset($options['empty']) ? $options['empty'] : ''); } return (isset($options['pre']) ? $options['pre'] : '') . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart) . (isset($options['post']) ? $options['post'] : ''); } /** * Resets DQL parts. * * @param array|null $parts * * @return QueryBuilder */ public function resetDQLParts($parts = null) { if (is_null($parts)) { $parts = array_keys($this->_dqlParts); } foreach ($parts as $part) { $this->resetDQLPart($part); } return $this; } /** * Resets single DQL part. * * @param string $part * * @return QueryBuilder */ public function resetDQLPart($part) { $this->_dqlParts[$part] = is_array($this->_dqlParts[$part]) ? array() : null; $this->_state = self::STATE_DIRTY; return $this; } /** * Gets a string representation of this QueryBuilder which corresponds to * the final DQL query being constructed. * * @return string The string representation of this QueryBuilder. */ public function __toString() { return $this->getDQL(); } /** * Deep clones all expression objects in the DQL parts. * * @return void */ public function __clone() { foreach ($this->_dqlParts as $part => $elements) { if (is_array($this->_dqlParts[$part])) { foreach ($this->_dqlParts[$part] as $idx => $element) { if (is_object($element)) { $this->_dqlParts[$part][$idx] = clone $element; } } } else if (is_object($elements)) { $this->_dqlParts[$part] = clone $elements; } } $parameters = array(); foreach ($this->parameters as $parameter) { $parameters[] = clone $parameter; } $this->parameters = new ArrayCollection($parameters); } } doctrine2-2.4.8/lib/Doctrine/ORM/README.markdown000066400000000000000000000000001257105210500210550ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Repository/000077500000000000000000000000001257105210500205455ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Repository/DefaultRepositoryFactory.php000066400000000000000000000053751257105210500263040ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Repository; use Doctrine\ORM\EntityManagerInterface; /** * This factory is used to create default repository objects for entities at runtime. * * @author Guilherme Blanco * @since 2.4 */ class DefaultRepositoryFactory implements RepositoryFactory { /** * The list of EntityRepository instances. * * @var array<\Doctrine\Common\Persistence\ObjectRepository> */ private $repositoryList = array(); /** * {@inheritdoc} */ public function getRepository(EntityManagerInterface $entityManager, $entityName) { $entityName = ltrim($entityName, '\\'); if (isset($this->repositoryList[$entityName])) { return $this->repositoryList[$entityName]; } $repository = $this->createRepository($entityManager, $entityName); $this->repositoryList[$entityName] = $repository; return $repository; } /** * Create a new repository instance for an entity class. * * @param \Doctrine\ORM\EntityManagerInterface $entityManager The EntityManager instance. * @param string $entityName The name of the entity. * * @return \Doctrine\Common\Persistence\ObjectRepository */ protected function createRepository(EntityManagerInterface $entityManager, $entityName) { $metadata = $entityManager->getClassMetadata($entityName); $repositoryClassName = $metadata->customRepositoryClassName; if ($repositoryClassName === null) { $configuration = $entityManager->getConfiguration(); $repositoryClassName = $configuration->getDefaultRepositoryClassName(); } return new $repositoryClassName($entityManager, $metadata); } }doctrine2-2.4.8/lib/Doctrine/ORM/Repository/RepositoryFactory.php000066400000000000000000000031431257105210500247660ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Repository; use Doctrine\ORM\EntityManagerInterface; /** * Interface for entity repository factory. * * @author Guilherme Blanco * @since 2.4 */ interface RepositoryFactory { /** * Gets the repository for an entity class. * * @param \Doctrine\ORM\EntityManagerInterface $entityManager The EntityManager instance. * @param string $entityName The name of the entity. * * @return \Doctrine\Common\Persistence\ObjectRepository */ public function getRepository(EntityManagerInterface $entityManager, $entityName); }doctrine2-2.4.8/lib/Doctrine/ORM/Tools/000077500000000000000000000000001257105210500174665ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/000077500000000000000000000000001257105210500210705ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/000077500000000000000000000000001257105210500224465ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/000077500000000000000000000000001257105210500244205ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php000066400000000000000000000075641257105210500301640ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Doctrine\Common\Cache\ApcCache; /** * Command to clear the metadata cache of the various cache drivers. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class MetadataCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:clear-cache:metadata') ->setDescription('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.' ) )); $this->setHelp(<<%command.name% 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: %command.name% Alternatively, if you want to flush the cache provider using this command: %command.name% --flush Finally, be aware that if --flush option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); $cacheDriver = $em->getConfiguration()->getMetadataCacheImpl(); if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Metadata cache driver is configured on given EntityManager.'); } if ($cacheDriver instanceof ApcCache) { throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); } $output->writeln('Clearing ALL Metadata cache entries'); $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->writeln($message); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php000066400000000000000000000075541257105210500275500ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Doctrine\Common\Cache\ApcCache; /** * Command to clear the query cache of the various cache drivers. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class QueryCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:clear-cache:query') ->setDescription('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.' ) )); $this->setHelp(<<%command.name% 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: %command.name% Alternatively, if you want to flush the cache provider using this command: %command.name% --flush Finally, be aware that if --flush option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); $cacheDriver = $em->getConfiguration()->getQueryCacheImpl(); if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Query cache driver is configured on given EntityManager.'); } if ($cacheDriver instanceof 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); $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); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php000066400000000000000000000075441257105210500277200ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Doctrine\Common\Cache\ApcCache; /** * Command to clear the result cache of the various cache drivers. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ResultCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:clear-cache:result') ->setDescription('Clear all result 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.' ) )); $this->setHelp(<<%command.name% 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: %command.name% Alternatively, if you want to flush the cache provider using this command: %command.name% --flush Finally, be aware that if --flush option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); $cacheDriver = $em->getConfiguration()->getResultCacheImpl(); if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Result cache driver is configured on given EntityManager.'); } if ($cacheDriver instanceof ApcCache) { throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); } $output->writeln('Clearing ALL Result cache entries'); $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->writeln($message); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/ConvertDoctrine1SchemaCommand.php000066400000000000000000000171571257105210500310030ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console; use Doctrine\ORM\Tools\Export\ClassMetadataExporter; use Doctrine\ORM\Tools\ConvertDoctrine1Schema; use Doctrine\ORM\Tools\EntityGenerator; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Command\Command; /** * Command to convert a Doctrine 1 schema to a Doctrine 2 mapping file. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConvertDoctrine1SchemaCommand extends Command { /** * @var EntityGenerator|null */ private $entityGenerator = null; /** * @var ClassMetadataExporter|null */ private $metadataExporter = null; /** * @return EntityGenerator */ public function getEntityGenerator() { if ($this->entityGenerator == null) { $this->entityGenerator = new EntityGenerator(); } return $this->entityGenerator; } /** * @param EntityGenerator $entityGenerator * * @return void */ public function setEntityGenerator(EntityGenerator $entityGenerator) { $this->entityGenerator = $entityGenerator; } /** * @return ClassMetadataExporter */ public function getMetadataExporter() { if ($this->metadataExporter == null) { $this->metadataExporter = new ClassMetadataExporter(); } return $this->metadataExporter; } /** * @param ClassMetadataExporter $metadataExporter * * @return void */ public function setMetadataExporter(ClassMetadataExporter $metadataExporter) { $this->metadataExporter = $metadataExporter; } /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:convert-d1-schema') ->setAliases(array('orm:convert:d1-schema')) ->setDescription('Converts Doctrine 1.X schema into a Doctrine 2.X schema.') ->setDefinition(array( new InputArgument( 'from-path', InputArgument::REQUIRED, 'The path of Doctrine 1.X schema information.' ), new InputArgument( 'to-type', InputArgument::REQUIRED, 'The destination Doctrine 2.X mapping type.' ), new InputArgument( 'dest-path', InputArgument::REQUIRED, 'The path to generate your Doctrine 2.X mapping information.' ), new InputOption( 'from', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Optional paths of Doctrine 1.X schema information.', array() ), new InputOption( 'extend', null, InputOption::VALUE_OPTIONAL, 'Defines a base class to be extended by generated entity classes.' ), new InputOption( 'num-spaces', null, InputOption::VALUE_OPTIONAL, 'Defines the number of indentation spaces', 4 ) )) ->setHelp(<<getArgument('from-path')), $input->getOption('from')); // Process destination directory $destPath = realpath($input->getArgument('dest-path')); $toType = $input->getArgument('to-type'); $extend = $input->getOption('extend'); $numSpaces = $input->getOption('num-spaces'); $this->convertDoctrine1Schema($fromPaths, $destPath, $toType, $numSpaces, $extend, $output); } /** * @param array $fromPaths * @param string $destPath * @param string $toType * @param int $numSpaces * @param string|null $extend * @param OutputInterface $output * * @throws \InvalidArgumentException */ public function convertDoctrine1Schema(array $fromPaths, $destPath, $toType, $numSpaces, $extend, OutputInterface $output) { foreach ($fromPaths as &$dirName) { $dirName = realpath($dirName); if ( ! file_exists($dirName)) { throw new \InvalidArgumentException( sprintf("Doctrine 1.X schema directory '%s' does not exist.", $dirName) ); } if ( ! is_readable($dirName)) { throw new \InvalidArgumentException( sprintf("Doctrine 1.X schema directory '%s' does not have read permissions.", $dirName) ); } } if ( ! file_exists($destPath)) { throw new \InvalidArgumentException( sprintf("Doctrine 2.X mapping destination directory '%s' does not exist.", $destPath) ); } if ( ! is_writable($destPath)) { throw new \InvalidArgumentException( sprintf("Doctrine 2.X mapping destination directory '%s' does not have write permissions.", $destPath) ); } $cme = $this->getMetadataExporter(); $exporter = $cme->getExporter($toType, $destPath); if (strtolower($toType) === 'annotation') { $entityGenerator = $this->getEntityGenerator(); $exporter->setEntityGenerator($entityGenerator); $entityGenerator->setNumSpaces($numSpaces); if ($extend !== null) { $entityGenerator->setClassToExtend($extend); } } $converter = new ConvertDoctrine1Schema($fromPaths); $metadata = $converter->getMetadata(); if ($metadata) { $output->writeln(''); foreach ($metadata as $class) { $output->writeln(sprintf('Processing entity "%s"', $class->name)); } $exporter->setMetadata($metadata); $exporter->export(); $output->writeln(PHP_EOL . sprintf( 'Converting Doctrine 1.X schema to "%s" mapping type in "%s"', $toType, $destPath )); } else { $output->writeln('No Metadata Classes to process.'); } } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php000066400000000000000000000170621257105210500274200ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Doctrine\ORM\Tools\Console\MetadataFilter; use Doctrine\ORM\Tools\Export\ClassMetadataExporter; use Doctrine\ORM\Tools\EntityGenerator; use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory; use Doctrine\ORM\Mapping\Driver\DatabaseDriver; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Command\Command; /** * Command to convert your mapping information between the various formats. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConvertMappingCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:convert-mapping') ->setAliases(array('orm:convert:mapping')) ->setDescription('Convert mapping information between supported formats.') ->setDefinition(array( new InputOption( 'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.' ), new InputArgument( 'to-type', InputArgument::REQUIRED, 'The mapping type to be converted.' ), new InputArgument( 'dest-path', InputArgument::REQUIRED, 'The path to generate your entities classes.' ), new InputOption( 'force', null, InputOption::VALUE_NONE, 'Force to overwrite existing mapping files.' ), new InputOption( 'from-database', null, null, 'Whether or not to convert mapping information from existing database.' ), new InputOption( 'extend', null, InputOption::VALUE_OPTIONAL, 'Defines a base class to be extended by generated entity classes.' ), new InputOption( '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(<<one-time command. It should not be necessary for you to call this method multiple times, especially when using the --from-database flag. Converting an existing database schema into mapping files only solves about 70-80% of the necessary mapping information. Additionally the detection from an existing database cannot detect inverse associations, inheritance types, entities with foreign keys as primary keys and many of the semantical operations on associations such as cascade. Hint: There is no need to convert YAML or XML mapping files to annotations every time you make changes. All mapping drivers are first class citizens in Doctrine 2 and can be used as runtime mapping for the ORM. Hint: If you have a database with tables that should not be managed by the ORM, you can use a DBAL functionality to filter the tables and sequences down on a global level: \$config->setFilterSchemaAssetsExpression(\$regexp); EOT ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); if ($input->getOption('from-database') === true) { $databaseDriver = new DatabaseDriver( $em->getConnection()->getSchemaManager() ); $em->getConfiguration()->setMetadataDriverImpl( $databaseDriver ); if (($namespace = $input->getOption('namespace')) !== null) { $databaseDriver->setNamespace($namespace); } } $cmf = new DisconnectedClassMetadataFactory(); $cmf->setEntityManager($em); $metadata = $cmf->getAllMetadata(); $metadata = MetadataFilter::filter($metadata, $input->getOption('filter')); // Process destination directory if ( ! is_dir($destPath = $input->getArgument('dest-path'))) { mkdir($destPath, 0775, true); } $destPath = realpath($destPath); if ( ! file_exists($destPath)) { throw new \InvalidArgumentException( sprintf("Mapping destination directory '%s' does not exist.", $input->getArgument('dest-path')) ); } if ( ! is_writable($destPath)) { throw new \InvalidArgumentException( sprintf("Mapping destination directory '%s' does not have write permissions.", $destPath) ); } $toType = strtolower($input->getArgument('to-type')); $exporter = $this->getExporter($toType, $destPath); $exporter->setOverwriteExistingFiles($input->getOption('force')); if ($toType == 'annotation') { $entityGenerator = new EntityGenerator(); $exporter->setEntityGenerator($entityGenerator); $entityGenerator->setNumSpaces($input->getOption('num-spaces')); if (($extend = $input->getOption('extend')) !== null) { $entityGenerator->setClassToExtend($extend); } } if (count($metadata)) { foreach ($metadata as $class) { $output->writeln(sprintf('Processing entity "%s"', $class->name)); } $exporter->setMetadata($metadata); $exporter->export(); $output->writeln(PHP_EOL . sprintf( 'Exporting "%s" mapping information to "%s"', $toType, $destPath )); } else { $output->writeln('No Metadata Classes to process.'); } } /** * @param string $toType * @param string $destPath * * @return \Doctrine\ORM\Tools\Export\Driver\AbstractExporter */ protected function getExporter($toType, $destPath) { $cme = new ClassMetadataExporter(); return $cme->getExporter($toType, $destPath); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/EnsureProductionSettingsCommand.php000066400000000000000000000055041257105210500315130ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Command to ensure that Doctrine is properly configured for a production environment. * * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EnsureProductionSettingsCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:ensure-production-settings') ->setDescription('Verify that Doctrine is properly configured for a production environment.') ->setDefinition(array( new InputOption( 'complete', null, InputOption::VALUE_NONE, 'Flag to also inspect database connection existence.' ) )) ->setHelp(<<getHelper('em')->getEntityManager(); try { $em->getConfiguration()->ensureProductionSettings(); if ($input->getOption('complete') !== null) { $em->getConnection()->connect(); } } catch (\Exception $e) { $output->writeln('' . $e->getMessage() . ''); return 1; } $output->writeln('Environment is correctly configured for production.'); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/GenerateEntitiesCommand.php000066400000000000000000000156771257105210500277350ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Doctrine\ORM\Tools\Console\MetadataFilter; use Doctrine\ORM\Tools\EntityGenerator; use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Command\Command; /** * Command to generate entity classes and method stubs from your mapping information. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GenerateEntitiesCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:generate-entities') ->setAliases(array('orm:generate:entities')) ->setDescription('Generate entity classes and method stubs from your mapping information.') ->setDefinition(array( new InputOption( 'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.' ), new InputArgument( 'dest-path', InputArgument::REQUIRED, 'The path to generate your entity classes.' ), new InputOption( 'generate-annotations', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should generate annotation metadata on entities.', false ), new InputOption( 'generate-methods', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should generate stub methods on entities.', true ), new InputOption( 'regenerate-entities', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should regenerate entity if it exists.', false ), new InputOption( 'update-entities', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should only update entity if it exists.', true ), new InputOption( 'extend', null, InputOption::VALUE_OPTIONAL, 'Defines a base class to be extended by generated entity classes.' ), new InputOption( 'num-spaces', null, InputOption::VALUE_OPTIONAL, 'Defines the number of indentation spaces', 4 ) )) ->setHelp(<<--update-entities or --regenerate-entities flags your existing code gets overwritten. The EntityGenerator will only append new code to your file and will not delete the old code. However this approach may still be prone to error and we suggest you use code repositories such as GIT or SVN to make backups of your code. It makes sense to generate the entity code if you are using entities as Data Access Objects only and don't put much additional logic on them. If you are however putting much more logic on the entities you should refrain from using the entity-generator and code your entities manually. Important: Even if you specified Inheritance options in your XML or YAML Mapping files the generator cannot generate the base and child classes for you correctly, because it doesn't know which class is supposed to extend which. You have to adjust the entity code manually for inheritance to work! EOT ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); $cmf = new DisconnectedClassMetadataFactory(); $cmf->setEntityManager($em); $metadatas = $cmf->getAllMetadata(); $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); // Process destination directory $destPath = realpath($input->getArgument('dest-path')); if ( ! file_exists($destPath)) { throw new \InvalidArgumentException( sprintf("Entities destination directory '%s' does not exist.", $input->getArgument('dest-path')) ); } if ( ! is_writable($destPath)) { throw new \InvalidArgumentException( sprintf("Entities destination directory '%s' does not have write permissions.", $destPath) ); } if (count($metadatas)) { // Create EntityGenerator $entityGenerator = new EntityGenerator(); $entityGenerator->setGenerateAnnotations($input->getOption('generate-annotations')); $entityGenerator->setGenerateStubMethods($input->getOption('generate-methods')); $entityGenerator->setRegenerateEntityIfExists($input->getOption('regenerate-entities')); $entityGenerator->setUpdateEntityIfExists($input->getOption('update-entities')); $entityGenerator->setNumSpaces($input->getOption('num-spaces')); if (($extend = $input->getOption('extend')) !== null) { $entityGenerator->setClassToExtend($extend); } foreach ($metadatas as $metadata) { $output->writeln( sprintf('Processing entity "%s"', $metadata->name) ); } // Generating Entities $entityGenerator->generate($metadatas, $destPath); // Outputting information message $output->writeln(PHP_EOL . sprintf('Entity classes generated to "%s"', $destPath)); } else { $output->writeln('No Metadata Classes to process.'); } } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/GenerateProxiesCommand.php000066400000000000000000000103031257105210500275570ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Doctrine\ORM\Tools\Console\MetadataFilter; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Command\Command; /** * Command to (re)generate the proxy classes used by doctrine. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GenerateProxiesCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:generate-proxies') ->setAliases(array('orm:generate:proxies')) ->setDescription('Generates proxy classes for entity classes.') ->setDefinition(array( new InputOption( 'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.' ), new InputArgument( 'dest-path', InputArgument::OPTIONAL, 'The path to generate your proxy classes. If none is provided, it will attempt to grab from configuration.' ), )) ->setHelp(<<getHelper('em')->getEntityManager(); $metadatas = $em->getMetadataFactory()->getAllMetadata(); $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); // Process destination directory if (($destPath = $input->getArgument('dest-path')) === null) { $destPath = $em->getConfiguration()->getProxyDir(); } if ( ! is_dir($destPath)) { mkdir($destPath, 0775, true); } $destPath = realpath($destPath); if ( ! file_exists($destPath)) { throw new \InvalidArgumentException( sprintf("Proxies destination directory '%s' does not exist.", $em->getConfiguration()->getProxyDir()) ); } if ( ! is_writable($destPath)) { throw new \InvalidArgumentException( sprintf("Proxies destination directory '%s' does not have write permissions.", $destPath) ); } if ( count($metadatas)) { foreach ($metadatas as $metadata) { $output->writeln( sprintf('Processing entity "%s"', $metadata->name) ); } // Generating Proxies $em->getProxyFactory()->generateProxyClasses($metadatas, $destPath); // Outputting information message $output->writeln(PHP_EOL . sprintf('Proxy classes generated to "%s"', $destPath)); } else { $output->writeln('No Metadata Classes to process.'); } } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php000066400000000000000000000106411257105210500306220ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Doctrine\ORM\Tools\Console\MetadataFilter; use Doctrine\ORM\Tools\EntityRepositoryGenerator; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Command\Command; /** * Command to generate repository classes for mapping information. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GenerateRepositoriesCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:generate-repositories') ->setAliases(array('orm:generate:repositories')) ->setDescription('Generate repository classes from your mapping information.') ->setDefinition(array( new InputOption( 'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.' ), new InputArgument( 'dest-path', InputArgument::REQUIRED, 'The path to generate your repository classes.' ) )) ->setHelp(<<getHelper('em')->getEntityManager(); $metadatas = $em->getMetadataFactory()->getAllMetadata(); $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); // Process destination directory $destPath = realpath($input->getArgument('dest-path')); if ( ! file_exists($destPath)) { throw new \InvalidArgumentException( sprintf("Entities destination directory '%s' does not exist.", $input->getArgument('dest-path')) ); } if ( ! is_writable($destPath)) { throw new \InvalidArgumentException( sprintf("Entities destination directory '%s' does not have write permissions.", $destPath) ); } if (count($metadatas)) { $numRepositories = 0; $generator = new EntityRepositoryGenerator(); foreach ($metadatas as $metadata) { if ($metadata->customRepositoryClassName) { $output->writeln( sprintf('Processing repository "%s"', $metadata->customRepositoryClassName) ); $generator->writeEntityRepositoryClass($metadata->customRepositoryClassName, $destPath); $numRepositories++; } } if ($numRepositories) { // Outputting information message $output->writeln(PHP_EOL . sprintf('Repository classes generated to "%s"', $destPath) ); } else { $output->writeln('No Repository classes were found to be processed.' ); } } else { $output->writeln('No Metadata Classes to process.' ); } } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/InfoCommand.php000066400000000000000000000063521257105210500253570ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\ORM\Mapping\MappingException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Command\Command; /** * Show information about mapped entities. * * @link www.doctrine-project.org * @since 2.1 * @author Benjamin Eberlei */ class InfoCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:info') ->setDescription('Show basic information about all mapped entities') ->setHelp(<<%command.name% shows basic information about which entities exist and possibly if their mapping information contains errors or not. EOT ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { /* @var $entityManager \Doctrine\ORM\EntityManager */ $entityManager = $this->getHelper('em')->getEntityManager(); $entityClassNames = $entityManager->getConfiguration() ->getMetadataDriverImpl() ->getAllClassNames(); if (!$entityClassNames) { throw new \Exception( 'You do not have any mapped Doctrine ORM entities according to the current configuration. '. 'If you have entities or mapping files you should check your mapping configuration for errors.' ); } $output->writeln(sprintf("Found %d mapped entities:", count($entityClassNames))); $failure = false; foreach ($entityClassNames as $entityClassName) { try { $entityManager->getClassMetadata($entityClassName); $output->writeln(sprintf("[OK] %s", $entityClassName)); } catch (MappingException $e) { $output->writeln("[FAIL] ".$entityClassName); $output->writeln(sprintf("%s", $e->getMessage())); $output->writeln(''); $failure = true; } } return $failure ? 1 : 0; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php000066400000000000000000000107761257105210500256760ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Doctrine\Common\Util\Debug; /** * Command to execute DQL queries in a given EntityManager. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class RunDqlCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:run-dql') ->setDescription('Executes arbitrary DQL directly from the command line.') ->setDefinition(array( new InputArgument('dql', InputArgument::REQUIRED, 'The DQL to execute.'), new InputOption( 'hydrate', null, InputOption::VALUE_REQUIRED, 'Hydration mode of result set. Should be either: object, array, scalar or single-scalar.', 'object' ), new InputOption( 'first-result', null, InputOption::VALUE_REQUIRED, 'The first result in the result set.' ), new InputOption( 'max-result', null, InputOption::VALUE_REQUIRED, 'The maximum number of results in the result set.' ), new InputOption( 'depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of Entity graph.', 7 ) )) ->setHelp(<<getHelper('em')->getEntityManager(); if (($dql = $input->getArgument('dql')) === null) { throw new \RuntimeException("Argument 'DQL' is required in order to execute this command correctly."); } $depth = $input->getOption('depth'); if ( ! is_numeric($depth)) { throw new \LogicException("Option 'depth' must contains an integer value"); } $hydrationModeName = $input->getOption('hydrate'); $hydrationMode = 'Doctrine\ORM\Query::HYDRATE_' . strtoupper(str_replace('-', '_', $hydrationModeName)); if ( ! defined($hydrationMode)) { throw new \RuntimeException( "Hydration mode '$hydrationModeName' does not exist. It should be either: object. array, scalar or single-scalar." ); } $query = $em->createQuery($dql); if (($firstResult = $input->getOption('first-result')) !== null) { if ( ! is_numeric($firstResult)) { throw new \LogicException("Option 'first-result' must contains an integer value"); } $query->setFirstResult((int) $firstResult); } if (($maxResult = $input->getOption('max-result')) !== null) { if ( ! is_numeric($maxResult)) { throw new \LogicException("Option 'max-result' must contains an integer value"); } $query->setMaxResults((int) $maxResult); } $resultSet = $query->execute(array(), constant($hydrationMode)); Debug::dump($resultSet, $input->getOption('depth')); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/000077500000000000000000000000001257105210500245045ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/AbstractCommand.php000066400000000000000000000051601257105210500302610ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Command\Command; use Doctrine\ORM\Tools\SchemaTool; /** * Base class for CreateCommand, DropCommand and UpdateCommand. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ abstract class AbstractCommand extends Command { /** * @param InputInterface $input * @param OutputInterface $output * @param SchemaTool $schemaTool * @param array $metadatas * * @return null|int Null or 0 if everything went fine, or an error code. */ abstract protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas); /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $emHelper = $this->getHelper('em'); /* @var $em \Doctrine\ORM\EntityManager */ $em = $emHelper->getEntityManager(); $metadatas = $em->getMetadataFactory()->getAllMetadata(); if ( ! empty($metadatas)) { // Create SchemaTool $tool = new SchemaTool($em); return $this->executeSchemaCommand($input, $output, $tool, $metadatas); } else { $output->writeln('No Metadata Classes to process.'); return 0; } } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/CreateCommand.php000066400000000000000000000064551257105210500277310ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Doctrine\ORM\Tools\SchemaTool; /** * Command to create the database schema for a set of classes based on their mappings. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class CreateCommand extends AbstractCommand { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:schema-tool:create') ->setDescription( 'Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output.' ) ->setDefinition(array( new InputOption( 'dump-sql', null, InputOption::VALUE_NONE, 'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.' ) )) ->setHelp(<<Hint: If you have a database with tables that should not be managed by the ORM, you can use a DBAL functionality to filter the tables and sequences down on a global level: \$config->setFilterSchemaAssetsExpression(\$regexp); EOT ); } /** * {@inheritdoc} */ protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas) { if ($input->getOption('dump-sql')) { $sqls = $schemaTool->getCreateSchemaSql($metadatas); $output->writeln(implode(';' . PHP_EOL, $sqls) . ';'); } else { $output->writeln('ATTENTION: This operation should not be executed in a production environment.' . PHP_EOL); $output->writeln('Creating database schema...'); $schemaTool->createSchema($metadatas); $output->writeln('Database schema created successfully!'); } return 0; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/DropCommand.php000066400000000000000000000114131257105210500274200ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Doctrine\ORM\Tools\SchemaTool; /** * Command to drop the database schema for a set of classes based on their mappings. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class DropCommand extends AbstractCommand { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:schema-tool:drop') ->setDescription( 'Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output.' ) ->setDefinition(array( new InputOption( 'dump-sql', null, InputOption::VALUE_NONE, 'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.' ), new InputOption( 'force', null, InputOption::VALUE_NONE, "Don't ask for the deletion of the database, but force the operation to run." ), new InputOption( 'full-database', null, InputOption::VALUE_NONE, 'Instead of using the Class Metadata to detect the database table schema, drop ALL assets that the database contains.' ), )) ->setHelp(<<Hint: If you have a database with tables that should not be managed by the ORM, you can use a DBAL functionality to filter the tables and sequences down on a global level: \$config->setFilterSchemaAssetsExpression(\$regexp); EOT ); } /** * {@inheritdoc} */ protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas) { $isFullDatabaseDrop = $input->getOption('full-database'); if ($input->getOption('dump-sql')) { if ($isFullDatabaseDrop) { $sqls = $schemaTool->getDropDatabaseSQL(); } else { $sqls = $schemaTool->getDropSchemaSQL($metadatas); } $output->writeln(implode(';' . PHP_EOL, $sqls)); return 0; } if ($input->getOption('force')) { $output->writeln('Dropping database schema...'); if ($isFullDatabaseDrop) { $schemaTool->dropDatabase(); } else { $schemaTool->dropSchema($metadatas); } $output->writeln('Database schema dropped successfully!'); return 0; } $output->writeln('ATTENTION: This operation should not be executed in a production environment.' . PHP_EOL); if ($isFullDatabaseDrop) { $sqls = $schemaTool->getDropDatabaseSQL(); } else { $sqls = $schemaTool->getDropSchemaSQL($metadatas); } if (count($sqls)) { $output->writeln('Schema-Tool would execute ' . count($sqls) . ' queries to drop the database.'); $output->writeln('Please run the operation with --force to execute these queries or use --dump-sql to see them.'); return 1; } $output->writeln('Nothing to drop. The database is empty!'); return 0; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php000066400000000000000000000137621257105210500277470ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Doctrine\ORM\Tools\SchemaTool; /** * Command to generate the SQL needed to update the database schema to match * the current mapping information. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Ryan Weaver */ class UpdateCommand extends AbstractCommand { /** * @var string */ protected $name = 'orm:schema-tool:update'; /** * {@inheritdoc} */ protected function configure() { $this ->setName($this->name) ->setDescription( '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, 'Dumps the generated SQL statements to the screen (does not execute them).' ), new InputOption( 'force', null, InputOption::VALUE_NONE, 'Causes the generated SQL statements to be physically executed against your database.' ), )); $this->setHelp(<<%command.name% 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: %command.name% --dump-sql Alternatively, you can execute the generated queries: %command.name% --force If both options are specified, the queries are output and then executed: %command.name% --dump-sql --force Finally, be aware that if the --complete 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. Hint: If you have a database with tables that should not be managed by the ORM, you can use a DBAL functionality to filter the tables and sequences down on a global level: \$config->setFilterSchemaAssetsExpression(\$regexp); EOT ); } /** * {@inheritdoc} */ protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas) { // Defining if update is complete or not (--complete not defined means $saveMode = true) $saveMode = ! $input->getOption('complete'); $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 0; } $dumpSql = true === $input->getOption('dump-sql'); $force = true === $input->getOption('force'); if ($dumpSql) { $output->writeln(implode(';' . PHP_EOL, $sqls) . ';'); } if ($force) { if ($dumpSql) { $output->writeln(''); } $output->writeln('Updating database schema...'); $schemaTool->updateSchema($metadatas, $saveMode); $output->writeln(sprintf('Database schema updated successfully! "%s" queries were executed', count($sqls))); } if ($dumpSql || $force) { return 0; } $output->writeln('ATTENTION: 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(''); $output->writeln(sprintf('The Schema-Tool would execute "%s" queries to update the database.', count($sqls))); $output->writeln('Please run the operation by passing one - or both - of the following options:'); $output->writeln(sprintf(' %s --force to execute the command', $this->getName())); $output->writeln(sprintf(' %s --dump-sql to dump the SQL statements to the screen', $this->getName())); return 1; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php000066400000000000000000000062641257105210500273400ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Doctrine\ORM\Tools\SchemaValidator; /** * Command to validate that the current mapping is valid. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ValidateSchemaCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('orm:validate-schema') ->setDescription('Validate the mapping files.') ->setHelp(<<getHelper('em')->getEntityManager(); $validator = new SchemaValidator($em); $errors = $validator->validateMapping(); $exit = 0; if ($errors) { foreach ($errors as $className => $errorMessages) { $output->writeln("[Mapping] FAIL - The entity-class '" . $className . "' mapping is invalid:"); foreach ($errorMessages as $errorMessage) { $output->writeln('* ' . $errorMessage); } $output->writeln(''); } $exit += 1; } else { $output->writeln('[Mapping] OK - The mapping files are correct.'); } if (!$validator->schemaInSyncWithMetadata()) { $output->writeln('[Database] FAIL - The database schema is not in sync with the current mapping file.'); $exit += 2; } else { $output->writeln('[Database] OK - The database schema is in sync with the mapping files.'); } return $exit; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php000066400000000000000000000107021257105210500243750ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\HelperSet; use Doctrine\ORM\Version; use Doctrine\ORM\EntityManagerInterface; use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper; use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper; /** * Handles running the Console Tools inside Symfony Console context. */ class ConsoleRunner { /** * Create a Symfony Console HelperSet * * @param EntityManagerInterface $entityManager * @return HelperSet */ public static function createHelperSet(EntityManagerInterface $entityManager) { return new HelperSet(array( 'db' => new ConnectionHelper($entityManager->getConnection()), 'em' => new EntityManagerHelper($entityManager) )); } /** * Runs console with the given helperset. * * @param \Symfony\Component\Console\Helper\HelperSet $helperSet * @param \Symfony\Component\Console\Command\Command[] $commands * * @return void */ static public function run(HelperSet $helperSet, $commands = array()) { $cli = new Application('Doctrine Command Line Interface', Version::VERSION); $cli->setCatchExceptions(true); $cli->setHelperSet($helperSet); self::addCommands($cli); $cli->addCommands($commands); $cli->run(); } /** * @param Application $cli * * @return void */ static public function addCommands(Application $cli) { $cli->addCommands(array( // DBAL Commands new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(), new \Doctrine\DBAL\Tools\Console\Command\ImportCommand(), // ORM Commands new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand(), new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand(), new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand(), new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand(), new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand(), new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand(), new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand(), new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand(), new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand(), new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand(), new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand(), new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(), new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(), new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(), new \Doctrine\ORM\Tools\Console\Command\InfoCommand() )); } static public function printCliConfigTemplate() { echo <<<'HELP' You are missing a "cli-config.php" or "config/cli-config.php" file in your project, which is required to get the Doctrine Console working. You can use the following sample as a template: . */ namespace Doctrine\ORM\Tools\Console\Helper; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Helper\Helper; /** * Doctrine CLI Connection Helper. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EntityManagerHelper extends Helper { /** * Doctrine ORM EntityManagerInterface. * * @var EntityManagerInterface */ protected $_em; /** * Constructor. * * @param EntityManagerInterface $em */ public function __construct(EntityManagerInterface $em) { $this->_em = $em; } /** * Retrieves Doctrine ORM EntityManager. * * @return EntityManagerInterface */ public function getEntityManager() { return $this->_em; } /** * {@inheritdoc} */ public function getName() { return 'entityManager'; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Console/MetadataFilter.php000066400000000000000000000053401257105210500244710ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Console; /** * Used by CLI Tools to restrict entity-based commands to given patterns. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class MetadataFilter extends \FilterIterator implements \Countable { /** * @var array */ private $filter = array(); /** * Filter Metadatas by one or more filter options. * * @param array $metadatas * @param array|string $filter * * @return array */ static public function filter(array $metadatas, $filter) { $metadatas = new MetadataFilter(new \ArrayIterator($metadatas), $filter); return iterator_to_array($metadatas); } /** * @param \ArrayIterator $metadata * @param array|string $filter */ public function __construct(\ArrayIterator $metadata, $filter) { $this->filter = (array) $filter; parent::__construct($metadata); } /** * @return bool */ public function accept() { if (count($this->filter) == 0) { return true; } $it = $this->getInnerIterator(); $metadata = $it->current(); foreach ($this->filter as $filter) { if (strpos($metadata->name, $filter) !== false) { return true; } } return false; } /** * @return int */ public function count() { return count($this->getInnerIterator()); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php000066400000000000000000000257171257105210500245250ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\Common\Util\Inflector; use Doctrine\DBAL\Types\Type; use Symfony\Component\Yaml\Yaml; /** * Class to help with converting Doctrine 1 schema files to Doctrine 2 mapping files * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConvertDoctrine1Schema { /** * @var array */ private $from; /** * @var array */ private $legacyTypeMap = array( // TODO: This list may need to be updated 'clob' => 'text', 'timestamp' => 'datetime', 'enum' => 'string' ); /** * Constructor passes the directory or array of directories * to convert the Doctrine 1 schema files from. * * @param array $from * * @author Jonathan Wage */ public function __construct($from) { $this->from = (array) $from; } /** * Gets an array of ClassMetadataInfo instances from the passed * Doctrine 1 schema. * * @return array An array of ClassMetadataInfo instances */ public function getMetadata() { $schema = array(); foreach ($this->from as $path) { if (is_dir($path)) { $files = glob($path . '/*.yml'); foreach ($files as $file) { $schema = array_merge($schema, (array) Yaml::parse($file)); } } else { $schema = array_merge($schema, (array) Yaml::parse($path)); } } $metadatas = array(); foreach ($schema as $className => $mappingInformation) { $metadatas[] = $this->convertToClassMetadataInfo($className, $mappingInformation); } return $metadatas; } /** * @param string $className * @param array $mappingInformation * * @return \Doctrine\ORM\Mapping\ClassMetadataInfo */ private function convertToClassMetadataInfo($className, $mappingInformation) { $metadata = new ClassMetadataInfo($className); $this->convertTableName($className, $mappingInformation, $metadata); $this->convertColumns($className, $mappingInformation, $metadata); $this->convertIndexes($className, $mappingInformation, $metadata); $this->convertRelations($className, $mappingInformation, $metadata); return $metadata; } /** * @param string $className * @param array $model * @param ClassMetadataInfo $metadata * * @return void */ private function convertTableName($className, array $model, ClassMetadataInfo $metadata) { if (isset($model['tableName']) && $model['tableName']) { $e = explode('.', $model['tableName']); if (count($e) > 1) { $metadata->table['schema'] = $e[0]; $metadata->table['name'] = $e[1]; } else { $metadata->table['name'] = $e[0]; } } } /** * @param string $className * @param array $model * @param ClassMetadataInfo $metadata * * @return void */ private function convertColumns($className, array $model, ClassMetadataInfo $metadata) { $id = false; if (isset($model['columns']) && $model['columns']) { foreach ($model['columns'] as $name => $column) { $fieldMapping = $this->convertColumn($className, $name, $column, $metadata); if (isset($fieldMapping['id']) && $fieldMapping['id']) { $id = true; } } } if ( ! $id) { $fieldMapping = array( 'fieldName' => 'id', 'columnName' => 'id', 'type' => 'integer', 'id' => true ); $metadata->mapField($fieldMapping); $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); } } /** * @param string $className * @param string $name * @param string|array $column * @param ClassMetadataInfo $metadata * * @return array * * @throws ToolsException */ private function convertColumn($className, $name, $column, ClassMetadataInfo $metadata) { if (is_string($column)) { $string = $column; $column = array(); $column['type'] = $string; } if ( ! isset($column['name'])) { $column['name'] = $name; } // check if a column alias was used (column_name as field_name) if (preg_match("/(\w+)\sas\s(\w+)/i", $column['name'], $matches)) { $name = $matches[1]; $column['name'] = $name; $column['alias'] = $matches[2]; } if (preg_match("/([a-zA-Z]+)\(([0-9]+)\)/", $column['type'], $matches)) { $column['type'] = $matches[1]; $column['length'] = $matches[2]; } $column['type'] = strtolower($column['type']); // check if legacy column type (1.x) needs to be mapped to a 2.0 one if (isset($this->legacyTypeMap[$column['type']])) { $column['type'] = $this->legacyTypeMap[$column['type']]; } if ( ! Type::hasType($column['type'])) { throw ToolsException::couldNotMapDoctrine1Type($column['type']); } $fieldMapping = array(); if (isset($column['primary'])) { $fieldMapping['id'] = true; } $fieldMapping['fieldName'] = isset($column['alias']) ? $column['alias'] : $name; $fieldMapping['columnName'] = $column['name']; $fieldMapping['type'] = $column['type']; if (isset($column['length'])) { $fieldMapping['length'] = $column['length']; } $allowed = array('precision', 'scale', 'unique', 'options', 'notnull', 'version'); foreach ($column as $key => $value) { if (in_array($key, $allowed)) { $fieldMapping[$key] = $value; } } $metadata->mapField($fieldMapping); if (isset($column['autoincrement'])) { $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); } elseif (isset($column['sequence'])) { $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE); $definition = array( 'sequenceName' => is_array($column['sequence']) ? $column['sequence']['name']:$column['sequence'] ); if (isset($column['sequence']['size'])) { $definition['allocationSize'] = $column['sequence']['size']; } if (isset($column['sequence']['value'])) { $definition['initialValue'] = $column['sequence']['value']; } $metadata->setSequenceGeneratorDefinition($definition); } return $fieldMapping; } /** * @param string $className * @param array $model * @param ClassMetadataInfo $metadata * * @return void */ private function convertIndexes($className, array $model, ClassMetadataInfo $metadata) { if (empty($model['indexes'])) { return; } foreach ($model['indexes'] as $name => $index) { $type = (isset($index['type']) && $index['type'] == 'unique') ? 'uniqueConstraints' : 'indexes'; $metadata->table[$type][$name] = array( 'columns' => $index['fields'] ); } } /** * @param string $className * @param array $model * @param ClassMetadataInfo $metadata * * @return void */ private function convertRelations($className, array $model, ClassMetadataInfo $metadata) { if (empty($model['relations'])) { return; } foreach ($model['relations'] as $name => $relation) { if ( ! isset($relation['alias'])) { $relation['alias'] = $name; } if ( ! isset($relation['class'])) { $relation['class'] = $name; } if ( ! isset($relation['local'])) { $relation['local'] = Inflector::tableize($relation['class']); } if ( ! isset($relation['foreign'])) { $relation['foreign'] = 'id'; } if ( ! isset($relation['foreignAlias'])) { $relation['foreignAlias'] = $className; } if (isset($relation['refClass'])) { $type = 'many'; $foreignType = 'many'; $joinColumns = array(); } else { $type = isset($relation['type']) ? $relation['type'] : 'one'; $foreignType = isset($relation['foreignType']) ? $relation['foreignType'] : 'many'; $joinColumns = array( array( 'name' => $relation['local'], 'referencedColumnName' => $relation['foreign'], 'onDelete' => isset($relation['onDelete']) ? $relation['onDelete'] : null, ) ); } if ($type == 'one' && $foreignType == 'one') { $method = 'mapOneToOne'; } elseif ($type == 'many' && $foreignType == 'many') { $method = 'mapManyToMany'; } else { $method = 'mapOneToMany'; } $associationMapping = array(); $associationMapping['fieldName'] = $relation['alias']; $associationMapping['targetEntity'] = $relation['class']; $associationMapping['mappedBy'] = $relation['foreignAlias']; $associationMapping['joinColumns'] = $joinColumns; $metadata->$method($associationMapping); } } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/DebugUnitOfWorkListener.php000066400000000000000000000137271257105210500247350ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools; use Doctrine\Common\Persistence\Proxy; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\UnitOfWork; use Doctrine\ORM\EntityManager; /** * 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 { /** * @var string */ private $file; /** * @var string */ private $context; /** * Pass a stream and context 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; } /** * @param \Doctrine\ORM\Event\OnFlushEventArgs $args * * @return void */ public function onFlush(OnFlushEventArgs $args) { $this->dumpIdentityMap($args->getEntityManager()); } /** * Dumps 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 $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->getFieldValue($entity, $field); 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"); } elseif ($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); } /** * @param mixed $var * * @return string */ private function getType($var) { if (is_object($var)) { $refl = new \ReflectionObject($var); return $refl->getShortname(); } return gettype($var); } /** * @param object $entity * @param UnitOfWork $uow * * @return string */ private function getIdString($entity, UnitOfWork $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]"; } elseif ($state == UnitOfWork::STATE_REMOVED) { $idstring .= " [REMOVED]"; } elseif ($state == UnitOfWork::STATE_MANAGED) { $idstring .= " [MANAGED]"; } elseif ($state == UnitOfwork::STATE_DETACHED) { $idstring .= " [DETACHED]"; } return $idstring; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/DisconnectedClassMetadataFactory.php000066400000000000000000000036241257105210500265650ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools; use Doctrine\Common\Persistence\Mapping\StaticReflectionService; use Doctrine\ORM\Mapping\ClassMetadataFactory; /** * The DisconnectedClassMetadataFactory is used to create ClassMetadataInfo objects * that do not require the entity class actually exist. This allows us to * load some mapping information and use it to do things like generate code * from the mapping information. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class DisconnectedClassMetadataFactory extends ClassMetadataFactory { /** * @return \Doctrine\Common\Persistence\Mapping\StaticReflectionService */ public function getReflectionService() { return new StaticReflectionService(); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/EntityGenerator.php000066400000000000000000001360571257105210500233360ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\Common\Util\Inflector; use Doctrine\DBAL\Types\Type; /** * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances. * * [php] * $classes = $em->getClassMetadataFactory()->getAllMetadata(); * * $generator = new \Doctrine\ORM\Tools\EntityGenerator(); * $generator->setGenerateAnnotations(true); * $generator->setGenerateStubMethods(true); * $generator->setRegenerateEntityIfExists(false); * $generator->setUpdateEntityIfExists(true); * $generator->generate($classes, '/path/to/generate/entities'); * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EntityGenerator { /** * Specifies class fields should be protected. */ const FIELD_VISIBLE_PROTECTED = 'protected'; /** * Specifies class fields should be private. */ const FIELD_VISIBLE_PRIVATE = 'private'; /** * @var bool */ protected $backupExisting = true; /** * The extension to use for written php files. * * @var string */ protected $extension = '.php'; /** * Whether or not the current ClassMetadataInfo instance is new or old. * * @var boolean */ protected $isNew = true; /** * @var array */ protected $staticReflection = array(); /** * Number of spaces to use for indention in generated code. */ protected $numSpaces = 4; /** * The actual spaces to use for indention. * * @var string */ protected $spaces = ' '; /** * The class all generated entities should extend. * * @var string */ protected $classToExtend; /** * Whether or not to generation annotations. * * @var boolean */ protected $generateAnnotations = false; /** * @var string */ protected $annotationsPrefix = ''; /** * Whether or not to generate sub methods. * * @var boolean */ protected $generateEntityStubMethods = false; /** * Whether or not to update the entity class if it exists already. * * @var boolean */ protected $updateEntityIfExists = false; /** * Whether or not to re-generate entity class if it exists already. * * @var boolean */ protected $regenerateEntityIfExists = false; /** * @var boolean */ protected $fieldVisibility = 'private'; /** * Hash-map for handle types. * * @var array */ protected $typeAlias = array( Type::DATETIMETZ => '\DateTime', Type::DATETIME => '\DateTime', Type::DATE => '\DateTime', Type::TIME => '\DateTime', Type::OBJECT => '\stdClass', Type::BIGINT => 'integer', Type::SMALLINT => 'integer', Type::TEXT => 'string', Type::BLOB => 'string', Type::DECIMAL => 'string', Type::JSON_ARRAY => 'array', Type::SIMPLE_ARRAY => 'array', ); /** * Hash-map to handle generator types string. * * @var array */ protected static $generatorStrategyMap = array( ClassMetadataInfo::GENERATOR_TYPE_AUTO => 'AUTO', ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE => 'SEQUENCE', ClassMetadataInfo::GENERATOR_TYPE_TABLE => 'TABLE', ClassMetadataInfo::GENERATOR_TYPE_IDENTITY => 'IDENTITY', ClassMetadataInfo::GENERATOR_TYPE_NONE => 'NONE', ClassMetadataInfo::GENERATOR_TYPE_UUID => 'UUID', ClassMetadataInfo::GENERATOR_TYPE_CUSTOM => 'CUSTOM' ); /** * Hash-map to handle the change tracking policy string. * * @var array */ protected static $changeTrackingPolicyMap = array( ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT => 'DEFERRED_IMPLICIT', ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT => 'DEFERRED_EXPLICIT', ClassMetadataInfo::CHANGETRACKING_NOTIFY => 'NOTIFY', ); /** * Hash-map to handle the inheritance type string. * * @var array */ protected static $inheritanceTypeMap = array( ClassMetadataInfo::INHERITANCE_TYPE_NONE => 'NONE', ClassMetadataInfo::INHERITANCE_TYPE_JOINED => 'JOINED', ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE => 'SINGLE_TABLE', ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS', ); /** * @var string */ protected static $classTemplate = ' use Doctrine\ORM\Mapping as ORM; { } '; /** * @var string */ protected static $getMethodTemplate = '/** * * * @return */ public function () { return $this->; }'; /** * @var string */ protected static $setMethodTemplate = '/** * * * @param $ * @return */ public function ($) { $this-> = $; return $this; }'; /** * @var string */ protected static $addMethodTemplate = '/** * * * @param $ * @return */ public function ($) { $this->[] = $; return $this; }'; /** * @var string */ protected static $removeMethodTemplate = '/** * * * @param $ */ public function ($) { $this->->removeElement($); }'; /** * @var string */ protected static $lifecycleCallbackMethodTemplate = '/** * @ */ public function () { // Add your code here }'; /** * @var string */ protected static $constructorMethodTemplate = '/** * Constructor */ public function __construct() { } '; /** * Constructor. */ public function __construct() { if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) { $this->annotationsPrefix = 'ORM\\'; } } /** * Generates and writes entity classes for the given array of ClassMetadataInfo instances. * * @param array $metadatas * @param string $outputDirectory * * @return void */ public function generate(array $metadatas, $outputDirectory) { foreach ($metadatas as $metadata) { $this->writeEntityClass($metadata, $outputDirectory); } } /** * Generates and writes entity class to disk for the given ClassMetadataInfo instance. * * @param ClassMetadataInfo $metadata * @param string $outputDirectory * * @return void * * @throws \RuntimeException */ public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory) { $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension; $dir = dirname($path); if ( ! is_dir($dir)) { mkdir($dir, 0775, true); } $this->isNew = !file_exists($path) || (file_exists($path) && $this->regenerateEntityIfExists); if ( ! $this->isNew) { $this->parseTokensInEntityFile(file_get_contents($path)); } else { $this->staticReflection[$metadata->name] = array('properties' => array(), 'methods' => array()); } if ($this->backupExisting && file_exists($path)) { $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~"; if (!copy($path, $backupPath)) { throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed."); } } // If entity doesn't exist or we're re-generating the entities entirely if ($this->isNew) { file_put_contents($path, $this->generateEntityClass($metadata)); // If entity exists and we're allowed to update the entity class } elseif ( ! $this->isNew && $this->updateEntityIfExists) { file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path)); } chmod($path, 0664); } /** * Generates a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance. * * @param ClassMetadataInfo $metadata * * @return string */ public function generateEntityClass(ClassMetadataInfo $metadata) { $placeHolders = array( '', '', '', '' ); $replacements = array( $this->generateEntityNamespace($metadata), $this->generateEntityDocBlock($metadata), $this->generateEntityClassName($metadata), $this->generateEntityBody($metadata) ); $code = str_replace($placeHolders, $replacements, self::$classTemplate); return str_replace('', $this->spaces, $code); } /** * Generates the updated code for the given ClassMetadataInfo and entity at path. * * @param ClassMetadataInfo $metadata * @param string $path * * @return string */ public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path) { $currentCode = file_get_contents($path); $body = $this->generateEntityBody($metadata); $body = str_replace('', $this->spaces, $body); $last = strrpos($currentCode, '}'); return substr($currentCode, 0, $last) . $body . (strlen($body) > 0 ? "\n" : ''). "}\n"; } /** * Sets the number of spaces the exported class should have. * * @param integer $numSpaces * * @return void */ public function setNumSpaces($numSpaces) { $this->spaces = str_repeat(' ', $numSpaces); $this->numSpaces = $numSpaces; } /** * Sets the extension to use when writing php files to disk. * * @param string $extension * * @return void */ public function setExtension($extension) { $this->extension = $extension; } /** * Sets the name of the class the generated classes should extend from. * * @param string $classToExtend * * @return void */ public function setClassToExtend($classToExtend) { $this->classToExtend = $classToExtend; } /** * Sets whether or not to generate annotations for the entity. * * @param bool $bool * * @return void */ public function setGenerateAnnotations($bool) { $this->generateAnnotations = $bool; } /** * Sets the class fields visibility for the entity (can either be private or protected). * * @param bool $visibility * * @return void * * @throws \InvalidArgumentException */ public function setFieldVisibility($visibility) { if ($visibility !== self::FIELD_VISIBLE_PRIVATE && $visibility !== self::FIELD_VISIBLE_PROTECTED) { throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility); } $this->fieldVisibility = $visibility; } /** * Sets an annotation prefix. * * @param string $prefix * * @return void */ public function setAnnotationPrefix($prefix) { $this->annotationsPrefix = $prefix; } /** * Sets whether or not to try and update the entity if it already exists. * * @param bool $bool * * @return void */ public function setUpdateEntityIfExists($bool) { $this->updateEntityIfExists = $bool; } /** * Sets whether or not to regenerate the entity if it exists. * * @param bool $bool * * @return void */ public function setRegenerateEntityIfExists($bool) { $this->regenerateEntityIfExists = $bool; } /** * Sets whether or not to generate stub methods for the entity. * * @param bool $bool * * @return void */ public function setGenerateStubMethods($bool) { $this->generateEntityStubMethods = $bool; } /** * Should an existing entity be backed up if it already exists? * * @param bool $bool * * @return void */ public function setBackupExisting($bool) { $this->backupExisting = $bool; } /** * @param string $type * * @return string */ protected function getType($type) { if (isset($this->typeAlias[$type])) { return $this->typeAlias[$type]; } return $type; } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateEntityNamespace(ClassMetadataInfo $metadata) { if ($this->hasNamespace($metadata)) { return 'namespace ' . $this->getNamespace($metadata) .';'; } } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateEntityClassName(ClassMetadataInfo $metadata) { return 'class ' . $this->getClassName($metadata) . ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null); } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateEntityBody(ClassMetadataInfo $metadata) { $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata); $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata); $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null; $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata); $code = array(); if ($fieldMappingProperties) { $code[] = $fieldMappingProperties; } if ($associationMappingProperties) { $code[] = $associationMappingProperties; } $code[] = $this->generateEntityConstructor($metadata); if ($stubMethods) { $code[] = $stubMethods; } if ($lifecycleCallbackMethods) { $code[] = $lifecycleCallbackMethods; } return implode("\n", $code); } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateEntityConstructor(ClassMetadataInfo $metadata) { if ($this->hasMethod('__construct', $metadata)) { return ''; } $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("", implode("\n".$this->spaces, $collections), self::$constructorMethodTemplate)); } return ''; } /** * @todo this won't work if there is a namespace in brackets and a class outside of it. * * @param string $src * * @return void */ protected function parseTokensInEntityFile($src) { $tokens = token_get_all($src); $lastSeenNamespace = ""; $lastSeenClass = false; $inNamespace = false; $inClass = false; for ($i = 0; $i < count($tokens); $i++) { $token = $tokens[$i]; if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) { continue; } if ($inNamespace) { if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) { $lastSeenNamespace .= $token[1]; } elseif (is_string($token) && in_array($token, array(';', '{'))) { $inNamespace = false; } } if ($inClass) { $inClass = false; $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1]; $this->staticReflection[$lastSeenClass]['properties'] = array(); $this->staticReflection[$lastSeenClass]['methods'] = array(); } if ($token[0] == T_NAMESPACE) { $lastSeenNamespace = ""; $inNamespace = true; } elseif ($token[0] == T_CLASS && $tokens[$i-1][0] != T_DOUBLE_COLON) { $inClass = true; } elseif ($token[0] == T_FUNCTION) { if ($tokens[$i+2][0] == T_STRING) { $this->staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+2][1]; } elseif ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) { $this->staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+3][1]; } } elseif (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) { $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1); } } } /** * @param string $property * @param ClassMetadataInfo $metadata * * @return bool */ protected function hasProperty($property, ClassMetadataInfo $metadata) { if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) { // don't generate property if its already on the base class. $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name); if ($reflClass->hasProperty($property)) { return true; } } return ( isset($this->staticReflection[$metadata->name]) && in_array($property, $this->staticReflection[$metadata->name]['properties']) ); } /** * @param string $method * @param ClassMetadataInfo $metadata * * @return bool */ protected function hasMethod($method, ClassMetadataInfo $metadata) { if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) { // don't generate method if its already on the base class. $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name); if ($reflClass->hasMethod($method)) { return true; } } return ( isset($this->staticReflection[$metadata->name]) && in_array($method, $this->staticReflection[$metadata->name]['methods']) ); } /** * @param ClassMetadataInfo $metadata * * @return bool */ protected function hasNamespace(ClassMetadataInfo $metadata) { return strpos($metadata->name, '\\') ? true : false; } /** * @return bool */ protected function extendsClass() { return $this->classToExtend ? true : false; } /** * @return string */ protected function getClassToExtend() { return $this->classToExtend; } /** * @return string */ protected function getClassToExtendName() { $refl = new \ReflectionClass($this->getClassToExtend()); return '\\' . $refl->getName(); } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function getClassName(ClassMetadataInfo $metadata) { return ($pos = strrpos($metadata->name, '\\')) ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name; } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function getNamespace(ClassMetadataInfo $metadata) { return substr($metadata->name, 0, strrpos($metadata->name, '\\')); } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateEntityDocBlock(ClassMetadataInfo $metadata) { $lines = array(); $lines[] = '/**'; $lines[] = ' * ' . $this->getClassName($metadata); if ($this->generateAnnotations) { $lines[] = ' *'; $methods = array( 'generateTableAnnotation', 'generateInheritanceAnnotation', 'generateDiscriminatorColumnAnnotation', 'generateDiscriminatorMapAnnotation' ); foreach ($methods as $method) { if ($code = $this->$method($metadata)) { $lines[] = ' * ' . $code; } } if ($metadata->isMappedSuperclass) { $lines[] = ' * @' . $this->annotationsPrefix . 'MappedSuperClass'; } else { $lines[] = ' * @' . $this->annotationsPrefix . 'Entity'; } if ($metadata->customRepositoryClassName) { $lines[count($lines) - 1] .= '(repositoryClass="' . $metadata->customRepositoryClassName . '")'; } if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks'; } } $lines[] = ' */'; return implode("\n", $lines); } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateTableAnnotation($metadata) { $table = array(); if (isset($metadata->table['schema'])) { $table[] = 'schema="' . $metadata->table['schema'] . '"'; } if (isset($metadata->table['name'])) { $table[] = 'name="' . $metadata->table['name'] . '"'; } if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) { $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']); $table[] = 'uniqueConstraints={' . $constraints . '}'; } if (isset($metadata->table['indexes']) && $metadata->table['indexes']) { $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']); $table[] = 'indexes={' . $constraints . '}'; } return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')'; } /** * @param string $constraintName * @param array $constraints * * @return string */ protected function generateTableConstraints($constraintName, $constraints) { $annotations = array(); foreach ($constraints as $name => $constraint) { $columns = array(); foreach ($constraint['columns'] as $column) { $columns[] = '"' . $column . '"'; } $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})'; } return implode(', ', $annotations); } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateInheritanceAnnotation($metadata) { if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")'; } } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateDiscriminatorColumnAnnotation($metadata) { if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { $discrColumn = $metadata->discriminatorColumn; $columnDefinition = 'name="' . $discrColumn['name'] . '", type="' . $discrColumn['type'] . '", length=' . $discrColumn['length']; return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')'; } } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateDiscriminatorMapAnnotation($metadata) { if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { $inheritanceClassMap = array(); foreach ($metadata->discriminatorMap as $type => $class) { $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"'; } return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})'; } } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateEntityStubMethods(ClassMetadataInfo $metadata) { $methods = array(); foreach ($metadata->fieldMappings as $fieldMapping) { if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE) { if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) { $methods[] = $code; } } if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) { $methods[] = $code; } } foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null; if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) { $methods[] = $code; } if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { $methods[] = $code; } } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { $methods[] = $code; } if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { $methods[] = $code; } if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) { $methods[] = $code; } } } return implode("\n\n", $methods); } /** * @param array $associationMapping * * @return bool */ protected function isAssociationIsNullable($associationMapping) { if (isset($associationMapping['id']) && $associationMapping['id']) { return false; } if (isset($associationMapping['joinColumns'])) { $joinColumns = $associationMapping['joinColumns']; } else { //@todo there is no way to retrieve targetEntity metadata $joinColumns = array(); } foreach ($joinColumns as $joinColumn) { if(isset($joinColumn['nullable']) && !$joinColumn['nullable']) { return false; } } return true; } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata) { if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { $methods = array(); foreach ($metadata->lifecycleCallbacks as $name => $callbacks) { foreach ($callbacks as $callback) { if ($code = $this->generateLifecycleCallbackMethod($name, $callback, $metadata)) { $methods[] = $code; } } } return implode("\n\n", $methods); } return ""; } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata) { $lines = array(); foreach ($metadata->associationMappings as $associationMapping) { if ($this->hasProperty($associationMapping['fieldName'], $metadata)) { continue; } $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata); $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName'] . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n"; } return implode("\n", $lines); } /** * @param ClassMetadataInfo $metadata * * @return string */ protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata) { $lines = array(); foreach ($metadata->fieldMappings as $fieldMapping) { if ($this->hasProperty($fieldMapping['fieldName'], $metadata) || $metadata->isInheritedField($fieldMapping['fieldName'])) { continue; } $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata); $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName'] . (isset($fieldMapping['options']['default']) ? ' = ' . var_export($fieldMapping['options']['default'], true) : null) . ";\n"; } return implode("\n", $lines); } /** * @param ClassMetadataInfo $metadata * @param string $type * @param string $fieldName * @param string|null $typeHint * @param string|null $defaultValue * * @return string */ protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null) { $methodName = $type . Inflector::classify($fieldName); if (in_array($type, array("add", "remove"))) { $methodName = Inflector::singularize($methodName); } if ($this->hasMethod($methodName, $metadata)) { return ''; } $this->staticReflection[$metadata->name]['methods'][] = $methodName; $var = sprintf('%sMethodTemplate', $type); $template = self::$$var; $methodTypeHint = null; $types = Type::getTypesMap(); $variableType = $typeHint ? $this->getType($typeHint) . ' ' : null; if ($typeHint && ! isset($types[$typeHint])) { $variableType = '\\' . ltrim($variableType, '\\'); $methodTypeHint = '\\' . $typeHint . ' '; } $replacements = array( '' => ucfirst($type) . ' ' . $fieldName, '' => $methodTypeHint, '' => $variableType, '' => Inflector::camelize($fieldName), '' => $methodName, '' => $fieldName, '' => ($defaultValue !== null ) ? (' = '.$defaultValue) : '', '' => $this->getClassName($metadata) ); $method = str_replace( array_keys($replacements), array_values($replacements), $template ); return $this->prefixCodeWithSpaces($method); } /** * @param string $name * @param string $methodName * @param ClassMetadataInfo $metadata * * @return string */ protected function generateLifecycleCallbackMethod($name, $methodName, $metadata) { if ($this->hasMethod($methodName, $metadata)) { return ''; } $this->staticReflection[$metadata->name]['methods'][] = $methodName; $replacements = array( '' => $this->annotationsPrefix . ucfirst($name), '' => $methodName, ); $method = str_replace( array_keys($replacements), array_values($replacements), self::$lifecycleCallbackMethodTemplate ); return $this->prefixCodeWithSpaces($method); } /** * @param array $joinColumn * * @return string */ protected function generateJoinColumnAnnotation(array $joinColumn) { $joinColumnAnnot = array(); if (isset($joinColumn['name'])) { $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"'; } if (isset($joinColumn['referencedColumnName'])) { $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"'; } if (isset($joinColumn['unique']) && $joinColumn['unique']) { $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false'); } if (isset($joinColumn['nullable'])) { $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false'); } if (isset($joinColumn['onDelete'])) { $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"'); } if (isset($joinColumn['columnDefinition'])) { $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"'; } return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')'; } /** * @param array $associationMapping * @param ClassMetadataInfo $metadata * * @return string */ protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata) { $lines = array(); $lines[] = $this->spaces . '/**'; if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection'; } else { $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\'); } if ($this->generateAnnotations) { $lines[] = $this->spaces . ' *'; if (isset($associationMapping['id']) && $associationMapping['id']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id'; if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")'; } } $type = null; switch ($associationMapping['type']) { case ClassMetadataInfo::ONE_TO_ONE: $type = 'OneToOne'; break; case ClassMetadataInfo::MANY_TO_ONE: $type = 'ManyToOne'; break; case ClassMetadataInfo::ONE_TO_MANY: $type = 'OneToMany'; break; case ClassMetadataInfo::MANY_TO_MANY: $type = 'ManyToMany'; break; } $typeOptions = array(); if (isset($associationMapping['targetEntity'])) { $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"'; } if (isset($associationMapping['inversedBy'])) { $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"'; } if (isset($associationMapping['mappedBy'])) { $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"'; } if ($associationMapping['cascade']) { $cascades = array(); if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"'; if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"'; if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"'; if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"'; if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"'; if (count($cascades) === 5) { $cascades = array('"all"'); } $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}'; } if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) { $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false'); } if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) { $fetchMap = array( ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY', ClassMetadataInfo::FETCH_EAGER => 'EAGER', ); $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"'; } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')'; if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({'; $joinColumnsLines = array(); foreach ($associationMapping['joinColumns'] as $joinColumn) { if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) { $joinColumnsLines[] = $this->spaces . ' * ' . $joinColumnAnnot; } } $lines[] = implode(",\n", $joinColumnsLines); $lines[] = $this->spaces . ' * })'; } if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) { $joinTable = array(); $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"'; if (isset($associationMapping['joinTable']['schema'])) { $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"'; } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ','; $lines[] = $this->spaces . ' * joinColumns={'; $joinColumnsLines = array(); foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) { $joinColumnsLines[] = $this->spaces . ' * ' . $this->generateJoinColumnAnnotation($joinColumn); } $lines[] = implode(",". PHP_EOL, $joinColumnsLines); $lines[] = $this->spaces . ' * },'; $lines[] = $this->spaces . ' * inverseJoinColumns={'; $inverseJoinColumnsLines = array(); foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $inverseJoinColumnsLines[] = $this->spaces . ' * ' . $this->generateJoinColumnAnnotation($joinColumn); } $lines[] = implode(",". PHP_EOL, $inverseJoinColumnsLines); $lines[] = $this->spaces . ' * }'; $lines[] = $this->spaces . ' * )'; } if (isset($associationMapping['orderBy'])) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({'; foreach ($associationMapping['orderBy'] as $name => $direction) { $lines[] = $this->spaces . ' * "' . $name . '"="' . $direction . '",'; } $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1); $lines[] = $this->spaces . ' * })'; } } $lines[] = $this->spaces . ' */'; return implode("\n", $lines); } /** * @param array $fieldMapping * @param ClassMetadataInfo $metadata * * @return string */ protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata) { $lines = array(); $lines[] = $this->spaces . '/**'; $lines[] = $this->spaces . ' * @var ' . $this->getType($fieldMapping['type']); if ($this->generateAnnotations) { $lines[] = $this->spaces . ' *'; $column = array(); if (isset($fieldMapping['columnName'])) { $column[] = 'name="' . $fieldMapping['columnName'] . '"'; } if (isset($fieldMapping['type'])) { $column[] = 'type="' . $fieldMapping['type'] . '"'; } if (isset($fieldMapping['length'])) { $column[] = 'length=' . $fieldMapping['length']; } if (isset($fieldMapping['precision'])) { $column[] = 'precision=' . $fieldMapping['precision']; } if (isset($fieldMapping['scale'])) { $column[] = 'scale=' . $fieldMapping['scale']; } if (isset($fieldMapping['nullable'])) { $column[] = 'nullable=' . var_export($fieldMapping['nullable'], true); } if (isset($fieldMapping['columnDefinition'])) { $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"'; } if (isset($fieldMapping['unique'])) { $column[] = 'unique=' . var_export($fieldMapping['unique'], true); } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')'; if (isset($fieldMapping['id']) && $fieldMapping['id']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id'; if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) { $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")'; } if ($metadata->sequenceGeneratorDefinition) { $sequenceGenerator = array(); if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) { $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"'; } if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) { $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize']; } if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) { $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue']; } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')'; } } if (isset($fieldMapping['version']) && $fieldMapping['version']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version'; } } $lines[] = $this->spaces . ' */'; return implode("\n", $lines); } /** * @param string $code * @param int $num * * @return string */ protected function prefixCodeWithSpaces($code, $num = 1) { $lines = explode("\n", $code); foreach ($lines as $key => $value) { if ( ! empty($value)) { $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key]; } } return implode("\n", $lines); } /** * @param integer $type The inheritance type used by the class and its subclasses. * * @return string The literal string for the inheritance type. * * @throws \InvalidArgumentException When the inheritance type does not exists. */ protected function getInheritanceTypeString($type) { if ( ! isset(self::$inheritanceTypeMap[$type])) { throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type)); } return self::$inheritanceTypeMap[$type]; } /** * @param integer $type The policy used for change-tracking for the mapped class. * * @return string The literal string for the change-tracking type. * * @throws \InvalidArgumentException When the change-tracking type does not exists. */ protected function getChangeTrackingPolicyString($type) { if ( ! isset(self::$changeTrackingPolicyMap[$type])) { throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type)); } return self::$changeTrackingPolicyMap[$type]; } /** * @param integer $type The generator to use for the mapped class. * * @return string The literal string for the generator type. * * @throws \InvalidArgumentException When the generator type does not exists. */ protected function getIdGeneratorTypeString($type) { if ( ! isset(self::$generatorStrategyMap[$type])) { throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type)); } return self::$generatorStrategyMap[$type]; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php000066400000000000000000000063421257105210500254270ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools; /** * Class to generate entity repository classes * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EntityRepositoryGenerator { protected static $_template = ' use Doctrine\ORM\EntityRepository; /** * * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. */ class extends EntityRepository { } '; /** * @param string $fullClassName * * @return string */ public function generateEntityRepositoryClass($fullClassName) { $className = substr($fullClassName, strrpos($fullClassName, '\\') + 1, strlen($fullClassName)); $variables = array( '' => $this->generateEntityRepositoryNamespace($fullClassName), '' => $className ); return str_replace(array_keys($variables), array_values($variables), self::$_template); } /** * Generates the namespace statement, if class do not have namespace, return empty string instead. * * @param string $fullClassName The full repository class name. * * @return string $namespace */ private function generateEntityRepositoryNamespace($fullClassName) { $namespace = substr($fullClassName, 0, strrpos($fullClassName, '\\')); return $namespace ? 'namespace ' . $namespace . ';' : ''; } /** * @param string $fullClassName * @param string $outputDirectory * * @return void */ public function writeEntityRepositoryClass($fullClassName, $outputDirectory) { $code = $this->generateEntityRepositoryClass($fullClassName); $path = $outputDirectory . DIRECTORY_SEPARATOR . str_replace('\\', \DIRECTORY_SEPARATOR, $fullClassName) . '.php'; $dir = dirname($path); if ( ! is_dir($dir)) { mkdir($dir, 0775, true); } if ( ! file_exists($path)) { file_put_contents($path, $code); chmod($path, 0664); } } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Event/000077500000000000000000000000001257105210500205475ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Event/GenerateSchemaEventArgs.php000066400000000000000000000037761257105210500257670ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Event; use Doctrine\Common\EventArgs; use Doctrine\DBAL\Schema\Schema; use Doctrine\ORM\EntityManager; /** * Event Args used for the Events::postGenerateSchema event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei */ class GenerateSchemaEventArgs extends EventArgs { /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * @var \Doctrine\DBAL\Schema\Schema */ private $schema; /** * @param EntityManager $em * @param Schema $schema */ public function __construct(EntityManager $em, Schema $schema) { $this->em = $em; $this->schema = $schema; } /** * @return EntityManager */ public function getEntityManager() { return $this->em; } /** * @return Schema */ public function getSchema() { return $this->schema; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Event/GenerateSchemaTableEventArgs.php000066400000000000000000000046471257105210500267350ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Event; use Doctrine\Common\EventArgs; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; /** * Event Args used for the Events::postGenerateSchemaTable event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei */ class GenerateSchemaTableEventArgs extends EventArgs { /** * @var \Doctrine\ORM\Mapping\ClassMetadata */ private $classMetadata; /** * @var \Doctrine\DBAL\Schema\Schema */ private $schema; /** * @var \Doctrine\DBAL\Schema\Table */ private $classTable; /** * @param ClassMetadata $classMetadata * @param Schema $schema * @param Table $classTable */ public function __construct(ClassMetadata $classMetadata, Schema $schema, Table $classTable) { $this->classMetadata = $classMetadata; $this->schema = $schema; $this->classTable = $classTable; } /** * @return ClassMetadata */ public function getClassMetadata() { return $this->classMetadata; } /** * @return Schema */ public function getSchema() { return $this->schema; } /** * @return Table */ public function getClassTable() { return $this->classTable; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Export/000077500000000000000000000000001257105210500207475ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Export/ClassMetadataExporter.php000066400000000000000000000051241257105210500257210ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Export; use Doctrine\ORM\Tools\Export\ExportException; /** * Class used for converting your mapping information between the * supported formats: yaml, xml, and php/annotation. * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ class ClassMetadataExporter { /** * @var array */ private static $_exporterDrivers = array( 'xml' => 'Doctrine\ORM\Tools\Export\Driver\XmlExporter', 'yaml' => 'Doctrine\ORM\Tools\Export\Driver\YamlExporter', 'yml' => 'Doctrine\ORM\Tools\Export\Driver\YamlExporter', 'php' => 'Doctrine\ORM\Tools\Export\Driver\PhpExporter', 'annotation' => 'Doctrine\ORM\Tools\Export\Driver\AnnotationExporter' ); /** * Registers a new exporter driver class under a specified name. * * @param string $name * @param string $class * * @return void */ public static function registerExportDriver($name, $class) { self::$_exporterDrivers[$name] = $class; } /** * Gets an exporter driver instance. * * @param string $type The type to get (yml, xml, etc.). * @param string|null $dest The directory where the exporter will export to. * * @return Driver\AbstractExporter * * @throws ExportException */ public function getExporter($type, $dest = null) { if ( ! isset(self::$_exporterDrivers[$type])) { throw ExportException::invalidExporterDriverType($type); } $class = self::$_exporterDrivers[$type]; return new $class($dest); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Export/Driver/000077500000000000000000000000001257105210500222025ustar00rootroot00000000000000doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Export/Driver/AbstractExporter.php000066400000000000000000000151301257105210500262070ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Tools\Export\ExportException; /** * Abstract base class which is to be used for the Exporter drivers * which can be found in \Doctrine\ORM\Tools\Export\Driver. * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ abstract class AbstractExporter { /** * @var array */ protected $_metadata = array(); /** * @var string|null */ protected $_outputDir; /** * @var string|null */ protected $_extension; /** * @var bool */ protected $_overwriteExistingFiles = false; /** * @param string|null $dir */ public function __construct($dir = null) { $this->_outputDir = $dir; } /** * @param bool $overwrite * * @return void */ public function setOverwriteExistingFiles($overwrite) { $this->_overwriteExistingFiles = $overwrite; } /** * Converts a single ClassMetadata instance to the exported format * and returns it. * * @param ClassMetadataInfo $metadata * * @return string */ abstract public function exportClassMetadata(ClassMetadataInfo $metadata); /** * Sets the array of ClassMetadataInfo instances to export. * * @param array $metadata * * @return void */ public function setMetadata(array $metadata) { $this->_metadata = $metadata; } /** * Gets the extension used to generated the path to a class. * * @return string|null */ public function getExtension() { return $this->_extension; } /** * Sets the directory to output the mapping files to. * * [php] * $exporter = new YamlExporter($metadata); * $exporter->setOutputDir(__DIR__ . '/yaml'); * $exporter->export(); * * @param string $dir * * @return void */ public function setOutputDir($dir) { $this->_outputDir = $dir; } /** * Exports each ClassMetadata instance to a single Doctrine Mapping file * named after the entity. * * @return void * * @throws \Doctrine\ORM\Tools\Export\ExportException */ public function export() { if ( ! is_dir($this->_outputDir)) { mkdir($this->_outputDir, 0775, true); } foreach ($this->_metadata as $metadata) { //In case output is returned, write it to a file, skip otherwise if($output = $this->exportClassMetadata($metadata)){ $path = $this->_generateOutputPath($metadata); $dir = dirname($path); if ( ! is_dir($dir)) { mkdir($dir, 0775, true); } if (file_exists($path) && !$this->_overwriteExistingFiles) { throw ExportException::attemptOverwriteExistingFile($path); } file_put_contents($path, $output); chmod($path, 0664); } } } /** * Generates the path to write the class for the given ClassMetadataInfo instance. * * @param ClassMetadataInfo $metadata * * @return string */ protected function _generateOutputPath(ClassMetadataInfo $metadata) { return $this->_outputDir . '/' . str_replace('\\', '.', $metadata->name) . $this->_extension; } /** * Sets the directory to output the mapping files to. * * [php] * $exporter = new YamlExporter($metadata, __DIR__ . '/yaml'); * $exporter->setExtension('.yml'); * $exporter->export(); * * @param string $extension * * @return void */ public function setExtension($extension) { $this->_extension = $extension; } /** * @param int $type * * @return string */ protected function _getInheritanceTypeString($type) { switch ($type) { case ClassMetadataInfo::INHERITANCE_TYPE_NONE: return 'NONE'; case ClassMetadataInfo::INHERITANCE_TYPE_JOINED: return 'JOINED'; case ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE: return 'SINGLE_TABLE'; case ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS: return 'PER_CLASS'; } } /** * @param int $policy * * @return string */ protected function _getChangeTrackingPolicyString($policy) { switch ($policy) { case ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT: return 'DEFERRED_IMPLICIT'; case ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT: return 'DEFERRED_EXPLICIT'; case ClassMetadataInfo::CHANGETRACKING_NOTIFY: return 'NOTIFY'; } } /** * @param int $type * * @return string */ protected function _getIdGeneratorTypeString($type) { switch ($type) { case ClassMetadataInfo::GENERATOR_TYPE_AUTO: return 'AUTO'; case ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE: return 'SEQUENCE'; case ClassMetadataInfo::GENERATOR_TYPE_TABLE: return 'TABLE'; case ClassMetadataInfo::GENERATOR_TYPE_IDENTITY: return 'IDENTITY'; case ClassMetadataInfo::GENERATOR_TYPE_UUID: return 'UUID'; case ClassMetadataInfo::GENERATOR_TYPE_CUSTOM: return 'CUSTOM'; } } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php000066400000000000000000000052071257105210500265620ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Tools\EntityGenerator; /** * ClassMetadata exporter for PHP classes with annotations. * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ class AnnotationExporter extends AbstractExporter { /** * @var string */ protected $_extension = '.php'; /** * @var EntityGenerator|null */ private $_entityGenerator; /** * {@inheritdoc} */ public function exportClassMetadata(ClassMetadataInfo $metadata) { if ( ! $this->_entityGenerator) { throw new \RuntimeException('For the AnnotationExporter you must set an EntityGenerator instance with the setEntityGenerator() method.'); } $this->_entityGenerator->setGenerateAnnotations(true); $this->_entityGenerator->setGenerateStubMethods(false); $this->_entityGenerator->setRegenerateEntityIfExists(false); $this->_entityGenerator->setUpdateEntityIfExists(false); return $this->_entityGenerator->generateEntityClass($metadata); } /** * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata * * @return string */ protected function _generateOutputPath(ClassMetadataInfo $metadata) { return $this->_outputDir . '/' . str_replace('\\', '/', $metadata->name) . $this->_extension; } /** * @param \Doctrine\ORM\Tools\EntityGenerator $entityGenerator * * @return void */ public function setEntityGenerator(EntityGenerator $entityGenerator) { $this->_entityGenerator = $entityGenerator; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php000066400000000000000000000155301257105210500251770ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * ClassMetadata exporter for PHP code. * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ class PhpExporter extends AbstractExporter { /** * @var string */ protected $_extension = '.php'; /** * {@inheritdoc} */ public function exportClassMetadata(ClassMetadataInfo $metadata) { $lines = array(); $lines[] = 'isMappedSuperclass) { $lines[] = '$metadata->isMappedSuperclass = true;'; } if ($metadata->inheritanceType) { $lines[] = '$metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_' . $this->_getInheritanceTypeString($metadata->inheritanceType) . ');'; } if ($metadata->customRepositoryClassName) { $lines[] = "\$metadata->customRepositoryClassName = '" . $metadata->customRepositoryClassName . "';"; } if ($metadata->table) { $lines[] = '$metadata->setPrimaryTable(' . $this->_varExport($metadata->table) . ');'; } if ($metadata->discriminatorColumn) { $lines[] = '$metadata->setDiscriminatorColumn(' . $this->_varExport($metadata->discriminatorColumn) . ');'; } if ($metadata->discriminatorMap) { $lines[] = '$metadata->setDiscriminatorMap(' . $this->_varExport($metadata->discriminatorMap) . ');'; } if ($metadata->changeTrackingPolicy) { $lines[] = '$metadata->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_' . $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy) . ');'; } if ($metadata->lifecycleCallbacks) { foreach ($metadata->lifecycleCallbacks as $event => $callbacks) { foreach ($callbacks as $callback) { $lines[] = "\$metadata->addLifecycleCallback('$callback', '$event');"; } } } foreach ($metadata->fieldMappings as $fieldMapping) { $lines[] = '$metadata->mapField(' . $this->_varExport($fieldMapping) . ');'; } if ( ! $metadata->isIdentifierComposite && $generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) { $lines[] = '$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_' . $generatorType . ');'; } foreach ($metadata->associationMappings as $associationMapping) { $cascade = array('remove', 'persist', 'refresh', 'merge', 'detach'); foreach ($cascade as $key => $value) { if ( ! $associationMapping['isCascade'.ucfirst($value)]) { unset($cascade[$key]); } } if (count($cascade) === 5) { $cascade = array('all'); } $associationMappingArray = array( 'fieldName' => $associationMapping['fieldName'], 'targetEntity' => $associationMapping['targetEntity'], 'cascade' => $cascade, ); if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $method = 'mapOneToOne'; $oneToOneMappingArray = array( 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'joinColumns' => $associationMapping['joinColumns'], 'orphanRemoval' => $associationMapping['orphanRemoval'], ); $associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray); } elseif ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) { $method = 'mapOneToMany'; $potentialAssociationMappingIndexes = array( 'mappedBy', 'orphanRemoval', 'orderBy', ); foreach ($potentialAssociationMappingIndexes as $index) { if (isset($associationMapping[$index])) { $oneToManyMappingArray[$index] = $associationMapping[$index]; } } $associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray); } elseif ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) { $method = 'mapManyToMany'; $potentialAssociationMappingIndexes = array( 'mappedBy', 'joinTable', 'orderBy', ); foreach ($potentialAssociationMappingIndexes as $index) { if (isset($associationMapping[$index])) { $manyToManyMappingArray[$index] = $associationMapping[$index]; } } $associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray); } $lines[] = '$metadata->' . $method . '(' . $this->_varExport($associationMappingArray) . ');'; } return implode("\n", $lines); } /** * @param mixed $var * * @return string */ protected function _varExport($var) { $export = var_export($var, true); $export = str_replace("\n", PHP_EOL . str_repeat(' ', 8), $export); $export = str_replace(' ', ' ', $export); $export = str_replace('array (', 'array(', $export); $export = str_replace('array( ', 'array(', $export); $export = str_replace(',)', ')', $export); $export = str_replace(', )', ')', $export); $export = str_replace(' ', ' ', $export); return $export; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php000066400000000000000000000404071257105210500252110ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * ClassMetadata exporter for Doctrine XML mapping files. * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ class XmlExporter extends AbstractExporter { /** * @var string */ protected $_extension = '.dcm.xml'; /** * {@inheritdoc} */ public function exportClassMetadata(ClassMetadataInfo $metadata) { $xml = new \SimpleXmlElement(""); /*$xml->addAttribute('xmlns', 'http://doctrine-project.org/schemas/orm/doctrine-mapping'); $xml->addAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); $xml->addAttribute('xsi:schemaLocation', 'http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd');*/ if ($metadata->isMappedSuperclass) { $root = $xml->addChild('mapped-superclass'); } else { $root = $xml->addChild('entity'); } if ($metadata->customRepositoryClassName) { $root->addAttribute('repository-class', $metadata->customRepositoryClassName); } $root->addAttribute('name', $metadata->name); if (isset($metadata->table['name'])) { $root->addAttribute('table', $metadata->table['name']); } if (isset($metadata->table['schema'])) { $root->addAttribute('schema', $metadata->table['schema']); } if (isset($metadata->table['inheritance-type'])) { $root->addAttribute('inheritance-type', $metadata->table['inheritance-type']); } if ($metadata->discriminatorColumn) { $discriminatorColumnXml = $root->addChild('discriminator-column'); $discriminatorColumnXml->addAttribute('name', $metadata->discriminatorColumn['name']); $discriminatorColumnXml->addAttribute('type', $metadata->discriminatorColumn['type']); if (isset($metadata->discriminatorColumn['length'])) { $discriminatorColumnXml->addAttribute('length', $metadata->discriminatorColumn['length']); } } if ($metadata->discriminatorMap) { $discriminatorMapXml = $root->addChild('discriminator-map'); foreach ($metadata->discriminatorMap as $value => $className) { $discriminatorMappingXml = $discriminatorMapXml->addChild('discriminator-mapping'); $discriminatorMappingXml->addAttribute('value', $value); $discriminatorMappingXml->addAttribute('class', $className); } } $trackingPolicy = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy); if ( $trackingPolicy != 'DEFERRED_IMPLICIT') { $root->addChild('change-tracking-policy', $trackingPolicy); } if (isset($metadata->table['indexes'])) { $indexesXml = $root->addChild('indexes'); foreach ($metadata->table['indexes'] as $name => $index) { $indexXml = $indexesXml->addChild('index'); $indexXml->addAttribute('name', $name); $indexXml->addAttribute('columns', implode(',', $index['columns'])); } } if (isset($metadata->table['uniqueConstraints'])) { $uniqueConstraintsXml = $root->addChild('unique-constraints'); foreach ($metadata->table['uniqueConstraints'] as $name => $unique) { $uniqueConstraintXml = $uniqueConstraintsXml->addChild('unique-constraint'); $uniqueConstraintXml->addAttribute('name', $name); $uniqueConstraintXml->addAttribute('columns', implode(',', $unique['columns'])); } } $fields = $metadata->fieldMappings; $id = array(); foreach ($fields as $name => $field) { if (isset($field['id']) && $field['id']) { $id[$name] = $field; unset($fields[$name]); } } foreach ($metadata->associationMappings as $name => $assoc) { if (isset($assoc['id']) && $assoc['id']) { $id[$name] = array( 'fieldName' => $name, 'associationKey' => true ); } } if ( ! $metadata->isIdentifierComposite && $idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) { $id[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $idGeneratorType; } if ($id) { foreach ($id as $field) { $idXml = $root->addChild('id'); $idXml->addAttribute('name', $field['fieldName']); if (isset($field['type'])) { $idXml->addAttribute('type', $field['type']); } if (isset($field['columnName'])) { $idXml->addAttribute('column', $field['columnName']); } if (isset($field['length'])) { $idXml->addAttribute('length', $field['length']); } if (isset($field['associationKey']) && $field['associationKey']) { $idXml->addAttribute('association-key', 'true'); } if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) { $generatorXml = $idXml->addChild('generator'); $generatorXml->addAttribute('strategy', $idGeneratorType); } } } if ($fields) { foreach ($fields as $field) { $fieldXml = $root->addChild('field'); $fieldXml->addAttribute('name', $field['fieldName']); $fieldXml->addAttribute('type', $field['type']); if (isset($field['columnName'])) { $fieldXml->addAttribute('column', $field['columnName']); } if (isset($field['length'])) { $fieldXml->addAttribute('length', $field['length']); } if (isset($field['precision'])) { $fieldXml->addAttribute('precision', $field['precision']); } if (isset($field['scale'])) { $fieldXml->addAttribute('scale', $field['scale']); } if (isset($field['unique']) && $field['unique']) { $fieldXml->addAttribute('unique', $field['unique'] ? 'true' : 'false'); } if (isset($field['options'])) { $optionsXml = $fieldXml->addChild('options'); foreach ($field['options'] as $key => $value) { $optionsXml->addAttribute($key, $value); } } if (isset($field['version'])) { $fieldXml->addAttribute('version', $field['version']); } if (isset($field['columnDefinition'])) { $fieldXml->addAttribute('column-definition', $field['columnDefinition']); } if (isset($field['nullable'])) { $fieldXml->addAttribute('nullable', $field['nullable'] ? 'true' : 'false'); } } } $orderMap = array( ClassMetadataInfo::ONE_TO_ONE, ClassMetadataInfo::ONE_TO_MANY, ClassMetadataInfo::MANY_TO_ONE, ClassMetadataInfo::MANY_TO_MANY, ); uasort($metadata->associationMappings, function($m1, $m2) use (&$orderMap){ $a1 = array_search($m1['type'], $orderMap); $a2 = array_search($m2['type'], $orderMap); return strcmp($a1, $a2); }); foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_ONE) { $associationMappingXml = $root->addChild('one-to-one'); } elseif ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_ONE) { $associationMappingXml = $root->addChild('many-to-one'); } elseif ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) { $associationMappingXml = $root->addChild('one-to-many'); } elseif ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) { $associationMappingXml = $root->addChild('many-to-many'); } $associationMappingXml->addAttribute('field', $associationMapping['fieldName']); $associationMappingXml->addAttribute('target-entity', $associationMapping['targetEntity']); if (isset($associationMapping['mappedBy'])) { $associationMappingXml->addAttribute('mapped-by', $associationMapping['mappedBy']); } if (isset($associationMapping['inversedBy'])) { $associationMappingXml->addAttribute('inversed-by', $associationMapping['inversedBy']); } if (isset($associationMapping['indexBy'])) { $associationMappingXml->addAttribute('index-by', $associationMapping['indexBy']); } if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval'] !== false) { $associationMappingXml->addAttribute('orphan-removal', 'true'); } if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) { $joinTableXml = $associationMappingXml->addChild('join-table'); $joinTableXml->addAttribute('name', $associationMapping['joinTable']['name']); $joinColumnsXml = $joinTableXml->addChild('join-columns'); foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) { $joinColumnXml = $joinColumnsXml->addChild('join-column'); $joinColumnXml->addAttribute('name', $joinColumn['name']); $joinColumnXml->addAttribute('referenced-column-name', $joinColumn['referencedColumnName']); if (isset($joinColumn['onDelete'])) { $joinColumnXml->addAttribute('on-delete', $joinColumn['onDelete']); } } $inverseJoinColumnsXml = $joinTableXml->addChild('inverse-join-columns'); foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $inverseJoinColumn) { $inverseJoinColumnXml = $inverseJoinColumnsXml->addChild('join-column'); $inverseJoinColumnXml->addAttribute('name', $inverseJoinColumn['name']); $inverseJoinColumnXml->addAttribute('referenced-column-name', $inverseJoinColumn['referencedColumnName']); if (isset($inverseJoinColumn['onDelete'])) { $inverseJoinColumnXml->addAttribute('on-delete', $inverseJoinColumn['onDelete']); } if (isset($inverseJoinColumn['columnDefinition'])) { $inverseJoinColumnXml->addAttribute('column-definition', $inverseJoinColumn['columnDefinition']); } if (isset($inverseJoinColumn['nullable'])) { $inverseJoinColumnXml->addAttribute('nullable', $inverseJoinColumn['nullable']); } if (isset($inverseJoinColumn['orderBy'])) { $inverseJoinColumnXml->addAttribute('order-by', $inverseJoinColumn['orderBy']); } } } if (isset($associationMapping['joinColumns'])) { $joinColumnsXml = $associationMappingXml->addChild('join-columns'); foreach ($associationMapping['joinColumns'] as $joinColumn) { $joinColumnXml = $joinColumnsXml->addChild('join-column'); $joinColumnXml->addAttribute('name', $joinColumn['name']); $joinColumnXml->addAttribute('referenced-column-name', $joinColumn['referencedColumnName']); if (isset($joinColumn['onDelete'])) { $joinColumnXml->addAttribute('on-delete', $joinColumn['onDelete']); } if (isset($joinColumn['columnDefinition'])) { $joinColumnXml->addAttribute('column-definition', $joinColumn['columnDefinition']); } if (isset($joinColumn['nullable'])) { $joinColumnXml->addAttribute('nullable', $joinColumn['nullable']); } } } if (isset($associationMapping['orderBy'])) { $orderByXml = $associationMappingXml->addChild('order-by'); foreach ($associationMapping['orderBy'] as $name => $direction) { $orderByFieldXml = $orderByXml->addChild('order-by-field'); $orderByFieldXml->addAttribute('name', $name); $orderByFieldXml->addAttribute('direction', $direction); } } $cascade = array(); if ($associationMapping['isCascadeRemove']) { $cascade[] = 'cascade-remove'; } if ($associationMapping['isCascadePersist']) { $cascade[] = 'cascade-persist'; } if ($associationMapping['isCascadeRefresh']) { $cascade[] = 'cascade-refresh'; } if ($associationMapping['isCascadeMerge']) { $cascade[] = 'cascade-merge'; } if ($associationMapping['isCascadeDetach']) { $cascade[] = 'cascade-detach'; } if (count($cascade) === 5) { $cascade = array('cascade-all'); } if ($cascade) { $cascadeXml = $associationMappingXml->addChild('cascade'); foreach ($cascade as $type) { $cascadeXml->addChild($type); } } } if (isset($metadata->lifecycleCallbacks) && count($metadata->lifecycleCallbacks)>0) { $lifecycleCallbacksXml = $root->addChild('lifecycle-callbacks'); foreach ($metadata->lifecycleCallbacks as $name => $methods) { foreach ($methods as $method) { $lifecycleCallbackXml = $lifecycleCallbacksXml->addChild('lifecycle-callback'); $lifecycleCallbackXml->addAttribute('type', $name); $lifecycleCallbackXml->addAttribute('method', $method); } } } return $this->_asXml($xml); } /** * @param \SimpleXMLElement $simpleXml * * @return string $xml */ private function _asXml($simpleXml) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->loadXML($simpleXml->asXML()); $dom->formatOutput = true; return $dom->saveXML(); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php000066400000000000000000000177211257105210500253560ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Export\Driver; use Symfony\Component\Yaml\Yaml; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * ClassMetadata exporter for Doctrine YAML mapping files. * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ class YamlExporter extends AbstractExporter { /** * @var string */ protected $_extension = '.dcm.yml'; /** * {@inheritdoc} */ 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'])) { $array['schema'] = $metadata->table['schema']; } $inheritanceType = $metadata->inheritanceType; if ($inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) { $array['inheritanceType'] = $this->_getInheritanceTypeString($inheritanceType); } if ($column = $metadata->discriminatorColumn) { $array['discriminatorColumn'] = $column; } if ($map = $metadata->discriminatorMap) { $array['discriminatorMap'] = $map; } if ($metadata->changeTrackingPolicy !== ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT) { $array['changeTrackingPolicy'] = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy); } if (isset($metadata->table['indexes'])) { $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']; unset($fieldMapping['columnName'], $fieldMapping['fieldName']); if ($fieldMapping['column'] == $name) { unset($fieldMapping['column']); } if (isset($fieldMapping['id']) && $fieldMapping['id']) { $ids[$name] = $fieldMapping; unset($fieldMappings[$name]); continue; } $fieldMappings[$name] = $fieldMapping; } if ( ! $metadata->isIdentifierComposite && $idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) { $ids[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $idGeneratorType; } $array['id'] = $ids; if ($fieldMappings) { if ( ! isset($array['fields'])) { $array['fields'] = array(); } $array['fields'] = array_merge($array['fields'], $fieldMappings); } foreach ($metadata->associationMappings as $name => $associationMapping) { $cascade = array(); if ($associationMapping['isCascadeRemove']) { $cascade[] = 'remove'; } if ($associationMapping['isCascadePersist']) { $cascade[] = 'persist'; } if ($associationMapping['isCascadeRefresh']) { $cascade[] = 'refresh'; } if ($associationMapping['isCascadeMerge']) { $cascade[] = 'merge'; } if ($associationMapping['isCascadeDetach']) { $cascade[] = 'detach'; } if (count($cascade) === 5) { $cascade = array('all'); } $associationMappingArray = array( 'targetEntity' => $associationMapping['targetEntity'], 'cascade' => $cascade, ); if (isset($mapping['id']) && $mapping['id'] === true) { $array['id'][$name]['associationKey'] = true; } if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $joinColumns = $associationMapping['joinColumns']; $newJoinColumns = array(); foreach ($joinColumns as $joinColumn) { $newJoinColumns[$joinColumn['name']]['referencedColumnName'] = $joinColumn['referencedColumnName']; if (isset($joinColumn['onDelete'])) { $newJoinColumns[$joinColumn['name']]['onDelete'] = $joinColumn['onDelete']; } } $oneToOneMappingArray = array( 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'joinColumns' => $newJoinColumns, 'orphanRemoval' => $associationMapping['orphanRemoval'], ); $associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray); if ($associationMapping['type'] & ClassMetadataInfo::ONE_TO_ONE) { $array['oneToOne'][$name] = $associationMappingArray; } else { $array['manyToOne'][$name] = $associationMappingArray; } } elseif ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) { $oneToManyMappingArray = array( 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'orphanRemoval' => $associationMapping['orphanRemoval'], 'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null ); $associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray); $array['oneToMany'][$name] = $associationMappingArray; } elseif ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) { $manyToManyMappingArray = array( 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'joinTable' => isset($associationMapping['joinTable']) ? $associationMapping['joinTable'] : null, 'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null ); $associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray); $array['manyToMany'][$name] = $associationMappingArray; } } if (isset($metadata->lifecycleCallbacks)) { $array['lifecycleCallbacks'] = $metadata->lifecycleCallbacks; } return Yaml::dump(array($metadata->name => $array), 10); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Export/ExportException.php000066400000000000000000000014611257105210500246220ustar00rootroot00000000000000 FROM ()) * * Works with composite keys but cannot deal with queries that have multiple * root entities (e.g. `SELECT f, b from Foo, Bar`) * * @author Sander Marechal */ class CountOutputWalker extends SqlWalker { /** * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ private $platform; /** * @var \Doctrine\ORM\Query\ResultSetMapping */ private $rsm; /** * @var array */ private $queryComponents; /** * Constructor. * * Stores various parameters that are otherwise unavailable * because Doctrine\ORM\Query\SqlWalker keeps everything private without * accessors. * * @param \Doctrine\ORM\Query $query * @param \Doctrine\ORM\Query\ParserResult $parserResult * @param array $queryComponents */ public function __construct($query, $parserResult, array $queryComponents) { $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); $this->rsm = $parserResult->getResultSetMapping(); $this->queryComponents = $queryComponents; parent::__construct($query, $parserResult, $queryComponents); } /** * Walks down a SelectStatement AST node, wrapping it in a COUNT (SELECT DISTINCT). * * Note that the ORDER BY clause is not removed. Many SQL implementations (e.g. MySQL) * are able to cache subqueries. By keeping the ORDER BY clause intact, the limitSubQuery * that will most likely be executed next can be read from the native SQL cache. * * @param SelectStatement $AST * * @return string * * @throws \RuntimeException */ public function walkSelectStatement(SelectStatement $AST) { if ($this->platform->getName() === "mssql") { $AST->orderByClause = null; } $sql = parent::walkSelectStatement($AST); // Find out the SQL alias of the identifier column of the root entity // It may be possible to make this work with multiple root entities but that // would probably require issuing multiple queries or doing a UNION SELECT // so for now, It's not supported. // Get the root entity and alias from the AST fromClause $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) > 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $rootAlias = $from[0]->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->queryComponents[$rootAlias]['metadata']; $rootIdentifier = $rootClass->identifier; // For every identifier, find out the SQL alias by combing through the ResultSetMapping $sqlIdentifier = array(); foreach ($rootIdentifier as $property) { if (isset($rootClass->fieldMappings[$property])) { foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } if (isset($rootClass->associationMappings[$property])) { $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name']; foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } } if (count($rootIdentifier) != count($sqlIdentifier)) { throw new \RuntimeException(sprintf( 'Not all identifier properties can be found in the ResultSetMapping: %s', implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))) )); } // Build the counter query return sprintf('SELECT %s AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table', $this->platform->getCountExpression('*'), implode(', ', $sqlIdentifier), $sql ); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Pagination/CountWalker.php000066400000000000000000000062401257105210500245300ustar00rootroot00000000000000 * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License */ class CountWalker extends TreeWalkerAdapter { /** * Distinct mode hint name. */ const HINT_DISTINCT = 'doctrine_paginator.distinct'; /** * Walks down a SelectStatement AST node, modifying it to retrieve a COUNT. * * @param SelectStatement $AST * * @return void * * @throws \RuntimeException */ public function walkSelectStatement(SelectStatement $AST) { if ($AST->havingClause) { throw new \RuntimeException('Cannot count query that uses a HAVING clause. Use the output walkers for pagination'); } $rootComponents = array(); foreach ($this->_getQueryComponents() as $dqlAlias => $qComp) { $isParent = array_key_exists('parent', $qComp) && $qComp['parent'] === null && $qComp['nestingLevel'] == 0 ; if ($isParent) { $rootComponents[] = array($dqlAlias => $qComp); } } if (count($rootComponents) > 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $root = reset($rootComponents); $parentName = key($root); $parent = current($root); $identifierFieldName = $parent['metadata']->getSingleIdentifierFieldName(); $pathType = PathExpression::TYPE_STATE_FIELD; if (isset($parent['metadata']->associationMappings[$identifierFieldName])) { $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; } $pathExpression = new PathExpression( PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, $identifierFieldName ); $pathExpression->type = $pathType; $distinct = $this->_getQuery()->getHint(self::HINT_DISTINCT); $AST->selectClause->selectExpressions = array( new SelectExpression( new AggregateExpression('count', $pathExpression, $distinct), null ) ); // ORDER BY is not needed, only increases query execution through unnecessary sorting. $AST->orderByClause = null; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php000066400000000000000000000177171257105210500275120ustar00rootroot00000000000000 FROM () LIMIT x OFFSET y * * Works with composite keys but cannot deal with queries that have multiple * root entities (e.g. `SELECT f, b from Foo, Bar`) * * @author Sander Marechal */ class LimitSubqueryOutputWalker extends SqlWalker { /** * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ private $platform; /** * @var \Doctrine\ORM\Query\ResultSetMapping */ private $rsm; /** * @var array */ private $queryComponents; /** * @var int */ private $firstResult; /** * @var int */ private $maxResults; /** * Constructor. * * Stores various parameters that are otherwise unavailable * because Doctrine\ORM\Query\SqlWalker keeps everything private without * accessors. * * @param \Doctrine\ORM\Query $query * @param \Doctrine\ORM\Query\ParserResult $parserResult * @param array $queryComponents */ public function __construct($query, $parserResult, array $queryComponents) { $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); $this->rsm = $parserResult->getResultSetMapping(); $this->queryComponents = $queryComponents; // Reset limit and offset $this->firstResult = $query->getFirstResult(); $this->maxResults = $query->getMaxResults(); $query->setFirstResult(null)->setMaxResults(null); parent::__construct($query, $parserResult, $queryComponents); } /** * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT. * * @param SelectStatement $AST * * @return string * * @throws \RuntimeException */ public function walkSelectStatement(SelectStatement $AST) { // Set every select expression as visible(hidden = false) to // make $AST have scalar mappings properly - this is relevant for referencing selected // fields from outside the subquery, for example in the ORDER BY segment $hiddens = array(); foreach ($AST->selectClause->selectExpressions as $idx => $expr) { $hiddens[$idx] = $expr->hiddenAliasResultVariable; $expr->hiddenAliasResultVariable = false; } $innerSql = parent::walkSelectStatement($AST); // Restore hiddens foreach ($AST->selectClause->selectExpressions as $idx => $expr) { $expr->hiddenAliasResultVariable = $hiddens[$idx]; } // Find out the SQL alias of the identifier column of the root entity. // It may be possible to make this work with multiple root entities but that // would probably require issuing multiple queries or doing a UNION SELECT. // So for now, it's not supported. // Get the root entity and alias from the AST fromClause. $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) !== 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $rootAlias = $from[0]->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->queryComponents[$rootAlias]['metadata']; $rootIdentifier = $rootClass->identifier; // For every identifier, find out the SQL alias by combing through the ResultSetMapping $sqlIdentifier = array(); foreach ($rootIdentifier as $property) { if (isset($rootClass->fieldMappings[$property])) { foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } if (isset($rootClass->associationMappings[$property])) { $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name']; foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } } if (count($rootIdentifier) != count($sqlIdentifier)) { throw new \RuntimeException(sprintf( 'Not all identifier properties can be found in the ResultSetMapping: %s', implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))) )); } // Build the counter query $sql = sprintf('SELECT DISTINCT %s FROM (%s) dctrn_result', implode(', ', $sqlIdentifier), $innerSql); // http://www.doctrine-project.org/jira/browse/DDC-1958 $sql = $this->preserveSqlOrdering($AST, $sqlIdentifier, $innerSql, $sql); // Apply the limit and offset. $sql = $this->platform->modifyLimitQuery( $sql, $this->maxResults, $this->firstResult ); // Add the columns to the ResultSetMapping. It's not really nice but // it works. Preferably I'd clear the RSM or simply create a new one // but that is not possible from inside the output walker, so we dirty // up the one we have. foreach ($sqlIdentifier as $property => $alias) { $this->rsm->addScalarResult($alias, $property); } return $sql; } /** * Generates new SQL for Postgresql or Oracle if necessary. * * @param SelectStatement $AST * @param array $sqlIdentifier * @param string $innerSql * @param string $sql * * @return void */ public function preserveSqlOrdering(SelectStatement $AST, array $sqlIdentifier, $innerSql, $sql) { // For every order by, find out the SQL alias by inspecting the ResultSetMapping. $sqlOrderColumns = array(); $orderBy = array(); if (isset($AST->orderByClause)) { foreach ($AST->orderByClause->orderByItems as $item) { $expression = $item->expression; $possibleAliases = $expression instanceof PathExpression ? array_keys($this->rsm->fieldMappings, $expression->field) : array_keys($this->rsm->scalarMappings, $expression); foreach ($possibleAliases as $alias) { if (!is_object($expression) || $this->rsm->columnOwnerMap[$alias] == $expression->identificationVariable) { $sqlOrderColumns[] = $alias; $orderBy[] = $alias . ' ' . $item->type; break; } } } //remove identifier aliases $sqlOrderColumns = array_diff($sqlOrderColumns, $sqlIdentifier); } if (count($orderBy)) { $sql = sprintf( 'SELECT DISTINCT %s FROM (%s) dctrn_result ORDER BY %s', implode(', ', array_merge($sqlIdentifier, $sqlOrderColumns)), $innerSql, implode(', ', $orderBy) ); } return $sql; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php000066400000000000000000000100661257105210500262570ustar00rootroot00000000000000 * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License */ namespace Doctrine\ORM\Tools\Pagination; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Query\TreeWalkerAdapter; use Doctrine\ORM\Query\AST\SelectStatement; use Doctrine\ORM\Query\AST\SelectExpression; use Doctrine\ORM\Query\AST\PathExpression; /** * Replaces the selectClause of the AST with a SELECT DISTINCT root.id equivalent. * * @category DoctrineExtensions * @package DoctrineExtensions\Paginate * @author David Abdemoulaie * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License */ class LimitSubqueryWalker extends TreeWalkerAdapter { /** * ID type hint. */ const IDENTIFIER_TYPE = 'doctrine_paginator.id.type'; /** * Counter for generating unique order column aliases. * * @var int */ private $_aliasCounter = 0; /** * Walks down a SelectStatement AST node, modifying it to retrieve DISTINCT ids * of the root Entity. * * @param SelectStatement $AST * * @return void * * @throws \RuntimeException */ public function walkSelectStatement(SelectStatement $AST) { $parent = null; $parentName = null; $selectExpressions = array(); foreach ($this->_getQueryComponents() as $dqlAlias => $qComp) { // Preserve mixed data in query for ordering. if (isset($qComp['resultVariable'])) { $selectExpressions[] = new SelectExpression($qComp['resultVariable'], $dqlAlias); continue; } if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { $parent = $qComp; $parentName = $dqlAlias; continue; } } $identifier = $parent['metadata']->getSingleIdentifierFieldName(); if (isset($parent['metadata']->associationMappings[$identifier])) { throw new \RuntimeException("Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator."); } $this->_getQuery()->setHint( self::IDENTIFIER_TYPE, Type::getType($parent['metadata']->getTypeOfField($identifier)) ); $pathExpression = new PathExpression( PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, $identifier ); $pathExpression->type = PathExpression::TYPE_STATE_FIELD; array_unshift($selectExpressions, new SelectExpression($pathExpression, '_dctrn_id')); $AST->selectClause->selectExpressions = $selectExpressions; if (isset($AST->orderByClause)) { foreach ($AST->orderByClause->orderByItems as $item) { if ($item->expression instanceof PathExpression) { $pathExpression = new PathExpression( PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $item->expression->identificationVariable, $item->expression->field ); $pathExpression->type = PathExpression::TYPE_STATE_FIELD; $AST->selectClause->selectExpressions[] = new SelectExpression( $pathExpression, '_dctrn_ord' . $this->_aliasCounter++ ); } } } $AST->selectClause->isDistinct = true; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Pagination/Paginator.php000077500000000000000000000200061257105210500242150ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools\Pagination; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Query; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\NoResultException; /** * The paginator can handle various complex scenarios with DQL. * * @author Pablo Díez * @author Benjamin Eberlei * @license New BSD */ class Paginator implements \Countable, \IteratorAggregate { /** * @var Query */ private $query; /** * @var bool */ private $fetchJoinCollection; /** * @var bool|null */ private $useOutputWalkers; /** * @var int */ private $count; /** * Constructor. * * @param Query|QueryBuilder $query A Doctrine ORM query or query builder. * @param boolean $fetchJoinCollection Whether the query joins a collection (true by default). */ public function __construct($query, $fetchJoinCollection = true) { if ($query instanceof QueryBuilder) { $query = $query->getQuery(); } $this->query = $query; $this->fetchJoinCollection = (Boolean) $fetchJoinCollection; } /** * Returns the query. * * @return Query */ public function getQuery() { return $this->query; } /** * Returns whether the query joins a collection. * * @return boolean Whether the query joins a collection. */ public function getFetchJoinCollection() { return $this->fetchJoinCollection; } /** * Returns whether the paginator will use an output walker. * * @return bool|null */ public function getUseOutputWalkers() { return $this->useOutputWalkers; } /** * Sets whether the paginator will use an output walker. * * @param bool|null $useOutputWalkers * * @return $this */ public function setUseOutputWalkers($useOutputWalkers) { $this->useOutputWalkers = $useOutputWalkers; return $this; } /** * {@inheritdoc} */ public function count() { if ($this->count === null) { try { $this->count = array_sum(array_map('current', $this->getCountQuery()->getScalarResult())); } catch(NoResultException $e) { $this->count = 0; } } return $this->count; } /** * {@inheritdoc} */ public function getIterator() { $offset = $this->query->getFirstResult(); $length = $this->query->getMaxResults(); if ($this->fetchJoinCollection) { $subQuery = $this->cloneQuery($this->query); if ($this->useOutputWalker($subQuery)) { $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryOutputWalker'); } else { $this->appendTreeWalker($subQuery, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker'); } $subQuery->setFirstResult($offset)->setMaxResults($length); $ids = array_map('current', $subQuery->getScalarResult()); $whereInQuery = $this->cloneQuery($this->query); // don't do this for an empty id array if (count($ids) == 0) { return new \ArrayIterator(array()); } $this->appendTreeWalker($whereInQuery, 'Doctrine\ORM\Tools\Pagination\WhereInWalker'); $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids)); $whereInQuery->setFirstResult(null)->setMaxResults(null); $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids); $result = $whereInQuery->getResult($this->query->getHydrationMode()); } else { $result = $this->cloneQuery($this->query) ->setMaxResults($length) ->setFirstResult($offset) ->getResult($this->query->getHydrationMode()) ; } return new \ArrayIterator($result); } /** * Clones a query. * * @param Query $query The query. * * @return Query The cloned query. */ private function cloneQuery(Query $query) { /* @var $cloneQuery Query */ $cloneQuery = clone $query; $cloneQuery->setParameters(clone $query->getParameters()); foreach ($query->getHints() as $name => $value) { $cloneQuery->setHint($name, $value); } return $cloneQuery; } /** * Determines whether to use an output walker for the query. * * @param Query $query The query. * * @return bool */ private function useOutputWalker(Query $query) { if ($this->useOutputWalkers === null) { return (Boolean) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) == false; } return $this->useOutputWalkers; } /** * Appends a custom tree walker to the tree walkers hint. * * @param Query $query * @param string $walkerClass */ private function appendTreeWalker(Query $query, $walkerClass) { $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS); if ($hints === false) { $hints = array(); } $hints[] = $walkerClass; $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints); } /** * Returns Query prepared to count. * * @return Query */ private function getCountQuery() { /* @var $countQuery Query */ $countQuery = $this->cloneQuery($this->query); if ( ! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) { $countQuery->setHint(CountWalker::HINT_DISTINCT, true); } if ($this->useOutputWalker($countQuery)) { $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win $rsm = new ResultSetMapping(); $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count'); $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\CountOutputWalker'); $countQuery->setResultSetMapping($rsm); } else { $this->appendTreeWalker($countQuery, 'Doctrine\ORM\Tools\Pagination\CountWalker'); } $countQuery->setFirstResult(null)->setMaxResults(null); $parser = new Parser($countQuery); $parameterMappings = $parser->parse()->getParameterMappings(); /* @var $parameters \Doctrine\Common\Collections\Collection|\Doctrine\ORM\Query\Parameter[] */ $parameters = $countQuery->getParameters(); foreach ($parameters as $key => $parameter) { $parameterName = $parameter->getName(); if( ! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) { unset($parameters[$key]); } } $countQuery->setParameters($parameters); return $countQuery; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php000066400000000000000000000130561257105210500250040ustar00rootroot00000000000000 * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License */ namespace Doctrine\ORM\Tools\Pagination; use Doctrine\ORM\Query\AST\ArithmeticExpression; use Doctrine\ORM\Query\AST\SimpleArithmeticExpression; use Doctrine\ORM\Query\TreeWalkerAdapter; use Doctrine\ORM\Query\AST\SelectStatement; use Doctrine\ORM\Query\AST\PathExpression; use Doctrine\ORM\Query\AST\InExpression; use Doctrine\ORM\Query\AST\NullComparisonExpression; use Doctrine\ORM\Query\AST\InputParameter; use Doctrine\ORM\Query\AST\ConditionalPrimary; use Doctrine\ORM\Query\AST\ConditionalTerm; use Doctrine\ORM\Query\AST\ConditionalExpression; use Doctrine\ORM\Query\AST\ConditionalFactor; use Doctrine\ORM\Query\AST\WhereClause; /** * Replaces the whereClause of the AST with a WHERE id IN (:foo_1, :foo_2) equivalent. * * @category DoctrineExtensions * @package DoctrineExtensions\Paginate * @author David Abdemoulaie * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License */ class WhereInWalker extends TreeWalkerAdapter { /** * ID Count hint name. */ const HINT_PAGINATOR_ID_COUNT = 'doctrine.id.count'; /** * Primary key alias for query. */ const PAGINATOR_ID_ALIAS = 'dpid'; /** * Replaces the whereClause in the AST. * * Generates a clause equivalent to WHERE IN (:dpid_1, :dpid_2, ...) * * The parameter namespace (dpid) is defined by * the PAGINATOR_ID_ALIAS * * The total number of parameters is retrieved from * the HINT_PAGINATOR_ID_COUNT query hint. * * @param SelectStatement $AST * * @return void * * @throws \RuntimeException */ public function walkSelectStatement(SelectStatement $AST) { $rootComponents = array(); foreach ($this->_getQueryComponents() as $dqlAlias => $qComp) { $isParent = array_key_exists('parent', $qComp) && $qComp['parent'] === null && $qComp['nestingLevel'] == 0 ; if ($isParent) { $rootComponents[] = array($dqlAlias => $qComp); } } if (count($rootComponents) > 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $root = reset($rootComponents); $parentName = key($root); $parent = current($root); $identifierFieldName = $parent['metadata']->getSingleIdentifierFieldName(); $pathType = PathExpression::TYPE_STATE_FIELD; if (isset($parent['metadata']->associationMappings[$identifierFieldName])) { $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; } $pathExpression = new PathExpression(PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, $identifierFieldName); $pathExpression->type = $pathType; $count = $this->_getQuery()->getHint(self::HINT_PAGINATOR_ID_COUNT); if ($count > 0) { $arithmeticExpression = new ArithmeticExpression(); $arithmeticExpression->simpleArithmeticExpression = new SimpleArithmeticExpression( array($pathExpression) ); $expression = new InExpression($arithmeticExpression); $expression->literals[] = new InputParameter(":" . self::PAGINATOR_ID_ALIAS); } else { $expression = new NullComparisonExpression($pathExpression); $expression->not = false; } $conditionalPrimary = new ConditionalPrimary; $conditionalPrimary->simpleConditionalExpression = $expression; if ($AST->whereClause) { if ($AST->whereClause->conditionalExpression instanceof ConditionalTerm) { $AST->whereClause->conditionalExpression->conditionalFactors[] = $conditionalPrimary; } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalPrimary) { $AST->whereClause->conditionalExpression = new ConditionalExpression(array( new ConditionalTerm(array( $AST->whereClause->conditionalExpression, $conditionalPrimary )) )); } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalExpression || $AST->whereClause->conditionalExpression instanceof ConditionalFactor ) { $tmpPrimary = new ConditionalPrimary; $tmpPrimary->conditionalExpression = $AST->whereClause->conditionalExpression; $AST->whereClause->conditionalExpression = new ConditionalTerm(array( $tmpPrimary, $conditionalPrimary )); } } else { $AST->whereClause = new WhereClause( new ConditionalExpression(array( new ConditionalTerm(array( $conditionalPrimary )) )) ); } } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/ResolveTargetEntityListener.php000066400000000000000000000066241257105210500257000ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; use Doctrine\ORM\Mapping\ClassMetadata; /** * ResolveTargetEntityListener * * Mechanism to overwrite interfaces or classes specified as association * targets. * * @author Benjamin Eberlei * @since 2.2 */ class ResolveTargetEntityListener { /** * @var array */ private $resolveTargetEntities = array(); /** * Adds a target-entity class name to resolve to a new class name. * * @param string $originalEntity * @param string $newEntity * @param array $mapping * * @return void */ public function addResolveTargetEntity($originalEntity, $newEntity, array $mapping) { $mapping['targetEntity'] = ltrim($newEntity, "\\"); $this->resolveTargetEntities[ltrim($originalEntity, "\\")] = $mapping; } /** * Processes event and resolves new target entity names. * * @param LoadClassMetadataEventArgs $args * * @return void */ public function loadClassMetadata(LoadClassMetadataEventArgs $args) { $cm = $args->getClassMetadata(); foreach ($cm->associationMappings as $mapping) { if (isset($this->resolveTargetEntities[$mapping['targetEntity']])) { $this->remapAssociation($cm, $mapping); } } } /** * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata * @param array $mapping * * @return void */ private function remapAssociation($classMetadata, $mapping) { $newMapping = $this->resolveTargetEntities[$mapping['targetEntity']]; $newMapping = array_replace_recursive($mapping, $newMapping); $newMapping['fieldName'] = $mapping['fieldName']; unset($classMetadata->associationMappings[$mapping['fieldName']]); switch ($mapping['type']) { case ClassMetadata::MANY_TO_MANY: $classMetadata->mapManyToMany($newMapping); break; case ClassMetadata::MANY_TO_ONE: $classMetadata->mapManyToOne($newMapping); break; case ClassMetadata::ONE_TO_MANY: $classMetadata->mapOneToMany($newMapping); break; case ClassMetadata::ONE_TO_ONE: $classMetadata->mapOneToOne($newMapping); break; } } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/SchemaTool.php000066400000000000000000001003421257105210500222350ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\ORMException; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Schema\Comparator; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector; use Doctrine\DBAL\Schema\Visitor\RemoveNamespacedAssets; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Internal\CommitOrderCalculator; use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; /** * The SchemaTool is a tool to create/drop/update database schemas based on * ClassMetadata class descriptors. * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei * @author Stefano Rodriguez */ class SchemaTool { /** * @var \Doctrine\ORM\EntityManagerInterface */ private $em; /** * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ private $platform; /** * The quote strategy. * * @var \Doctrine\ORM\Mapping\QuoteStrategy */ private $quoteStrategy; /** * Initializes a new SchemaTool instance that uses the connection of the * provided EntityManager. * * @param \Doctrine\ORM\EntityManagerInterface $em */ public function __construct(EntityManagerInterface $em) { $this->em = $em; $this->platform = $em->getConnection()->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); } /** * Creates the database schema for the given array of ClassMetadata instances. * * @param array $classes * * @return void * * @throws ToolsException */ public function createSchema(array $classes) { $createSchemaSql = $this->getCreateSchemaSql($classes); $conn = $this->em->getConnection(); foreach ($createSchemaSql as $sql) { try { $conn->executeQuery($sql); } catch (\Exception $e) { throw ToolsException::schemaToolFailure($sql, $e); } } } /** * Gets the list of DDL statements that are required to create the database schema for * the given list of ClassMetadata instances. * * @param array $classes * * @return array The SQL statements needed to create the schema for the classes. */ public function getCreateSchemaSql(array $classes) { $schema = $this->getSchemaFromMetadata($classes); return $schema->toSql($this->platform); } /** * Detects instances of ClassMetadata that don't need to be processed in the SchemaTool context. * * @param ClassMetadata $class * @param array $processedClasses * * @return bool */ private function processingNotRequired($class, array $processedClasses) { return ( isset($processedClasses[$class->name]) || $class->isMappedSuperclass || ($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName) ); } /** * Creates a Schema instance from a given set of metadata classes. * * @param array $classes * * @return Schema * * @throws \Doctrine\ORM\ORMException */ public function getSchemaFromMetadata(array $classes) { // Reminder for processed classes, used for hierarchies $processedClasses = array(); $eventManager = $this->em->getEventManager(); $schemaManager = $this->em->getConnection()->getSchemaManager(); $metadataSchemaConfig = $schemaManager->createSchemaConfig(); $metadataSchemaConfig->setExplicitForeignKeyIndexes(false); $schema = new Schema(array(), array(), $metadataSchemaConfig); $addedFks = array(); $blacklistedFks = array(); foreach ($classes as $class) { /** @var \Doctrine\ORM\Mapping\ClassMetadata $class */ if ($this->processingNotRequired($class, $processedClasses)) { continue; } $table = $schema->createTable($this->quoteStrategy->getTableName($class, $this->platform)); if ($class->isInheritanceTypeSingleTable()) { $this->gatherColumns($class, $table); $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); // Add the discriminator column $this->addDiscriminatorColumnDefinition($class, $table); // Aggregate all the information from all classes in the hierarchy foreach ($class->parentClasses as $parentClassName) { // Parent class information is already contained in this class $processedClasses[$parentClassName] = true; } foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $this->gatherColumns($subClass, $table); $this->gatherRelationsSql($subClass, $table, $schema, $addedFks, $blacklistedFks); $processedClasses[$subClassName] = true; } } elseif ($class->isInheritanceTypeJoined()) { // Add all non-inherited fields as columns $pkColumns = array(); foreach ($class->fieldMappings as $fieldName => $mapping) { if ( ! isset($mapping['inherited'])) { $columnName = $this->quoteStrategy->getColumnName( $mapping['fieldName'], $class, $this->platform ); $this->gatherColumn($class, $mapping, $table); if ($class->isIdentifier($fieldName)) { $pkColumns[] = $columnName; } } } $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); // Add the discriminator column only to the root table if ($class->name == $class->rootEntityName) { $this->addDiscriminatorColumnDefinition($class, $table); } else { // Add an ID FK column to child tables $inheritedKeyColumns = array(); foreach ($class->identifier as $identifierField) { $idMapping = $class->fieldMappings[$identifierField]; if (isset($idMapping['inherited'])) { $this->gatherColumn($class, $idMapping, $table); $columnName = $this->quoteStrategy->getColumnName( $identifierField, $class, $this->platform ); // TODO: This seems rather hackish, can we optimize it? $table->getColumn($columnName)->setAutoincrement(false); $pkColumns[] = $columnName; $inheritedKeyColumns[] = $columnName; } } if (!empty($inheritedKeyColumns)) { // Add a FK constraint on the ID column $table->addForeignKeyConstraint( $this->quoteStrategy->getTableName( $this->em->getClassMetadata($class->rootEntityName), $this->platform ), $inheritedKeyColumns, $inheritedKeyColumns, array('onDelete' => 'CASCADE') ); } } $table->setPrimaryKey($pkColumns); } elseif ($class->isInheritanceTypeTablePerClass()) { throw ORMException::notSupported(); } else { $this->gatherColumns($class, $table); $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); } $pkColumns = array(); foreach ($class->identifier as $identifierField) { if (isset($class->fieldMappings[$identifierField])) { $pkColumns[] = $this->quoteStrategy->getColumnName($identifierField, $class, $this->platform); } elseif (isset($class->associationMappings[$identifierField])) { /* @var $assoc \Doctrine\ORM\Mapping\OneToOne */ $assoc = $class->associationMappings[$identifierField]; foreach ($assoc['joinColumns'] as $joinColumn) { $pkColumns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } } } if ( ! $table->hasIndex('primary')) { $table->setPrimaryKey($pkColumns); } if (isset($class->table['indexes'])) { foreach ($class->table['indexes'] as $indexName => $indexData) { $table->addIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName); } } if (isset($class->table['uniqueConstraints'])) { foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) { $table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName); } } if (isset($class->table['options'])) { foreach ($class->table['options'] as $key => $val) { $table->addOption($key, $val); } } $processedClasses[$class->name] = true; if ($class->isIdGeneratorSequence() && $class->name == $class->rootEntityName) { $seqDef = $class->sequenceGeneratorDefinition; $quotedName = $this->quoteStrategy->getSequenceName($seqDef, $class, $this->platform); if ( ! $schema->hasSequence($quotedName)) { $schema->createSequence( $quotedName, $seqDef['allocationSize'], $seqDef['initialValue'] ); } } if ($eventManager->hasListeners(ToolEvents::postGenerateSchemaTable)) { $eventManager->dispatchEvent( ToolEvents::postGenerateSchemaTable, new GenerateSchemaTableEventArgs($class, $schema, $table) ); } } if ( ! $this->platform->supportsSchemas() && ! $this->platform->canEmulateSchemas() ) { $schema->visit(new RemoveNamespacedAssets()); } if ($eventManager->hasListeners(ToolEvents::postGenerateSchema)) { $eventManager->dispatchEvent( ToolEvents::postGenerateSchema, new GenerateSchemaEventArgs($this->em, $schema) ); } return $schema; } /** * Gets a portable column definition as required by the DBAL for the discriminator * column of a class. * * @param ClassMetadata $class * @param Table $table * * @return array The portable column definition of the discriminator column as required by * the DBAL. */ private function addDiscriminatorColumnDefinition($class, Table $table) { $discrColumn = $class->discriminatorColumn; if ( ! isset($discrColumn['type']) || (strtolower($discrColumn['type']) == 'string' && $discrColumn['length'] === null) ) { $discrColumn['type'] = 'string'; $discrColumn['length'] = 255; } $options = array( 'length' => isset($discrColumn['length']) ? $discrColumn['length'] : null, 'notnull' => true ); if (isset($discrColumn['columnDefinition'])) { $options['columnDefinition'] = $discrColumn['columnDefinition']; } $table->addColumn($discrColumn['name'], $discrColumn['type'], $options); } /** * Gathers the column definitions as required by the DBAL of all field mappings * found in the given class. * * @param ClassMetadata $class * @param Table $table * * @return array The list of portable column definitions as required by the DBAL. */ private function gatherColumns($class, Table $table) { $pkColumns = array(); foreach ($class->fieldMappings as $mapping) { if ($class->isInheritanceTypeSingleTable() && isset($mapping['inherited'])) { continue; } $this->gatherColumn($class, $mapping, $table); if ($class->isIdentifier($mapping['fieldName'])) { $pkColumns[] = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform); } } // For now, this is a hack required for single table inheritence, since this method is called // twice by single table inheritence relations if (!$table->hasIndex('primary')) { //$table->setPrimaryKey($pkColumns); } } /** * Creates a column definition as required by the DBAL from an ORM field mapping definition. * * @param ClassMetadata $class The class that owns the field mapping. * @param array $mapping The field mapping. * @param Table $table * * @return array The portable column definition as required by the DBAL. */ private function gatherColumn($class, array $mapping, Table $table) { $columnName = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform); $columnType = $mapping['type']; $options = array(); $options['length'] = isset($mapping['length']) ? $mapping['length'] : null; $options['notnull'] = isset($mapping['nullable']) ? ! $mapping['nullable'] : true; if ($class->isInheritanceTypeSingleTable() && count($class->parentClasses) > 0) { $options['notnull'] = false; } $options['platformOptions'] = array(); $options['platformOptions']['version'] = $class->isVersioned && $class->versionField == $mapping['fieldName'] ? true : false; if (strtolower($columnType) == 'string' && $options['length'] === null) { $options['length'] = 255; } if (isset($mapping['precision'])) { $options['precision'] = $mapping['precision']; } if (isset($mapping['scale'])) { $options['scale'] = $mapping['scale']; } if (isset($mapping['default'])) { $options['default'] = $mapping['default']; } if (isset($mapping['columnDefinition'])) { $options['columnDefinition'] = $mapping['columnDefinition']; } if (isset($mapping['options'])) { $knownOptions = array('comment', 'unsigned', 'fixed', 'default'); foreach ($knownOptions as $knownOption) { if (array_key_exists($knownOption, $mapping['options'])) { $options[$knownOption] = $mapping['options'][$knownOption]; unset($mapping['options'][$knownOption]); } } $options['customSchemaOptions'] = $mapping['options']; } if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() == array($mapping['fieldName'])) { $options['autoincrement'] = true; } if ($class->isInheritanceTypeJoined() && $class->name != $class->rootEntityName) { $options['autoincrement'] = false; } if ($table->hasColumn($columnName)) { // required in some inheritance scenarios $table->changeColumn($columnName, $options); } else { $table->addColumn($columnName, $columnType, $options); } $isUnique = isset($mapping['unique']) ? $mapping['unique'] : false; if ($isUnique) { $table->addUniqueIndex(array($columnName)); } } /** * Gathers the SQL for properly setting up the relations of the given class. * This includes the SQL for foreign key constraints and join tables. * * @param ClassMetadata $class * @param Table $table * @param Schema $schema * @param array $addedFks * @param array $blacklistedFks * * @return void * * @throws \Doctrine\ORM\ORMException */ private function gatherRelationsSql($class, $table, $schema, &$addedFks, &$blacklistedFks) { foreach ($class->associationMappings as $mapping) { if (isset($mapping['inherited'])) { continue; } $foreignClass = $this->em->getClassMetadata($mapping['targetEntity']); if ($mapping['type'] & ClassMetadata::TO_ONE && $mapping['isOwningSide']) { $primaryKeyColumns = $uniqueConstraints = array(); // PK is unnecessary for this relation-type $this->gatherRelationJoinColumns( $mapping['joinColumns'], $table, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints, $addedFks, $blacklistedFks ); foreach ($uniqueConstraints as $indexName => $unique) { $table->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName); } } elseif ($mapping['type'] == ClassMetadata::ONE_TO_MANY && $mapping['isOwningSide']) { //... create join table, one-many through join table supported later throw ORMException::notSupported(); } elseif ($mapping['type'] == ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) { // create join table $joinTable = $mapping['joinTable']; $theJoinTable = $schema->createTable( $this->quoteStrategy->getJoinTableName($mapping, $foreignClass, $this->platform) ); $primaryKeyColumns = $uniqueConstraints = array(); // Build first FK constraint (relation table => source table) $this->gatherRelationJoinColumns( $joinTable['joinColumns'], $theJoinTable, $class, $mapping, $primaryKeyColumns, $uniqueConstraints, $addedFks, $blacklistedFks ); // Build second FK constraint (relation table => target table) $this->gatherRelationJoinColumns( $joinTable['inverseJoinColumns'], $theJoinTable, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints, $addedFks, $blacklistedFks ); $theJoinTable->setPrimaryKey($primaryKeyColumns); foreach ($uniqueConstraints as $indexName => $unique) { $theJoinTable->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName); } } } } /** * Gets the class metadata that is responsible for the definition of the referenced column name. * * Previously this was a simple task, but with DDC-117 this problem is actually recursive. If its * not a simple field, go through all identifier field names that are associations recursively and * find that referenced column name. * * TODO: Is there any way to make this code more pleasing? * * @param ClassMetadata $class * @param string $referencedColumnName * * @return array (ClassMetadata, referencedFieldName) */ private function getDefiningClass($class, $referencedColumnName) { $referencedFieldName = $class->getFieldName($referencedColumnName); if ($class->hasField($referencedFieldName)) { return array($class, $referencedFieldName); } if (in_array($referencedColumnName, $class->getIdentifierColumnNames())) { // it seems to be an entity as foreign key foreach ($class->getIdentifierFieldNames() as $fieldName) { if ($class->hasAssociation($fieldName) && $class->getSingleAssociationJoinColumnName($fieldName) == $referencedColumnName) { return $this->getDefiningClass( $this->em->getClassMetadata($class->associationMappings[$fieldName]['targetEntity']), $class->getSingleAssociationReferencedJoinColumnName($fieldName) ); } } } return null; } /** * Gathers columns and fk constraints that are required for one part of relationship. * * @param array $joinColumns * @param Table $theJoinTable * @param ClassMetadata $class * @param array $mapping * @param array $primaryKeyColumns * @param array $uniqueConstraints * @param array $addedFks * @param array $blacklistedFks * * @return void * * @throws \Doctrine\ORM\ORMException */ private function gatherRelationJoinColumns( $joinColumns, $theJoinTable, $class, $mapping, &$primaryKeyColumns, &$uniqueConstraints, &$addedFks, &$blacklistedFks ) { $localColumns = array(); $foreignColumns = array(); $fkOptions = array(); $foreignTableName = $this->quoteStrategy->getTableName($class, $this->platform); foreach ($joinColumns as $joinColumn) { list($definingClass, $referencedFieldName) = $this->getDefiningClass( $class, $joinColumn['referencedColumnName'] ); if ( ! $definingClass) { throw new \Doctrine\ORM\ORMException( "Column name `".$joinColumn['referencedColumnName']."` referenced for relation from ". $mapping['sourceEntity'] . " towards ". $mapping['targetEntity'] . " does not exist." ); } $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); $quotedRefColumnName = $this->quoteStrategy->getReferencedJoinColumnName( $joinColumn, $class, $this->platform ); $primaryKeyColumns[] = $quotedColumnName; $localColumns[] = $quotedColumnName; $foreignColumns[] = $quotedRefColumnName; if ( ! $theJoinTable->hasColumn($quotedColumnName)) { // Only add the column to the table if it does not exist already. // It might exist already if the foreign key is mapped into a regular // property as well. $fieldMapping = $definingClass->getFieldMapping($referencedFieldName); $columnDef = null; if (isset($joinColumn['columnDefinition'])) { $columnDef = $joinColumn['columnDefinition']; } elseif (isset($fieldMapping['columnDefinition'])) { $columnDef = $fieldMapping['columnDefinition']; } $columnOptions = array('notnull' => false, 'columnDefinition' => $columnDef); if (isset($joinColumn['nullable'])) { $columnOptions['notnull'] = !$joinColumn['nullable']; } if (isset($fieldMapping['options'])) { $columnOptions['options'] = $fieldMapping['options']; } if ($fieldMapping['type'] == "string" && isset($fieldMapping['length'])) { $columnOptions['length'] = $fieldMapping['length']; } elseif ($fieldMapping['type'] == "decimal") { $columnOptions['scale'] = $fieldMapping['scale']; $columnOptions['precision'] = $fieldMapping['precision']; } $theJoinTable->addColumn($quotedColumnName, $fieldMapping['type'], $columnOptions); } if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) { $uniqueConstraints[] = array('columns' => array($quotedColumnName)); } if (isset($joinColumn['onDelete'])) { $fkOptions['onDelete'] = $joinColumn['onDelete']; } } $compositeName = $theJoinTable->getName().'.'.implode('', $localColumns); if (isset($addedFks[$compositeName]) && ($foreignTableName != $addedFks[$compositeName]['foreignTableName'] || 0 < count(array_diff($foreignColumns, $addedFks[$compositeName]['foreignColumns']))) ) { foreach ($theJoinTable->getForeignKeys() as $fkName => $key) { if (0 === count(array_diff($key->getLocalColumns(), $localColumns)) && (($key->getForeignTableName() != $foreignTableName) || 0 < count(array_diff($key->getForeignColumns(), $foreignColumns))) ) { $theJoinTable->removeForeignKey($fkName); break; } } $blacklistedFks[$compositeName] = true; } elseif (!isset($blacklistedFks[$compositeName])) { $addedFks[$compositeName] = array('foreignTableName' => $foreignTableName, 'foreignColumns' => $foreignColumns); $theJoinTable->addUnnamedForeignKeyConstraint( $foreignTableName, $localColumns, $foreignColumns, $fkOptions ); } } /** * Drops the database schema for the given classes. * * In any way when an exception is thrown it is suppressed since drop was * issued for all classes of the schema and some probably just don't exist. * * @param array $classes * * @return void */ public function dropSchema(array $classes) { $dropSchemaSql = $this->getDropSchemaSQL($classes); $conn = $this->em->getConnection(); foreach ($dropSchemaSql as $sql) { try { $conn->executeQuery($sql); } catch (\Exception $e) { } } } /** * Drops all elements in the database of the current connection. * * @return void */ public function dropDatabase() { $dropSchemaSql = $this->getDropDatabaseSQL(); $conn = $this->em->getConnection(); foreach ($dropSchemaSql as $sql) { $conn->executeQuery($sql); } } /** * Gets the SQL needed to drop the database schema for the connections database. * * @return array */ public function getDropDatabaseSQL() { $sm = $this->em->getConnection()->getSchemaManager(); $schema = $sm->createSchema(); $visitor = new DropSchemaSqlCollector($this->platform); $schema->visit($visitor); return $visitor->getQueries(); } /** * Gets SQL to drop the tables defined by the passed classes. * * @param array $classes * * @return array */ public function getDropSchemaSQL(array $classes) { $visitor = new DropSchemaSqlCollector($this->platform); $schema = $this->getSchemaFromMetadata($classes); $sm = $this->em->getConnection()->getSchemaManager(); $fullSchema = $sm->createSchema(); foreach ($fullSchema->getTables() as $table) { if ( ! $schema->hasTable($table->getName())) { foreach ($table->getForeignKeys() as $foreignKey) { /* @var $foreignKey \Doctrine\DBAL\Schema\ForeignKeyConstraint */ if ($schema->hasTable($foreignKey->getForeignTableName())) { $visitor->acceptForeignKey($table, $foreignKey); } } } else { $visitor->acceptTable($table); foreach ($table->getForeignKeys() as $foreignKey) { $visitor->acceptForeignKey($table, $foreignKey); } } } 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(); } /** * Updates the database schema of the given classes by comparing the ClassMetadata * instances to the current database schema that is inspected. If $saveMode is set * to true the command is executed in the Database, else SQL is returned. * * @param array $classes * @param boolean $saveMode * * @return void */ public function updateSchema(array $classes, $saveMode = false) { $updateSchemaSql = $this->getUpdateSchemaSql($classes, $saveMode); $conn = $this->em->getConnection(); foreach ($updateSchemaSql as $sql) { $conn->executeQuery($sql); } } /** * Gets the sequence of SQL statements that need to be performed in order * to bring the given class mappings in-synch with the relational schema. * If $saveMode is set to true the command is executed in the Database, * else SQL is returned. * * @param array $classes The classes to consider. * @param boolean $saveMode True for writing to DB, false for SQL string. * * @return array The sequence of SQL statements. */ public function getUpdateSchemaSql(array $classes, $saveMode = false) { $sm = $this->em->getConnection()->getSchemaManager(); $fromSchema = $sm->createSchema(); $toSchema = $this->getSchemaFromMetadata($classes); $comparator = new Comparator(); $schemaDiff = $comparator->compare($fromSchema, $toSchema); if ($saveMode) { return $schemaDiff->toSaveSql($this->platform); } return $schemaDiff->toSql($this->platform); } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/SchemaValidator.php000066400000000000000000000345731257105210500232610ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\DBAL\Types\Type; /** * Performs strict validation of the mapping schema * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SchemaValidator { /** * @var EntityManagerInterface */ private $em; /** * @param EntityManagerInterface $em */ public function __construct(EntityManagerInterface $em) { $this->em = $em; } /** * Checks the internal consistency of all mapping files. * * There are several checks that can't be done at runtime or are too expensive, which can be verified * with this command. For example: * * 1. Check if a relation with "mappedBy" is actually connected to that specified field. * 2. Check if "mappedBy" and "inversedBy" are consistent to each other. * 3. Check if "referencedColumnName" attributes are really pointing to primary key columns. * 4. Check if there are public properties that might cause problems with lazy loading. * * @return array */ public function validateMapping() { $errors = array(); $cmf = $this->em->getMetadataFactory(); $classes = $cmf->getAllMetadata(); foreach ($classes as $class) { if ($ce = $this->validateClass($class)) { $errors[$class->name] = $ce; } } return $errors; } /** * Validates a single class of the current. * * @param ClassMetadataInfo $class * * @return array */ public function validateClass(ClassMetadataInfo $class) { $ce = array(); $cmf = $this->em->getMetadataFactory(); foreach ($class->fieldMappings as $fieldName => $mapping) { if (!Type::hasType($mapping['type'])) { $ce[] = "The field '" . $class->name . "#" . $fieldName."' uses a non-existant type '" . $mapping['type'] . "'."; } } foreach ($class->associationMappings as $fieldName => $assoc) { if (!class_exists($assoc['targetEntity']) || $cmf->isTransient($assoc['targetEntity'])) { $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown or not an entity.'; return $ce; } if ($assoc['mappedBy'] && $assoc['inversedBy']) { $ce[] = "The association " . $class . "#" . $fieldName . " cannot be defined as both inverse and owning."; } $targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']); if (isset($assoc['id']) && $targetMetadata->containsForeignIdentifier) { $ce[] = "Cannot map association '" . $class->name. "#". $fieldName ." as identifier, because " . "the target entity '". $targetMetadata->name . "' also maps an association as identifier."; } if ($assoc['mappedBy']) { if ($targetMetadata->hasField($assoc['mappedBy'])) { $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which is not defined as association, but as field."; } if (!$targetMetadata->hasAssociation($assoc['mappedBy'])) { $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which does not exist."; } elseif ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] == null) { $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". "bi-directional relationship, but the specified mappedBy association on the target-entity ". $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". "'inversedBy=".$fieldName."' attribute."; } elseif ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] != $fieldName) { $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " are ". "inconsistent with each other."; } } if ($assoc['inversedBy']) { if ($targetMetadata->hasField($assoc['inversedBy'])) { $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which is not defined as association."; } if (!$targetMetadata->hasAssociation($assoc['inversedBy'])) { $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which does not exist."; } elseif ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] == null) { $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the owning side of a ". "bi-directional relationship, but the specified mappedBy association on the target-entity ". $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". "'inversedBy' attribute."; } elseif ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] != $fieldName) { $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " are ". "inconsistent with each other."; } // Verify inverse side/owning side match each other if (array_key_exists($assoc['inversedBy'], $targetMetadata->associationMappings)) { $targetAssoc = $targetMetadata->associationMappings[$assoc['inversedBy']]; if ($assoc['type'] == ClassMetadataInfo::ONE_TO_ONE && $targetAssoc['type'] !== ClassMetadataInfo::ONE_TO_ONE){ $ce[] = "If association " . $class->name . "#" . $fieldName . " is one-to-one, then the inversed " . "side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be one-to-one as well."; } elseif ($assoc['type'] == ClassMetadataInfo::MANY_TO_ONE && $targetAssoc['type'] !== ClassMetadataInfo::ONE_TO_MANY){ $ce[] = "If association " . $class->name . "#" . $fieldName . " is many-to-one, then the inversed " . "side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be one-to-many."; } elseif ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY && $targetAssoc['type'] !== ClassMetadataInfo::MANY_TO_MANY){ $ce[] = "If association " . $class->name . "#" . $fieldName . " is many-to-many, then the inversed " . "side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be many-to-many as well."; } } } if ($assoc['isOwningSide']) { if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY) { $identifierColumns = $class->getIdentifierColumnNames(); foreach ($assoc['joinTable']['joinColumns'] as $joinColumn) { if (!in_array($joinColumn['referencedColumnName'], $identifierColumns)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . "has to be a primary key column on the target entity class '".$class->name."'."; break; } } $identifierColumns = $targetMetadata->getIdentifierColumnNames(); foreach ($assoc['joinTable']['inverseJoinColumns'] as $inverseJoinColumn) { if (!in_array($inverseJoinColumn['referencedColumnName'], $identifierColumns)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . "has to be a primary key column on the target entity class '".$targetMetadata->name."'."; break; } } if (count($targetMetadata->getIdentifierColumnNames()) != count($assoc['joinTable']['inverseJoinColumns'])) { $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . "have to contain to ALL identifier columns of the target entity '". $targetMetadata->name . "', " . "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), array_values($assoc['relationToTargetKeyColumns']))) . "' are missing."; } if (count($class->getIdentifierColumnNames()) != count($assoc['joinTable']['joinColumns'])) { $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . "have to contain to ALL identifier columns of the source entity '". $class->name . "', " . "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), array_values($assoc['relationToSourceKeyColumns']))) . "' are missing."; } } elseif ($assoc['type'] & ClassMetadataInfo::TO_ONE) { $identifierColumns = $targetMetadata->getIdentifierColumnNames(); foreach ($assoc['joinColumns'] as $joinColumn) { if (!in_array($joinColumn['referencedColumnName'], $identifierColumns)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . "has to be a primary key column on the target entity class '".$targetMetadata->name."'."; } } if (count($identifierColumns) != count($assoc['joinColumns'])) { $ids = array(); foreach ($assoc['joinColumns'] as $joinColumn) { $ids[] = $joinColumn['name']; } $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . "have to match to ALL identifier columns of the target entity '". $class->name . "', " . "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), $ids)) . "' are missing."; } } } if (isset($assoc['orderBy']) && $assoc['orderBy'] !== null) { foreach ($assoc['orderBy'] as $orderField => $orientation) { if (!$targetMetadata->hasField($orderField)) { $ce[] = "The association " . $class->name."#".$fieldName." is ordered by a foreign field " . $orderField . " that is not a field on the target entity " . $targetMetadata->name; } } } } foreach ($class->reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $publicAttr) { if ($publicAttr->isStatic()) { continue; } if ( ! isset($class->fieldMappings[$publicAttr->getName()]) && ! isset($class->associationMappings[$publicAttr->getName()])) { continue; } $ce[] = "Field '".$publicAttr->getName()."' in class '".$class->name."' must be private ". "or protected. Public fields may break lazy-loading."; } foreach ($class->subClasses as $subClass) { if (!in_array($class->name, class_parents($subClass))) { $ce[] = "According to the discriminator map class '" . $subClass . "' has to be a child ". "of '" . $class->name . "' but these entities are not related through inheritance."; } } return $ce; } /** * Checks if the Database Schema is in sync with the current metadata state. * * @return bool */ public function schemaInSyncWithMetadata() { $schemaTool = new SchemaTool($this->em); $allMetadata = $this->em->getMetadataFactory()->getAllMetadata(); return count($schemaTool->getUpdateSchemaSql($allMetadata, true)) == 0; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/Setup.php000066400000000000000000000130141257105210500212760ustar00rootroot00000000000000. */ 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 */ class Setup { /** * Use this method to register all autoloads for a downloaded Doctrine library. * Pick the directory the library was uncompressed into. * * @param string $directory * * @return void */ public static 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(); } /** * Creates a configuration with an annotation metadata driver. * * @param array $paths * @param boolean $isDevMode * @param string $proxyDir * @param Cache $cache * @param bool $useSimpleAnnotationReader * * @return Configuration */ public static function createAnnotationMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null, $useSimpleAnnotationReader = true) { $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver($paths, $useSimpleAnnotationReader)); return $config; } /** * Creates a configuration with a xml metadata driver. * * @param array $paths * @param boolean $isDevMode * @param string $proxyDir * @param Cache $cache * * @return Configuration */ public static function createXMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null) { $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(new XmlDriver($paths)); return $config; } /** * Creates a configuration with a yaml metadata driver. * * @param array $paths * @param boolean $isDevMode * @param string $proxyDir * @param Cache $cache * * @return Configuration */ public static function createYAMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null) { $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(new YamlDriver($paths)); return $config; } /** * Creates a configuration without a metadata driver. * * @param bool $isDevMode * @param string $proxyDir * @param Cache $cache * * @return Configuration */ public static 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(); } elseif (extension_loaded('xcache')) { $cache = new \Doctrine\Common\Cache\XcacheCache(); } elseif (extension_loaded('memcache')) { $memcache = new \Memcache(); $memcache->connect('127.0.0.1'); $cache = new \Doctrine\Common\Cache\MemcacheCache(); $cache->setMemcache($memcache); } elseif (extension_loaded('redis')) { $redis = new \Redis(); $redis->connect('127.0.0.1'); $cache = new \Doctrine\Common\Cache\RedisCache(); $cache->setRedis($redis); } else { $cache = new ArrayCache(); } } elseif ($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; } } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/ToolEvents.php000066400000000000000000000034231257105210500223030ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools; class ToolEvents { /** * The postGenerateSchemaTable event occurs in SchemaTool#getSchemaFromMetadata() * whenever an entity class is transformed into its table representation. It receives * the current non-complete Schema instance, the Entity Metadata Class instance and * the Schema Table instance of this entity. * * @var string */ const postGenerateSchemaTable = 'postGenerateSchemaTable'; /** * The postGenerateSchema event is triggered in SchemaTool#getSchemaFromMetadata() * after all entity classes have been transformed into the related Schema structure. * The EventArgs contain the EntityManager and the created Schema instance. * * @var string */ const postGenerateSchema = 'postGenerateSchema'; } doctrine2-2.4.8/lib/Doctrine/ORM/Tools/ToolsException.php000066400000000000000000000032671257105210500231660ustar00rootroot00000000000000. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\ORMException; /** * Tools related Exceptions. * * @author Benjamin Eberlei */ class ToolsException extends ORMException { /** * @param string $sql * @param \Exception $e * * @return ToolsException */ public static function schemaToolFailure($sql, \Exception $e) { return new self("Schema-Tool failed with Error '" . $e->getMessage() . "' while executing DDL: " . $sql, "0", $e); } /** * @param string $type * * @return ToolsException */ public static function couldNotMapDoctrine1Type($type) { return new self("Could not map doctrine 1 type '$type'!"); } } doctrine2-2.4.8/lib/Doctrine/ORM/TransactionRequiredException.php000066400000000000000000000031321257105210500247430ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * Is thrown when a transaction is required for the current operation, but there is none open. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Roman Borschel */ class TransactionRequiredException extends ORMException { /** * @return TransactionRequiredException */ static public function transactionRequired() { return new self('An open transaction is required for this operation.'); } } doctrine2-2.4.8/lib/Doctrine/ORM/UnexpectedResultException.php000066400000000000000000000022551257105210500242650ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * Exception for a unexpected query result. * * @author Fabio B. Silva * @since 2.3 */ class UnexpectedResultException extends ORMException { } doctrine2-2.4.8/lib/Doctrine/ORM/UnitOfWork.php000066400000000000000000003335701257105210500211610ustar00rootroot00000000000000. */ namespace Doctrine\ORM; use Exception; use InvalidArgumentException; use UnexpectedValueException; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\NotifyPropertyChanged; use Doctrine\Common\PropertyChangedListener; use Doctrine\Common\Persistence\ObjectManagerAware; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Proxy\Proxy; use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Event\PreUpdateEventArgs; use Doctrine\ORM\Event\PreFlushEventArgs; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Event\PostFlushEventArgs; use Doctrine\ORM\Event\ListenersInvoker; /** * The UnitOfWork is responsible for tracking changes to objects during an * "object-level" transaction and for writing out changes to the database * in the correct order. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @internal This class contains highly performance-sensitive code. */ class UnitOfWork implements PropertyChangedListener { /** * An entity is in MANAGED state when its persistence is managed by an EntityManager. */ const STATE_MANAGED = 1; /** * An entity is new if it has just been instantiated (i.e. using the "new" operator) * and is not (yet) managed by an EntityManager. */ const STATE_NEW = 2; /** * A detached entity is an instance with persistent state and identity that is not * (or no longer) associated with an EntityManager (and a UnitOfWork). */ const STATE_DETACHED = 3; /** * A removed entity instance is an instance with a persistent identity, * associated with an EntityManager, whose persistent state will be deleted * on commit. */ const STATE_REMOVED = 4; /** * Hint used to collect all primary keys of associated entities during hydration * and execute it in a dedicated query afterwards * @see https://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html?highlight=eager#temporarily-change-fetch-mode-in-dql */ const HINT_DEFEREAGERLOAD = 'deferEagerLoad'; /** * The identity map that holds references to all managed entities that have * an identity. The entities are grouped by their class name. * Since all classes in a hierarchy must share the same identifier set, * we always take the root class name of the hierarchy. * * @var array */ private $identityMap = array(); /** * Map of all identifiers of managed entities. * Keys are object ids (spl_object_hash). * * @var array */ private $entityIdentifiers = array(); /** * Map of the original entity data of managed entities. * Keys are object ids (spl_object_hash). This is used for calculating changesets * at commit time. * * @var array * @internal Note that PHPs "copy-on-write" behavior helps a lot with memory usage. * A value will only really be copied if the value in the entity is modified * by the user. */ private $originalEntityData = array(); /** * Map of entity changes. Keys are object ids (spl_object_hash). * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end. * * @var array */ private $entityChangeSets = array(); /** * The (cached) states of any known entities. * Keys are object ids (spl_object_hash). * * @var array */ private $entityStates = array(); /** * Map of entities that are scheduled for dirty checking at commit time. * This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT. * Keys are object ids (spl_object_hash). * * @var array * @todo rename: scheduledForSynchronization */ private $scheduledForDirtyCheck = array(); /** * A list of all pending entity insertions. * * @var array */ private $entityInsertions = array(); /** * A list of all pending entity updates. * * @var array */ private $entityUpdates = array(); /** * Any pending extra updates that have been scheduled by persisters. * * @var array */ private $extraUpdates = array(); /** * A list of all pending entity deletions. * * @var array */ private $entityDeletions = array(); /** * All pending collection deletions. * * @var array */ private $collectionDeletions = array(); /** * All pending collection updates. * * @var array */ private $collectionUpdates = array(); /** * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork. * At the end of the UnitOfWork all these collections will make new snapshots * of their data. * * @var array */ private $visitedCollections = array(); /** * The EntityManager that "owns" this UnitOfWork instance. * * @var \Doctrine\ORM\EntityManager */ private $em; /** * The calculator used to calculate the order in which changes to * entities need to be written to the database. * * @var \Doctrine\ORM\Internal\CommitOrderCalculator */ private $commitOrderCalculator; /** * The entity persister instances used to persist entity instances. * * @var array */ private $persisters = array(); /** * The collection persister instances used to persist collections. * * @var array */ private $collectionPersisters = array(); /** * The EventManager used for dispatching events. * * @var \Doctrine\Common\EventManager */ private $evm; /** * The ListenersInvoker used for dispatching events. * * @var \Doctrine\ORM\Event\ListenersInvoker */ private $listenersInvoker; /** * Orphaned entities that are scheduled for removal. * * @var array */ private $orphanRemovals = 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. * * @var array */ private $eagerLoadingEntities = array(); /** * Initializes a new UnitOfWork instance, bound to the given EntityManager. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; $this->evm = $em->getEventManager(); $this->listenersInvoker = new ListenersInvoker($em); } /** * Commits the UnitOfWork, executing all operations that have been postponed * up to this point. The state of all managed entities will be synchronized with * the database. * * The operations are executed in the following order: * * 1) All entity insertions * 2) All entity updates * 3) All collection deletions * 4) All collection updates * 5) All entity deletions * * @param null|object|array $entity * * @return void * * @throws \Exception */ public function commit($entity = null) { // Raise preFlush if ($this->evm->hasListeners(Events::preFlush)) { $this->evm->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em)); } // Compute changes done since last commit. if ($entity === null) { $this->computeChangeSets(); } elseif (is_object($entity)) { $this->computeSingleEntityChangeSet($entity); } elseif (is_array($entity)) { foreach ($entity as $object) { $this->computeSingleEntityChangeSet($object); } } if ( ! ($this->entityInsertions || $this->entityDeletions || $this->entityUpdates || $this->collectionUpdates || $this->collectionDeletions || $this->orphanRemovals)) { $this->dispatchOnFlushEvent(); $this->dispatchPostFlushEvent(); return; // Nothing to do. } if ($this->orphanRemovals) { foreach ($this->orphanRemovals as $orphan) { $this->remove($orphan); } } $this->dispatchOnFlushEvent(); // Now we need a commit order to maintain referential integrity $commitOrder = $this->getCommitOrder(); $conn = $this->em->getConnection(); $conn->beginTransaction(); try { if ($this->entityInsertions) { foreach ($commitOrder as $class) { $this->executeInserts($class); } } if ($this->entityUpdates) { foreach ($commitOrder as $class) { $this->executeUpdates($class); } } // Extra updates that were requested by persisters. if ($this->extraUpdates) { $this->executeExtraUpdates(); } // Collection deletions (deletions of complete collections) foreach ($this->collectionDeletions as $collectionToDelete) { $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete); } // Collection updates (deleteRows, updateRows, insertRows) foreach ($this->collectionUpdates as $collectionToUpdate) { $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate); } // Entity deletions come last and need to be in reverse commit order if ($this->entityDeletions) { for ($count = count($commitOrder), $i = $count - 1; $i >= 0; --$i) { $this->executeDeletions($commitOrder[$i]); } } $conn->commit(); } catch (Exception $e) { $this->em->close(); $conn->rollback(); throw $e; } // Take new snapshots from visited collections foreach ($this->visitedCollections as $coll) { $coll->takeSnapshot(); } $this->dispatchPostFlushEvent(); // Clear up $this->entityInsertions = $this->entityUpdates = $this->entityDeletions = $this->extraUpdates = $this->entityChangeSets = $this->collectionUpdates = $this->collectionDeletions = $this->visitedCollections = $this->scheduledForDirtyCheck = $this->orphanRemovals = array(); } /** * Computes the changesets of all entities scheduled for insertion. * * @return void */ private function computeScheduleInsertsChangeSets() { foreach ($this->entityInsertions as $entity) { $class = $this->em->getClassMetadata(get_class($entity)); $this->computeChangeSet($class, $entity); } } /** * Only flushes the given entity according to a ruleset that keeps the UoW consistent. * * 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well! * 2. Read Only entities are skipped. * 3. Proxies are skipped. * 4. Only if entity is properly managed. * * @param object $entity * * @return void * * @throws \InvalidArgumentException */ private function computeSingleEntityChangeSet($entity) { $state = $this->getEntityState($entity); if ($state !== self::STATE_MANAGED && $state !== self::STATE_REMOVED) { throw new \InvalidArgumentException("Entity has to be managed or scheduled for removal for single computation " . self::objToStr($entity)); } $class = $this->em->getClassMetadata(get_class($entity)); if ($state === self::STATE_MANAGED && $class->isChangeTrackingDeferredImplicit()) { $this->persist($entity); } // Compute changes for INSERTed entities first. This must always happen even in this case. $this->computeScheduleInsertsChangeSets(); if ($class->isReadOnly) { return; } // Ignore uninitialized proxy objects if ($entity instanceof Proxy && ! $entity->__isInitialized__) { return; } // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } } /** * Executes any extra updates that have been scheduled. */ private function executeExtraUpdates() { foreach ($this->extraUpdates as $oid => $update) { list ($entity, $changeset) = $update; $this->entityChangeSets[$oid] = $changeset; $this->getEntityPersister(get_class($entity))->update($entity); } } /** * Gets the changeset for an entity. * * @param object $entity * * @return array */ public function getEntityChangeSet($entity) { $oid = spl_object_hash($entity); if (isset($this->entityChangeSets[$oid])) { return $this->entityChangeSets[$oid]; } return array(); } /** * Computes the changes that happened to a single entity. * * Modifies/populates the following properties: * * {@link _originalEntityData} * If the entity is NEW or MANAGED but not yet fully persisted (only has an id) * then it was not fetched from the database and therefore we have no original * entity data yet. All of the current entity data is stored as the original entity data. * * {@link _entityChangeSets} * The changes detected on all properties of the entity are stored there. * A change is a tuple array where the first entry is the old value and the second * entry is the new value of the property. Changesets are used by persisters * to INSERT/UPDATE the persistent entity state. * * {@link _entityUpdates} * If the entity is already fully MANAGED (has been fetched from the database before) * and any changes to its properties are detected, then a reference to the entity is stored * there to mark it for an update. * * {@link _collectionDeletions} * If a PersistentCollection has been de-referenced in a fully MANAGED entity, * then this collection is marked for deletion. * * @ignore * * @internal Don't call from the outside. * * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to compute the changes. * * @return void */ public function computeChangeSet(ClassMetadata $class, $entity) { $oid = spl_object_hash($entity); if (isset($this->readOnlyObjects[$oid])) { return; } if ( ! $class->isInheritanceTypeNone()) { $class = $this->em->getClassMetadata(get_class($entity)); } $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER; if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($this->em), $invoke); } $actualData = array(); foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); if ($class->isCollectionValuedAssociation($name) && $value !== null) { if ($value instanceof PersistentCollection) { if ($value->getOwner() === $entity) { continue; } $value = new ArrayCollection($value->getValues()); } // If $value is not a Collection then use an ArrayCollection. if ( ! $value instanceof Collection) { $value = new ArrayCollection($value); } $assoc = $class->associationMappings[$name]; // Inject PersistentCollection $value = new PersistentCollection( $this->em, $this->em->getClassMetadata($assoc['targetEntity']), $value ); $value->setOwner($entity, $assoc); $value->setDirty( ! $value->isEmpty()); $class->reflFields[$name]->setValue($entity, $value); $actualData[$name] = $value; continue; } if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) { $actualData[$name] = $value; } } if ( ! isset($this->originalEntityData[$oid])) { // Entity is either NEW or MANAGED but not yet fully persisted (only has an id). // These result in an INSERT. $this->originalEntityData[$oid] = $actualData; $changeSet = array(); foreach ($actualData as $propName => $actualValue) { if ( ! isset($class->associationMappings[$propName])) { $changeSet[$propName] = array(null, $actualValue); continue; } $assoc = $class->associationMappings[$propName]; if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { $changeSet[$propName] = array(null, $actualValue); } } $this->entityChangeSets[$oid] = $changeSet; } else { // Entity is "fully" MANAGED: it was already fully persisted before // and we have a copy of the original data $originalData = $this->originalEntityData[$oid]; $isChangeTrackingNotify = $class->isChangeTrackingNotify(); $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array(); foreach ($actualData as $propName => $actualValue) { // skip field, its a partially omitted one! if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) { continue; } $orgValue = $originalData[$propName]; // skip if value haven't changed if ($orgValue === $actualValue) { continue; } // if regular field if ( ! isset($class->associationMappings[$propName])) { if ($isChangeTrackingNotify) { continue; } $changeSet[$propName] = array($orgValue, $actualValue); continue; } $assoc = $class->associationMappings[$propName]; // Persistent collection was exchanged with the "originally" // created one. This can only mean it was cloned and replaced // on another entity. if ($actualValue instanceof PersistentCollection) { $owner = $actualValue->getOwner(); if ($owner === null) { // cloned $actualValue->setOwner($entity, $assoc); } else if ($owner !== $entity) { // no clone, we have to fix if (!$actualValue->isInitialized()) { $actualValue->initialize(); // we have to do this otherwise the cols share state } $newValue = clone $actualValue; $newValue->setOwner($entity, $assoc); $class->reflFields[$propName]->setValue($entity, $newValue); } } if ($orgValue instanceof PersistentCollection) { // A PersistentCollection was de-referenced, so delete it. $coid = spl_object_hash($orgValue); if (isset($this->collectionDeletions[$coid])) { continue; } $this->collectionDeletions[$coid] = $orgValue; $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. continue; } if ($assoc['type'] & ClassMetadata::TO_ONE) { if ($assoc['isOwningSide']) { $changeSet[$propName] = array($orgValue, $actualValue); } if ($orgValue !== null && $assoc['orphanRemoval']) { $this->scheduleOrphanRemoval($orgValue); } } } if ($changeSet) { $this->entityChangeSets[$oid] = $changeSet; $this->originalEntityData[$oid] = $actualData; $this->entityUpdates[$oid] = $entity; } } // Look for changes in associations of the entity foreach ($class->associationMappings as $field => $assoc) { if (($val = $class->reflFields[$field]->getValue($entity)) !== null) { $this->computeAssociationChanges($assoc, $val); if (!isset($this->entityChangeSets[$oid]) && $assoc['isOwningSide'] && $assoc['type'] == ClassMetadata::MANY_TO_MANY && $val instanceof PersistentCollection && $val->isDirty()) { $this->entityChangeSets[$oid] = array(); $this->originalEntityData[$oid] = $actualData; $this->entityUpdates[$oid] = $entity; } } } } /** * Computes all the changes that have been done to entities and collections * since the last commit and stores these changes in the _entityChangeSet map * temporarily for access by the persisters, until the UoW commit is finished. * * @return void */ public function computeChangeSets() { // Compute changes for INSERTed entities first. This must always happen. $this->computeScheduleInsertsChangeSets(); // Compute changes for other MANAGED entities. Change tracking policies take effect here. foreach ($this->identityMap as $className => $entities) { $class = $this->em->getClassMetadata($className); // Skip class if instances are read-only if ($class->isReadOnly) { continue; } // If change tracking is explicit or happens through notification, then only compute // changes on entities of that type that are explicitly marked for synchronization. switch (true) { case ($class->isChangeTrackingDeferredImplicit()): $entitiesToProcess = $entities; break; case (isset($this->scheduledForDirtyCheck[$className])): $entitiesToProcess = $this->scheduledForDirtyCheck[$className]; break; default: $entitiesToProcess = array(); } foreach ($entitiesToProcess as $entity) { // Ignore uninitialized proxy objects if ($entity instanceof Proxy && ! $entity->__isInitialized__) { continue; } // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } } } } /** * Computes the changes of an association. * * @param array $assoc * @param mixed $value The value of the association. * * @throws ORMInvalidArgumentException * @throws ORMException * * @return void */ private function computeAssociationChanges($assoc, $value) { if ($value instanceof Proxy && ! $value->__isInitialized__) { return; } if ($value instanceof PersistentCollection && $value->isDirty()) { $coid = spl_object_hash($value); if ($assoc['isOwningSide']) { $this->collectionUpdates[$coid] = $value; } $this->visitedCollections[$coid] = $value; } // Look through the entities, and in any of their associations, // for transient (new) entities, recursively. ("Persistence by reachability") // Unwrap. Uninitialized collections will simply be empty. $unwrappedValue = ($assoc['type'] & ClassMetadata::TO_ONE) ? array($value) : $value->unwrap(); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); foreach ($unwrappedValue as $key => $entry) { $state = $this->getEntityState($entry, self::STATE_NEW); if ( ! ($entry instanceof $assoc['targetEntity'])) { throw new ORMException( sprintf( 'Found entity of type %s on association %s#%s, but expecting %s', get_class($entry), $assoc['sourceEntity'], $assoc['fieldName'], $targetClass->name ) ); } switch ($state) { case self::STATE_NEW: if ( ! $assoc['isCascadePersist']) { throw ORMInvalidArgumentException::newEntityFoundThroughRelationship($assoc, $entry); } $this->persistNew($targetClass, $entry); $this->computeChangeSet($targetClass, $entry); break; case self::STATE_REMOVED: // Consume the $value as array (it's either an array or an ArrayAccess) // and remove the element from Collection. if ($assoc['type'] & ClassMetadata::TO_MANY) { unset($value[$key]); } break; case self::STATE_DETACHED: // Can actually not happen right now as we assume STATE_NEW, // so the exception will be raised from the DBAL layer (constraint violation). throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($assoc, $entry); break; default: // MANAGED associated entities are already taken into account // during changeset calculation anyway, since they are in the identity map. } } } /** * @param \Doctrine\ORM\Mapping\ClassMetadata $class * @param object $entity * * @return void */ private function persistNew($class, $entity) { $oid = spl_object_hash($entity); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist); if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } $idGen = $class->idGenerator; if ( ! $idGen->isPostInsertGenerator()) { $idValue = $idGen->generate($this->em, $entity); if ( ! $idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) { $idValue = array($class->identifier[0] => $idValue); $class->setIdentifierValues($entity, $idValue); } $this->entityIdentifiers[$oid] = $idValue; } $this->entityStates[$oid] = self::STATE_MANAGED; $this->scheduleForInsert($entity); } /** * INTERNAL: * Computes the changeset of an individual entity, independently of the * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit(). * * The passed entity must be a managed entity. If the entity already has a change set * because this method is invoked during a commit cycle then the change sets are added. * whereby changes detected in this method prevail. * * @ignore * * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to (re)calculate the change set. * * @return void * * @throws ORMInvalidArgumentException If the passed entity is not MANAGED. */ public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) { $oid = spl_object_hash($entity); if ( ! isset($this->entityStates[$oid]) || $this->entityStates[$oid] != self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } // skip if change tracking is "NOTIFY" if ($class->isChangeTrackingNotify()) { return; } if ( ! $class->isInheritanceTypeNone()) { $class = $this->em->getClassMetadata(get_class($entity)); } $actualData = array(); foreach ($class->reflFields as $name => $refProp) { if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField) && ! $class->isCollectionValuedAssociation($name)) { $actualData[$name] = $refProp->getValue($entity); } } if ( ! isset($this->originalEntityData[$oid])) { throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.'); } $originalData = $this->originalEntityData[$oid]; $changeSet = array(); foreach ($actualData as $propName => $actualValue) { $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; if ($orgValue !== $actualValue) { $changeSet[$propName] = array($orgValue, $actualValue); } } if ($changeSet) { if (isset($this->entityChangeSets[$oid])) { $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet); } else if ( ! isset($this->entityInsertions[$oid])) { $this->entityChangeSets[$oid] = $changeSet; $this->entityUpdates[$oid] = $entity; } $this->originalEntityData[$oid] = $actualData; } } /** * Executes all entity insertions for entities of the specified type. * * @param \Doctrine\ORM\Mapping\ClassMetadata $class * * @return void */ private function executeInserts($class) { $entities = array(); $className = $class->name; $persister = $this->getEntityPersister($className); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist); foreach ($this->entityInsertions as $oid => $entity) { if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } $persister->addInsert($entity); unset($this->entityInsertions[$oid]); if ($invoke !== ListenersInvoker::INVOKE_NONE) { $entities[] = $entity; } } $postInsertIds = $persister->executeInserts(); if ($postInsertIds) { // Persister returned post-insert IDs foreach ($postInsertIds as $id => $entity) { $oid = spl_object_hash($entity); $idField = $class->identifier[0]; $class->reflFields[$idField]->setValue($entity, $id); $this->entityIdentifiers[$oid] = array($idField => $id); $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid][$idField] = $id; $this->addToIdentityMap($entity); } } foreach ($entities as $entity) { $this->listenersInvoker->invoke($class, Events::postPersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } } /** * Executes all entity updates for entities of the specified type. * * @param \Doctrine\ORM\Mapping\ClassMetadata $class * * @return void */ private function executeUpdates($class) { $className = $class->name; $persister = $this->getEntityPersister($className); $preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate); $postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate); foreach ($this->entityUpdates as $oid => $entity) { if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->entityChangeSets[$oid]), $preUpdateInvoke); $this->recomputeSingleEntityChangeSet($class, $entity); } if ( ! empty($this->entityChangeSets[$oid])) { $persister->update($entity); } unset($this->entityUpdates[$oid]); if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke); } } } /** * Executes all entity deletions for entities of the specified type. * * @param \Doctrine\ORM\Mapping\ClassMetadata $class * * @return void */ private function executeDeletions($class) { $className = $class->name; $persister = $this->getEntityPersister($className); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove); foreach ($this->entityDeletions as $oid => $entity) { if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } $persister->delete($entity); unset( $this->entityDeletions[$oid], $this->entityIdentifiers[$oid], $this->originalEntityData[$oid], $this->entityStates[$oid] ); // Entity with this $oid after deletion treated as NEW, even if the $oid // is obtained by a new entity because the old one went out of scope. //$this->entityStates[$oid] = self::STATE_NEW; if ( ! $class->isIdentifierNatural()) { $class->reflFields[$class->identifier[0]]->setValue($entity, null); } if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } } } /** * Gets the commit order. * * @param array|null $entityChangeSet * * @return array */ private function getCommitOrder(array $entityChangeSet = null) { if ($entityChangeSet === null) { $entityChangeSet = array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions); } $calc = $this->getCommitOrderCalculator(); // See if there are any new classes in the changeset, that are not in the // commit order graph yet (don't have a node). // We have to inspect changeSet to be able to correctly build dependencies. // It is not possible to use IdentityMap here because post inserted ids // are not yet available. $newNodes = array(); foreach ($entityChangeSet as $entity) { $className = $this->em->getClassMetadata(get_class($entity))->name; if ($calc->hasClass($className)) { continue; } $class = $this->em->getClassMetadata($className); $calc->addClass($class); $newNodes[] = $class; } // Calculate dependencies for new nodes while ($class = array_pop($newNodes)) { foreach ($class->associationMappings as $assoc) { if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { continue; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); if ( ! $calc->hasClass($targetClass->name)) { $calc->addClass($targetClass); $newNodes[] = $targetClass; } $calc->addDependency($targetClass, $class); // If the target class has mapped subclasses, these share the same dependency. if ( ! $targetClass->subClasses) { continue; } foreach ($targetClass->subClasses as $subClassName) { $targetSubClass = $this->em->getClassMetadata($subClassName); if ( ! $calc->hasClass($subClassName)) { $calc->addClass($targetSubClass); $newNodes[] = $targetSubClass; } $calc->addDependency($targetSubClass, $class); } } } return $calc->getCommitOrder(); } /** * Schedules an entity for insertion into the database. * If the entity already has an identifier, it will be added to the identity map. * * @param object $entity The entity to schedule for insertion. * * @return void * * @throws ORMInvalidArgumentException * @throws \InvalidArgumentException */ public function scheduleForInsert($entity) { $oid = spl_object_hash($entity); if (isset($this->entityUpdates[$oid])) { throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion."); } if (isset($this->entityDeletions[$oid])) { throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity); } if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) { throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity); } if (isset($this->entityInsertions[$oid])) { throw ORMInvalidArgumentException::scheduleInsertTwice($entity); } $this->entityInsertions[$oid] = $entity; if (isset($this->entityIdentifiers[$oid])) { $this->addToIdentityMap($entity); } if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } } /** * Checks whether an entity is scheduled for insertion. * * @param object $entity * * @return boolean */ public function isScheduledForInsert($entity) { return isset($this->entityInsertions[spl_object_hash($entity)]); } /** * Schedules an entity for being updated. * * @param object $entity The entity to schedule for being updated. * * @return void * * @throws ORMInvalidArgumentException */ public function scheduleForUpdate($entity) { $oid = spl_object_hash($entity); if ( ! isset($this->entityIdentifiers[$oid])) { throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "scheduling for update"); } if (isset($this->entityDeletions[$oid])) { throw ORMInvalidArgumentException::entityIsRemoved($entity, "schedule for update"); } if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) { $this->entityUpdates[$oid] = $entity; } } /** * INTERNAL: * Schedules an extra update that will be executed immediately after the * regular entity updates within the currently running commit cycle. * * Extra updates for entities are stored as (entity, changeset) tuples. * * @ignore * * @param object $entity The entity for which to schedule an extra update. * @param array $changeset The changeset of the entity (what to update). * * @return void */ public function scheduleExtraUpdate($entity, array $changeset) { $oid = spl_object_hash($entity); $extraUpdate = array($entity, $changeset); if (isset($this->extraUpdates[$oid])) { list($ignored, $changeset2) = $this->extraUpdates[$oid]; $extraUpdate = array($entity, $changeset + $changeset2); } $this->extraUpdates[$oid] = $extraUpdate; } /** * Checks whether an entity is registered as dirty in the unit of work. * Note: Is not very useful currently as dirty entities are only registered * at commit time. * * @param object $entity * * @return boolean */ public function isScheduledForUpdate($entity) { return isset($this->entityUpdates[spl_object_hash($entity)]); } /** * Checks whether an entity is registered to be checked in the unit of work. * * @param object $entity * * @return boolean */ public function isScheduledForDirtyCheck($entity) { $rootEntityName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; return isset($this->scheduledForDirtyCheck[$rootEntityName][spl_object_hash($entity)]); } /** * INTERNAL: * Schedules an entity for deletion. * * @param object $entity * * @return void */ public function scheduleForDelete($entity) { $oid = spl_object_hash($entity); if (isset($this->entityInsertions[$oid])) { if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } unset($this->entityInsertions[$oid], $this->entityStates[$oid]); return; // entity has not been persisted yet, so nothing more to do. } if ( ! $this->isInIdentityMap($entity)) { return; } $this->removeFromIdentityMap($entity); if (isset($this->entityUpdates[$oid])) { unset($this->entityUpdates[$oid]); } if ( ! isset($this->entityDeletions[$oid])) { $this->entityDeletions[$oid] = $entity; $this->entityStates[$oid] = self::STATE_REMOVED; } } /** * Checks whether an entity is registered as removed/deleted with the unit * of work. * * @param object $entity * * @return boolean */ public function isScheduledForDelete($entity) { return isset($this->entityDeletions[spl_object_hash($entity)]); } /** * Checks whether an entity is scheduled for insertion, update or deletion. * * @param object $entity * * @return boolean */ public function isEntityScheduled($entity) { $oid = spl_object_hash($entity); return isset($this->entityInsertions[$oid]) || isset($this->entityUpdates[$oid]) || isset($this->entityDeletions[$oid]); } /** * INTERNAL: * Registers an entity in the identity map. * Note that entities in a hierarchy are registered with the class name of * the root entity. * * @ignore * * @param object $entity The entity to register. * * @return boolean TRUE if the registration was successful, FALSE if the identity of * the entity in question is already managed. * * @throws ORMInvalidArgumentException */ public function addToIdentityMap($entity) { $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[spl_object_hash($entity)]); if ($idHash === '') { throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->name, $entity); } $className = $classMetadata->rootEntityName; if (isset($this->identityMap[$className][$idHash])) { return false; } $this->identityMap[$className][$idHash] = $entity; return true; } /** * Gets the state of an entity with regard to the current unit of work. * * @param object $entity * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED). * This parameter can be set to improve performance of entity state detection * by potentially avoiding a database lookup if the distinction between NEW and DETACHED * is either known or does not matter for the caller of the method. * * @return int The entity state. */ public function getEntityState($entity, $assume = null) { $oid = spl_object_hash($entity); if (isset($this->entityStates[$oid])) { return $this->entityStates[$oid]; } if ($assume !== null) { return $assume; } // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known. // Note that you can not remember the NEW or DETACHED state in _entityStates since // the UoW does not hold references to such objects and the object hash can be reused. // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it. $class = $this->em->getClassMetadata(get_class($entity)); $id = $class->getIdentifierValues($entity); if ( ! $id) { return self::STATE_NEW; } if ($class->containsForeignIdentifier) { $id = $this->flattenIdentifier($class, $id); } switch (true) { case ($class->isIdentifierNatural()); // Check for a version field, if available, to avoid a db lookup. if ($class->isVersioned) { return ($class->getFieldValue($entity, $class->versionField)) ? self::STATE_DETACHED : self::STATE_NEW; } // Last try before db lookup: check the identity map. if ($this->tryGetById($id, $class->rootEntityName)) { return self::STATE_DETACHED; } // db lookup if ($this->getEntityPersister($class->name)->exists($entity)) { return self::STATE_DETACHED; } return self::STATE_NEW; case ( ! $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; } // db lookup if ($this->getEntityPersister($class->name)->exists($entity)) { return self::STATE_DETACHED; } return self::STATE_NEW; default: return self::STATE_DETACHED; } } /** * INTERNAL: * Removes an entity from the identity map. This effectively detaches the * entity from the persistence management of Doctrine. * * @ignore * * @param object $entity * * @return boolean * * @throws ORMInvalidArgumentException */ public function removeFromIdentityMap($entity) { $oid = spl_object_hash($entity); $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[$oid]); if ($idHash === '') { throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "remove from identity map"); } $className = $classMetadata->rootEntityName; if (isset($this->identityMap[$className][$idHash])) { unset($this->identityMap[$className][$idHash]); unset($this->readOnlyObjects[$oid]); //$this->entityStates[$oid] = self::STATE_DETACHED; return true; } return false; } /** * INTERNAL: * Gets an entity in the identity map by its identifier hash. * * @ignore * * @param string $idHash * @param string $rootClassName * * @return object */ public function getByIdHash($idHash, $rootClassName) { return $this->identityMap[$rootClassName][$idHash]; } /** * INTERNAL: * Tries to get an entity by its identifier hash. If no entity is found for * the given hash, FALSE is returned. * * @ignore * * @param string $idHash * @param string $rootClassName * * @return object|bool The found entity or FALSE. */ public function tryGetByIdHash($idHash, $rootClassName) { if (isset($this->identityMap[$rootClassName][$idHash])) { return $this->identityMap[$rootClassName][$idHash]; } return false; } /** * Checks whether an entity is registered in the identity map of this UnitOfWork. * * @param object $entity * * @return boolean */ public function isInIdentityMap($entity) { $oid = spl_object_hash($entity); if ( ! isset($this->entityIdentifiers[$oid])) { return false; } $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[$oid]); if ($idHash === '') { return false; } return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]); } /** * INTERNAL: * Checks whether an identifier hash exists in the identity map. * * @ignore * * @param string $idHash * @param string $rootClassName * * @return boolean */ public function containsIdHash($idHash, $rootClassName) { return isset($this->identityMap[$rootClassName][$idHash]); } /** * Persists an entity as part of the current unit of work. * * @param object $entity The entity to persist. * * @return void */ public function persist($entity) { $visited = array(); $this->doPersist($entity, $visited); } /** * Persists an entity as part of the current unit of work. * * This method is internally called during persist() cascades as it tracks * the already visited entities to prevent infinite recursions. * * @param object $entity The entity to persist. * @param array $visited The already visited entities. * * @return void * * @throws ORMInvalidArgumentException * @throws UnexpectedValueException */ private function doPersist($entity, array &$visited) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // Mark visited $class = $this->em->getClassMetadata(get_class($entity)); // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation). // If we would detect DETACHED here we would throw an exception anyway with the same // consequences (not recoverable/programming error), so just assuming NEW here // lets us avoid some database lookups for entities with natural identifiers. $entityState = $this->getEntityState($entity, self::STATE_NEW); switch ($entityState) { case self::STATE_MANAGED: // Nothing to do, except if policy is "deferred explicit" if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } break; case self::STATE_NEW: $this->persistNew($class, $entity); break; case self::STATE_REMOVED: // Entity becomes managed again unset($this->entityDeletions[$oid]); $this->addToIdentityMap($entity); $this->entityStates[$oid] = self::STATE_MANAGED; break; case self::STATE_DETACHED: // Can actually not happen right now since we assume STATE_NEW. throw ORMInvalidArgumentException::detachedEntityCannot($entity, "persisted"); default: throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity)); } $this->cascadePersist($entity, $visited); } /** * Deletes an entity as part of the current unit of work. * * @param object $entity The entity to remove. * * @return void */ public function remove($entity) { $visited = array(); $this->doRemove($entity, $visited); } /** * Deletes an entity as part of the current unit of work. * * This method is internally called during delete() cascades as it tracks * the already visited entities to prevent infinite recursions. * * @param object $entity The entity to delete. * @param array $visited The map of the already visited entities. * * @return void * * @throws ORMInvalidArgumentException If the instance is a detached entity. * @throws UnexpectedValueException */ private function doRemove($entity, array &$visited) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $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); switch ($entityState) { case self::STATE_NEW: case self::STATE_REMOVED: // nothing to do break; case self::STATE_MANAGED: $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preRemove); if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::preRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } $this->scheduleForDelete($entity); break; case self::STATE_DETACHED: throw ORMInvalidArgumentException::detachedEntityCannot($entity, "removed"); default: throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity)); } } /** * Merges the state of the given detached entity into this UnitOfWork. * * @param object $entity * * @return object The managed copy of the entity. * * @throws OptimisticLockException If the entity uses optimistic locking through a version * attribute and the version check against the managed copy fails. * * @todo Require active transaction!? OptimisticLockException may result in undefined state!? */ public function merge($entity) { $visited = array(); return $this->doMerge($entity, $visited); } /** * convert foreign identifiers into scalar foreign key values to avoid object to string conversion failures. * * @param ClassMetadata $class * @param array $id * @return array */ private function flattenIdentifier($class, $id) { $flatId = array(); foreach ($id as $idField => $idValue) { if (isset($class->associationMappings[$idField])) { $targetClassMetadata = $this->em->getClassMetadata($class->associationMappings[$idField]['targetEntity']); $associatedId = $this->getEntityIdentifier($idValue); $flatId[$idField] = $associatedId[$targetClassMetadata->identifier[0]]; } else { $flatId[$idField] = $idValue; } } return $flatId; } /** * Executes a merge operation on an entity. * * @param object $entity * @param array $visited * @param object|null $prevManagedCopy * @param array|null $assoc * * @return object The managed copy of the entity. * * @throws OptimisticLockException If the entity uses optimistic locking through a version * attribute and the version check against the managed copy fails. * @throws ORMInvalidArgumentException If the entity instance is NEW. * @throws EntityNotFoundException */ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return $visited[$oid]; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited $class = $this->em->getClassMetadata(get_class($entity)); // First we assume DETACHED, although it can still be NEW but we can avoid // an extra db-roundtrip this way. If it is not MANAGED but has an identity, // we need to fetch it from the db anyway in order to merge. // MANAGED entities are ignored by the merge operation. $managedCopy = $entity; if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) { if ($entity instanceof Proxy && ! $entity->__isInitialized()) { $this->em->getProxyFactory()->resetUninitializedProxy($entity); $entity->__load(); } // Try to look the entity up in the identity map. $id = $class->getIdentifierValues($entity); // If there is no ID, it is actually NEW. if ( ! $id) { $managedCopy = $this->newInstance($class); $this->persistNew($class, $managedCopy); } else { $flatId = ($class->containsForeignIdentifier) ? $this->flattenIdentifier($class, $id) : $id; $managedCopy = $this->tryGetById($flatId, $class->rootEntityName); if ($managedCopy) { // We have the entity in-memory already, just make sure its not removed. if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) { throw ORMInvalidArgumentException::entityIsRemoved($managedCopy, "merge"); } } else { // We need to fetch the managed copy in order to merge. $managedCopy = $this->em->find($class->name, $flatId); } if ($managedCopy === null) { // If the identifier is ASSIGNED, it is NEW, otherwise an error // since the managed entity was not found. if ( ! $class->isIdentifierNatural()) { throw new EntityNotFoundException; } $managedCopy = $this->newInstance($class); $class->setIdentifierValues($managedCopy, $id); $this->persistNew($class, $managedCopy); } else { if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized__) { $managedCopy->__load(); } } } if ($class->isVersioned) { $reflField = $class->reflFields[$class->versionField]; $managedCopyVersion = $reflField->getValue($managedCopy); $entityVersion = $reflField->getValue($entity); // Throw exception if versions don't match. if ($managedCopyVersion != $entityVersion) { throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion); } } // Merge state of $entity into existing (managed) entity foreach ($class->reflClass->getProperties() as $prop) { $name = $prop->name; $prop->setAccessible(true); if ( ! isset($class->associationMappings[$name])) { if ( ! $class->isIdentifier($name)) { $prop->setValue($managedCopy, $prop->getValue($entity)); } } else { $assoc2 = $class->associationMappings[$name]; if ($assoc2['type'] & ClassMetadata::TO_ONE) { $other = $prop->getValue($entity); if ($other === null) { $prop->setValue($managedCopy, null); } else if ($other instanceof Proxy && !$other->__isInitialized__) { // do not merge fields marked lazy that have not been fetched. continue; } else if ( ! $assoc2['isCascadeMerge']) { if ($this->getEntityState($other) === self::STATE_DETACHED) { $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); $relatedId = $targetClass->getIdentifierValues($other); if ($targetClass->subClasses) { $other = $this->em->find($targetClass->name, $relatedId); } else { $other = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId); $this->registerManaged($other, $relatedId, array()); } } $prop->setValue($managedCopy, $other); } } else { $mergeCol = $prop->getValue($entity); if ($mergeCol instanceof PersistentCollection && !$mergeCol->isInitialized()) { // do not merge fields marked lazy that have not been fetched. // keep the lazy persistent collection of the managed copy. continue; } $managedCol = $prop->getValue($managedCopy); if (!$managedCol) { $managedCol = new PersistentCollection($this->em, $this->em->getClassMetadata($assoc2['targetEntity']), new ArrayCollection ); $managedCol->setOwner($managedCopy, $assoc2); $prop->setValue($managedCopy, $managedCol); $this->originalEntityData[$oid][$name] = $managedCol; } if ($assoc2['isCascadeMerge']) { $managedCol->initialize(); // 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()) { $this->scheduleForDirtyCheck($managedCopy); } } } } } if ($class->isChangeTrackingNotify()) { // Just treat all properties as changed, there is no other choice. $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy)); } } if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } } if ($prevManagedCopy !== null) { $assocField = $assoc['fieldName']; $prevClass = $this->em->getClassMetadata(get_class($prevManagedCopy)); if ($assoc['type'] & ClassMetadata::TO_ONE) { $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy); } else { $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy); if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy); } } } // Mark the managed copy visited as well $visited[spl_object_hash($managedCopy)] = true; $this->cascadeMerge($entity, $managedCopy, $visited); return $managedCopy; } /** * Detaches an entity from the persistence management. It's persistence will * no longer be managed by Doctrine. * * @param object $entity The entity to detach. * * @return void */ public function detach($entity) { $visited = array(); $this->doDetach($entity, $visited); } /** * Executes a detach operation on the given entity. * * @param object $entity * @param array $visited * @param boolean $noCascade if true, don't cascade detach operation. * * @return void */ private function doDetach($entity, array &$visited, $noCascade = false) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited switch ($this->getEntityState($entity, self::STATE_DETACHED)) { case self::STATE_MANAGED: if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } unset( $this->entityInsertions[$oid], $this->entityUpdates[$oid], $this->entityDeletions[$oid], $this->entityIdentifiers[$oid], $this->entityStates[$oid], $this->originalEntityData[$oid] ); break; case self::STATE_NEW: case self::STATE_DETACHED: return; } if ( ! $noCascade) { $this->cascadeDetach($entity, $visited); } } /** * Refreshes the state of the given entity from the database, overwriting * any local, unpersisted changes. * * @param object $entity The entity to refresh. * * @return void * * @throws InvalidArgumentException If the entity is not MANAGED. */ public function refresh($entity) { $visited = array(); $this->doRefresh($entity, $visited); } /** * Executes a refresh operation on an entity. * * @param object $entity The entity to refresh. * @param array $visited The already visited entities during cascades. * * @return void * * @throws ORMInvalidArgumentException If the entity is not MANAGED. */ private function doRefresh($entity, array &$visited) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited $class = $this->em->getClassMetadata(get_class($entity)); if ($this->getEntityState($entity) !== self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } $this->getEntityPersister($class->name)->refresh( array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $entity ); $this->cascadeRefresh($entity, $visited); } /** * Cascades a refresh operation to associated entities. * * @param object $entity * @param array $visited * * @return void */ private function cascadeRefresh($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, function ($assoc) { return $assoc['isCascadeRefresh']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): foreach ($relatedEntities as $relatedEntity) { $this->doRefresh($relatedEntity, $visited); } break; case ($relatedEntities !== null): $this->doRefresh($relatedEntities, $visited); break; default: // Do nothing } } } /** * Cascades a detach operation to associated entities. * * @param object $entity * @param array $visited * * @return void */ private function cascadeDetach($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, function ($assoc) { return $assoc['isCascadeDetach']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): foreach ($relatedEntities as $relatedEntity) { $this->doDetach($relatedEntity, $visited); } break; case ($relatedEntities !== null): $this->doDetach($relatedEntities, $visited); break; default: // Do nothing } } } /** * Cascades a merge operation to associated entities. * * @param object $entity * @param object $managedCopy * @param array $visited * * @return void */ private function cascadeMerge($entity, $managedCopy, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, function ($assoc) { return $assoc['isCascadeMerge']; } ); foreach ($associationMappings as $assoc) { $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(); } foreach ($relatedEntities as $relatedEntity) { $this->doMerge($relatedEntity, $visited, $managedCopy, $assoc); } } else if ($relatedEntities !== null) { $this->doMerge($relatedEntities, $visited, $managedCopy, $assoc); } } } /** * Cascades the save operation to associated entities. * * @param object $entity * @param array $visited * * @return void */ private function cascadePersist($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, function ($assoc) { return $assoc['isCascadePersist']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): foreach ($relatedEntities as $relatedEntity) { $this->doPersist($relatedEntity, $visited); } break; case ($relatedEntities !== null): $this->doPersist($relatedEntities, $visited); break; default: // Do nothing } } } /** * Cascades the delete operation to associated entities. * * @param object $entity * @param array $visited * * @return void */ private function cascadeRemove($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, function ($assoc) { return $assoc['isCascadeRemove']; } ); $entitiesToCascade = array(); foreach ($associationMappings as $assoc) { if ($entity instanceof Proxy && !$entity->__isInitialized__) { $entity->__load(); } $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): // If its a PersistentCollection initialization is intended! No unwrap! foreach ($relatedEntities as $relatedEntity) { $entitiesToCascade[] = $relatedEntity; } break; case ($relatedEntities !== null): $entitiesToCascade[] = $relatedEntities; break; default: // Do nothing } } foreach ($entitiesToCascade as $relatedEntity) { $this->doRemove($relatedEntity, $visited); } } /** * Acquire a lock on the given entity. * * @param object $entity * @param int $lockMode * @param int $lockVersion * * @return void * * @throws ORMInvalidArgumentException * @throws TransactionRequiredException * @throws OptimisticLockException */ public function lock($entity, $lockMode, $lockVersion = null) { if ($entity === null) { throw new \InvalidArgumentException("No entity passed to UnitOfWork#lock()."); } if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } $class = $this->em->getClassMetadata(get_class($entity)); switch ($lockMode) { case \Doctrine\DBAL\LockMode::OPTIMISTIC; if ( ! $class->isVersioned) { throw OptimisticLockException::notVersioned($class->name); } if ($lockVersion === null) { return; } if ($entity instanceof Proxy && !$entity->__isInitialized__) { $entity->__load(); } $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); if ($entityVersion != $lockVersion) { throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion); } break; case \Doctrine\DBAL\LockMode::PESSIMISTIC_READ: case \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE: if (!$this->em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } $oid = spl_object_hash($entity); $this->getEntityPersister($class->name)->lock( array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $lockMode ); break; default: // Do nothing } } /** * Gets the CommitOrderCalculator used by the UnitOfWork to order commits. * * @return \Doctrine\ORM\Internal\CommitOrderCalculator */ public function getCommitOrderCalculator() { if ($this->commitOrderCalculator === null) { $this->commitOrderCalculator = new Internal\CommitOrderCalculator; } return $this->commitOrderCalculator; } /** * Clears the UnitOfWork. * * @param string|null $entityName if given, only entities of this type will get detached. * * @return void */ public function clear($entityName = null) { if ($entityName === null) { $this->identityMap = $this->entityIdentifiers = $this->originalEntityData = $this->entityChangeSets = $this->entityStates = $this->scheduledForDirtyCheck = $this->entityInsertions = $this->entityUpdates = $this->entityDeletions = $this->collectionDeletions = $this->collectionUpdates = $this->extraUpdates = $this->readOnlyObjects = $this->visitedCollections = $this->orphanRemovals = array(); if ($this->commitOrderCalculator !== null) { $this->commitOrderCalculator->clear(); } } else { $visited = array(); foreach ($this->identityMap as $className => $entities) { if ($className === $entityName) { foreach ($entities as $entity) { $this->doDetach($entity, $visited, true); } } } } if ($this->evm->hasListeners(Events::onClear)) { $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em, $entityName)); } } /** * INTERNAL: * Schedules an orphaned entity for removal. The remove() operation will be * invoked on that entity at the beginning of the next commit of this * UnitOfWork. * * @ignore * * @param object $entity * * @return void */ public function scheduleOrphanRemoval($entity) { $this->orphanRemovals[spl_object_hash($entity)] = $entity; } /** * INTERNAL: * Schedules a complete collection for removal when this UnitOfWork commits. * * @param PersistentCollection $coll * * @return void */ public function scheduleCollectionDeletion(PersistentCollection $coll) { $coid = spl_object_hash($coll); //TODO: if $coll is already scheduled for recreation ... what to do? // Just remove $coll from the scheduled recreations? if (isset($this->collectionUpdates[$coid])) { unset($this->collectionUpdates[$coid]); } $this->collectionDeletions[$coid] = $coll; } /** * @param PersistentCollection $coll * * @return bool */ public function isCollectionScheduledForDeletion(PersistentCollection $coll) { return isset($this->collectionDeletions[spl_object_hash($coll)]); } /** * @param ClassMetadata $class * * @return \Doctrine\Common\Persistence\ObjectManagerAware|object */ private function newInstance($class) { $entity = $class->newInstance(); if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } return $entity; } /** * INTERNAL: * Creates an entity. Used for reconstitution of persistent entities. * * @ignore * * @param string $className The name of the entity class. * @param array $data The data for the entity. * @param array $hints Any hints to account for during reconstitution/lookup of the entity. * * @return object The managed entity instance. * * @internal Highly performance-sensitive method. * * @todo Rename: getOrCreateEntity */ public function createEntity($className, array $data, &$hints = array()) { $class = $this->em->getClassMetadata($className); //$isReadOnly = isset($hints[Query::HINT_READ_ONLY]); if ($class->isIdentifierComposite) { $id = array(); foreach ($class->identifier as $fieldName) { $id[$fieldName] = isset($class->associationMappings[$fieldName]) ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] : $data[$fieldName]; } } else { $id = isset($class->associationMappings[$class->identifier[0]]) ? $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']] : $data[$class->identifier[0]]; $id = array($class->identifier[0] => $id); } $idHash = implode(' ', $id); if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_hash($entity); if ( isset($hints[Query::HINT_REFRESH]) && isset($hints[Query::HINT_REFRESH_ENTITY]) && ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity && $unmanagedProxy instanceof Proxy && $this->isIdentifierEquals($unmanagedProxy, $entity) ) { // DDC-1238 - we have a managed instance, but it isn't the provided one. // Therefore we clear its identifier. Also, we must re-fetch metadata since the // refreshed object may be anything foreach ($class->identifier as $fieldName) { $class->reflFields[$fieldName]->setValue($unmanagedProxy, null); } return $unmanagedProxy; } if ($entity instanceof Proxy && ! $entity->__isInitialized()) { $entity->__setInitialized(true); $overrideLocalValues = true; if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } // inject ObjectManager into just loaded proxies. if ($overrideLocalValues && $entity instanceof ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } } else { $overrideLocalValues = isset($hints[Query::HINT_REFRESH]); // If only a specific entity is set to refresh, check that it's the one if(isset($hints[Query::HINT_REFRESH_ENTITY])) { $overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity; } // inject ObjectManager upon refresh. if ($overrideLocalValues && $entity instanceof ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } } if ($overrideLocalValues) { $this->originalEntityData[$oid] = $data; } } else { $entity = $this->newInstance($class); $oid = spl_object_hash($entity); $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; $this->identityMap[$class->rootEntityName][$idHash] = $entity; if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } $overrideLocalValues = true; } if ( ! $overrideLocalValues) { return $entity; } foreach ($data as $field => $value) { if (isset($class->fieldMappings[$field])) { $class->reflFields[$field]->setValue($entity, $value); } } // Loading the entity right here, if its in the eager loading map get rid of it there. unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) { unset($this->eagerLoadingEntities[$class->rootEntityName]); } // Properly initialize any unfetched associations, if partial objects are not allowed. if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { return $entity; } foreach ($class->associationMappings as $field => $assoc) { // Check if the association is not among the fetch-joined associations already. if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) { continue; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); switch (true) { case ($assoc['type'] & ClassMetadata::TO_ONE): if ( ! $assoc['isOwningSide']) { // Inverse side of x-to-one can never be lazy $class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity)); continue 2; } $associatedId = array(); // TODO: Is this even computed right in all cases of composite keys? foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) { $joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null; if ($joinColumnValue !== null) { if ($targetClass->containsForeignIdentifier) { $associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue; } else { $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue; } } } if ( ! $associatedId) { // Foreign key is NULL $class->reflFields[$field]->setValue($entity, null); $this->originalEntityData[$oid][$field] = null; continue; } if ( ! isset($hints['fetchMode'][$class->name][$field])) { $hints['fetchMode'][$class->name][$field] = $assoc['fetch']; } // Foreign key is set // Check identity map first // FIXME: Can break easily with composite keys if join column values are in // wrong order. The correct order is the one in ClassMetadata#identifier. $relatedIdHash = implode(' ', $associatedId); switch (true) { case (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])): $newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash]; // If this is an uninitialized proxy, we are deferring eager loads, // this association is marked as eager fetch, and its an uninitialized proxy (wtf!) // then we can append this entity for eager loading! if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER && isset($hints[self::HINT_DEFEREAGERLOAD]) && !$targetClass->isIdentifierComposite && $newValue instanceof Proxy && $newValue->__isInitialized__ === false) { $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); } break; case ($targetClass->subClasses): // If it might be a subtype, it can not be lazy. There isn't even // 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, $associatedId); break; default: switch (true) { // We are negating the condition here. Other cases will assume it is valid! case ($hints['fetchMode'][$class->name][$field] !== ClassMetadata::FETCH_EAGER): $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId); break; // Deferred eager load only works for single identifier classes case (isset($hints[self::HINT_DEFEREAGERLOAD]) && ! $targetClass->isIdentifierComposite): // TODO: Is there a faster approach? $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId); break; default: // TODO: This is very imperformant, ignore it? $newValue = $this->em->find($assoc['targetEntity'], $associatedId); break; } // PERF: Inlined & optimized code from UnitOfWork#registerManaged() $newValueOid = spl_object_hash($newValue); $this->entityIdentifiers[$newValueOid] = $associatedId; $this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue; if ( $newValue instanceof NotifyPropertyChanged && ( ! $newValue instanceof Proxy || $newValue->__isInitialized()) ) { $newValue->addPropertyChangedListener($this); } $this->entityStates[$newValueOid] = self::STATE_MANAGED; // make sure that when an proxy is then finally loaded, $this->originalEntityData is set also! break; } $this->originalEntityData[$oid][$field] = $newValue; $class->reflFields[$field]->setValue($entity, $newValue); if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) { $inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']]; $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity); } break; default: // Inject collection $pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection); $pColl->setOwner($entity, $assoc); $pColl->setInitialized(false); $reflField = $class->reflFields[$field]; $reflField->setValue($entity, $pColl); if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) { $this->loadCollection($pColl); $pColl->takeSnapshot(); } $this->originalEntityData[$oid][$field] = $pColl; break; } } if ($overrideLocalValues) { $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postLoad); if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } } return $entity; } /** * @return void */ public function triggerEagerLoads() { if ( ! $this->eagerLoadingEntities) { return; } // avoid infinite recursion $eagerLoadingEntities = $this->eagerLoadingEntities; $this->eagerLoadingEntities = array(); foreach ($eagerLoadingEntities as $entityName => $ids) { if ( ! $ids) { continue; } $class = $this->em->getClassMetadata($entityName); $this->getEntityPersister($entityName)->loadAll( array_combine($class->identifier, array(array_values($ids))) ); } } /** * Initializes (loads) an uninitialized persistent collection of an entity. * * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize. * * @return void * * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733. */ public function loadCollection(PersistentCollection $collection) { $assoc = $collection->getMapping(); $persister = $this->getEntityPersister($assoc['targetEntity']); switch ($assoc['type']) { case ClassMetadata::ONE_TO_MANY: $persister->loadOneToManyCollection($assoc, $collection->getOwner(), $collection); break; case ClassMetadata::MANY_TO_MANY: $persister->loadManyToManyCollection($assoc, $collection->getOwner(), $collection); break; } $collection->setInitialized(true); } /** * Gets the identity map of the UnitOfWork. * * @return array */ public function getIdentityMap() { return $this->identityMap; } /** * Gets the original data of an entity. The original data is the data that was * present at the time the entity was reconstituted from the database. * * @param object $entity * * @return array */ public function getOriginalEntityData($entity) { $oid = spl_object_hash($entity); if (isset($this->originalEntityData[$oid])) { return $this->originalEntityData[$oid]; } return array(); } /** * @ignore * * @param object $entity * @param array $data * * @return void */ public function setOriginalEntityData($entity, array $data) { $this->originalEntityData[spl_object_hash($entity)] = $data; } /** * INTERNAL: * Sets a property value of the original data array of an entity. * * @ignore * * @param string $oid * @param string $property * @param mixed $value * * @return void */ public function setOriginalEntityProperty($oid, $property, $value) { $this->originalEntityData[$oid][$property] = $value; } /** * Gets the identifier of an entity. * The returned value is always an array of identifier values. If the entity * has a composite identifier then the identifier values are in the same * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames(). * * @param object $entity * * @return array The identifier values. */ public function getEntityIdentifier($entity) { return $this->entityIdentifiers[spl_object_hash($entity)]; } /** * Processes an entity instance to extract their identifier values. * * @param object $entity The entity instance. * * @return mixed A scalar value. * * @throws \Doctrine\ORM\ORMInvalidArgumentException */ public function getSingleIdentifierValue($entity) { $class = $this->em->getClassMetadata(get_class($entity)); if ($class->isIdentifierComposite) { throw ORMInvalidArgumentException::invalidCompositeIdentifier(); } $values = $this->isInIdentityMap($entity) ? $this->getEntityIdentifier($entity) : $class->getIdentifierValues($entity); return isset($values[$class->identifier[0]]) ? $values[$class->identifier[0]] : null; } /** * Tries to find an entity with the given identifier in the identity map of * this UnitOfWork. * * @param mixed $id The entity identifier to look for. * @param string $rootClassName The name of the root class of the mapped entity hierarchy. * * @return object|bool Returns the entity with the specified identifier if it exists in * this UnitOfWork, FALSE otherwise. */ public function tryGetById($id, $rootClassName) { $idHash = implode(' ', (array) $id); if (isset($this->identityMap[$rootClassName][$idHash])) { return $this->identityMap[$rootClassName][$idHash]; } return false; } /** * Schedules an entity for dirty-checking at commit-time. * * @param object $entity The entity to schedule for dirty-checking. * * @return void * * @todo Rename: scheduleForSynchronization */ public function scheduleForDirtyCheck($entity) { $rootClassName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; $this->scheduledForDirtyCheck[$rootClassName][spl_object_hash($entity)] = $entity; } /** * Checks whether the UnitOfWork has any pending insertions. * * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise. */ public function hasPendingInsertions() { return ! empty($this->entityInsertions); } /** * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the * number of entities in the identity map. * * @return integer */ public function size() { $countArray = array_map(function ($item) { return count($item); }, $this->identityMap); return array_sum($countArray); } /** * Gets the EntityPersister for an Entity. * * @param string $entityName The name of the Entity. * * @return \Doctrine\ORM\Persisters\BasicEntityPersister */ public function getEntityPersister($entityName) { if (isset($this->persisters[$entityName])) { return $this->persisters[$entityName]; } $class = $this->em->getClassMetadata($entityName); switch (true) { case ($class->isInheritanceTypeNone()): $persister = new Persisters\BasicEntityPersister($this->em, $class); break; case ($class->isInheritanceTypeSingleTable()): $persister = new Persisters\SingleTablePersister($this->em, $class); break; case ($class->isInheritanceTypeJoined()): $persister = new Persisters\JoinedSubclassPersister($this->em, $class); break; default: $persister = new Persisters\UnionSubclassPersister($this->em, $class); } $this->persisters[$entityName] = $persister; return $this->persisters[$entityName]; } /** * Gets a collection persister for a collection-valued association. * * @param array $association * * @return \Doctrine\ORM\Persisters\AbstractCollectionPersister */ public function getCollectionPersister(array $association) { $type = $association['type']; if (isset($this->collectionPersisters[$type])) { return $this->collectionPersisters[$type]; } switch ($type) { case ClassMetadata::ONE_TO_MANY: $persister = new Persisters\OneToManyPersister($this->em); break; case ClassMetadata::MANY_TO_MANY: $persister = new Persisters\ManyToManyPersister($this->em); break; } $this->collectionPersisters[$type] = $persister; return $this->collectionPersisters[$type]; } /** * INTERNAL: * Registers an entity as managed. * * @param object $entity The entity. * @param array $id The identifier values. * @param array $data The original entity data. * * @return void */ public function registerManaged($entity, array $id, array $data) { $oid = spl_object_hash($entity); $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; $this->addToIdentityMap($entity); if ($entity instanceof NotifyPropertyChanged && ( ! $entity instanceof Proxy || $entity->__isInitialized())) { $entity->addPropertyChangedListener($this); } } /** * INTERNAL: * Clears the property changeset of the entity with the given OID. * * @param string $oid The entity's OID. * * @return void */ public function clearEntityChangeSet($oid) { $this->entityChangeSets[$oid] = array(); } /* PropertyChangedListener implementation */ /** * Notifies this UnitOfWork of a property change in an entity. * * @param object $entity The entity that owns the property. * @param string $propertyName The name of the property that changed. * @param mixed $oldValue The old value of the property. * @param mixed $newValue The new value of the property. * * @return void */ public function propertyChanged($entity, $propertyName, $oldValue, $newValue) { $oid = spl_object_hash($entity); $class = $this->em->getClassMetadata(get_class($entity)); $isAssocField = isset($class->associationMappings[$propertyName]); if ( ! $isAssocField && ! isset($class->fieldMappings[$propertyName])) { return; // ignore non-persistent fields } // Update changeset and mark entity for synchronization $this->entityChangeSets[$oid][$propertyName] = array($oldValue, $newValue); if ( ! isset($this->scheduledForDirtyCheck[$class->rootEntityName][$oid])) { $this->scheduleForDirtyCheck($entity); } } /** * Gets the currently scheduled entity insertions in this UnitOfWork. * * @return array */ public function getScheduledEntityInsertions() { return $this->entityInsertions; } /** * Gets the currently scheduled entity updates in this UnitOfWork. * * @return array */ public function getScheduledEntityUpdates() { return $this->entityUpdates; } /** * Gets the currently scheduled entity deletions in this UnitOfWork. * * @return array */ public function getScheduledEntityDeletions() { return $this->entityDeletions; } /** * Gets the currently scheduled complete collection deletions * * @return array */ public function getScheduledCollectionDeletions() { return $this->collectionDeletions; } /** * Gets the currently scheduled collection inserts, updates and deletes. * * @return array */ public function getScheduledCollectionUpdates() { return $this->collectionUpdates; } /** * Helper method to initialize a lazy loading proxy or persistent collection. * * @param object $obj * * @return void */ public function initializeObject($obj) { if ($obj instanceof Proxy) { $obj->__load(); return; } 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 update. * * @param object $object * * @return void * * @throws ORMInvalidArgumentException */ public function markReadOnly($object) { if ( ! is_object($object) || ! $this->isInIdentityMap($object)) { throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object); } $this->readOnlyObjects[spl_object_hash($object)] = true; } /** * Is this entity read only? * * @param object $object * * @return bool * * @throws ORMInvalidArgumentException */ public function isReadOnly($object) { if ( ! is_object($object)) { throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object); } return isset($this->readOnlyObjects[spl_object_hash($object)]); } private function dispatchOnFlushEvent() { if ($this->evm->hasListeners(Events::onFlush)) { $this->evm->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em)); } } private function dispatchPostFlushEvent() { if ($this->evm->hasListeners(Events::postFlush)) { $this->evm->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em)); } } /** * Verifies if two given entities actually are the same based on identifier comparison * * @param object $entity1 * @param object $entity2 * * @return bool */ private function isIdentifierEquals($entity1, $entity2) { if ($entity1 === $entity2) { return true; } $class = $this->em->getClassMetadata(get_class($entity1)); if ($class !== $this->em->getClassMetadata(get_class($entity2))) { return false; } $oid1 = spl_object_hash($entity1); $oid2 = spl_object_hash($entity2); $id1 = isset($this->entityIdentifiers[$oid1]) ? $this->entityIdentifiers[$oid1] : $this->flattenIdentifier($class, $class->getIdentifierValues($entity1)); $id2 = isset($this->entityIdentifiers[$oid2]) ? $this->entityIdentifiers[$oid2] : $this->flattenIdentifier($class, $class->getIdentifierValues($entity2)); return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2); } } doctrine2-2.4.8/lib/Doctrine/ORM/Version.php000066400000000000000000000036761257105210500205400ustar00rootroot00000000000000. */ namespace Doctrine\ORM; /** * Class to store and retrieve the version of Doctrine * * * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Version { /** * Current Doctrine Version */ const VERSION = '2.4.8'; /** * Compares a Doctrine version with the current one. * * @param string $version Doctrine version to compare. * * @return int Returns -1 if older, 0 if it is the same, 1 if version * passed as argument is newer. */ public static function compare($version) { $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); $version = str_replace(' ', '', $version); return version_compare($version, $currentVersion); } } doctrine2-2.4.8/lib/vendor/000077500000000000000000000000001257105210500154575ustar00rootroot00000000000000doctrine2-2.4.8/lib/vendor/doctrine-build-common/000077500000000000000000000000001257105210500216515ustar00rootroot00000000000000