1
0
mirror of synced 2025-03-14 00:26:08 +03:00

Merge additional fix (and master changes) from taueres/fix-instance-of-subclasses

This commit is contained in:
Alessandro Lai 2017-06-26 09:45:44 +02:00
commit c7ef9085fb
No known key found for this signature in database
GPG Key ID: 5D9C513BE4F5798D
175 changed files with 3293 additions and 1136 deletions

View File

@ -1,34 +1,24 @@
build:
environment:
php:
version: 7.1
before_commands:
- "composer install --no-dev --prefer-source"
tools:
external_code_coverage:
timeout: 3600
php_code_coverage:
enabled: true
php_code_sniffer:
enabled: false
php_cpd:
enabled: true
excluded_dirs: ["bin", "docs", "tests", "tools", "vendor"]
php_cs_fixer:
enabled: false
php_loc:
enabled: true
excluded_dirs: ["bin", "docs", "tests", "tools", "vendor"]
php_mess_detector:
enabled: true
filter:
paths: ["lib/*"]
php_pdepend:
enabled: true
excluded_dirs: ["docs", "examples", "tests", "vendor"]
php_analyzer:
enabled: true
filter:
paths: ["lib/*", "tests/*"]
php_hhvm:
enabled: true
filter:
paths: ["lib/*", "tests/*"]
sensiolabs_security_checker: true
filter:
excluded_paths:
- docs
- tools
build_failure_conditions:
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
- 'issues.label("coding-style").new.exists' # No new coding style issues allowed
- 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity
- 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection
- 'patches.label("Doc Comments").new.exists' # No new doc comments patches allowed
- 'patches.label("Unused Use Statements").new.exists' # No new unused imports patches allowed

View File

@ -1,11 +1,10 @@
dist: trusty
sudo: false
language: php
php:
- 7.0
- 7.1
- nightly
- hhvm
env:
- DB=mysql
@ -13,28 +12,25 @@ env:
- DB=sqlite
before_script:
- if [[ $TRAVIS_PHP_VERSION = '7.0' && $DB = 'sqlite' ]]; then PHPUNIT_FLAGS="--coverage-clover ./build/logs/clover.xml"; else PHPUNIT_FLAGS=""; fi
- if [[ $TRAVIS_PHP_VERSION -lt '7.0' && $TRAVIS_PHP_VERSION != 'hhv*' ]]; then phpenv config-rm xdebug.ini; fi
- if [[ $TRAVIS_PHP_VERSION = '7.1' && $DB = 'sqlite' && "$DEPENDENCIES" != "low" ]]; then PHPUNIT_FLAGS="--coverage-clover ./build/logs/clover.xml"; else PHPUNIT_FLAGS=""; fi
- if [[ "$PHPUNIT_FLAGS" == "" ]]; then phpenv config-rm xdebug.ini; fi
- composer self-update
- composer install --prefer-source
- if [ "$DEPENDENCIES" != "low" ]; then composer update; fi;
- if [ "$DEPENDENCIES" == "low" ]; then composer update --prefer-lowest; fi;
- if [[ $DB == "mysql" || $DB == "mariadb" ]]; then mysql -e "CREATE SCHEMA doctrine_tests; GRANT ALL PRIVILEGES ON doctrine_tests.* to travis@'%'"; fi;
script:
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml $PHPUNIT_FLAGS
- ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional
after_script:
- if [[ $TRAVIS_PHP_VERSION = '7.0' && $DB = 'sqlite' ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi
- if [[ $TRAVIS_PHP_VERSION = '7.0' && $DB = 'sqlite' ]]; then php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml; fi
- if [[ "$PHPUNIT_FLAGS" != "" ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi
- if [[ "$PHPUNIT_FLAGS" != "" ]]; then php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml; fi
matrix:
fast_finish: true
include:
- php: 7.0
env: DB=mariadb
addons:
mariadb: 10.1
- php: 7.1
env: DB=mariadb
addons:
@ -43,42 +39,9 @@ matrix:
env:
- DB=sqlite
- DEPENDENCIES='low'
- php: hhvm
sudo: true
dist: trusty
group: edge # until the next Trusty update
addons:
mariadb: 10.1
env: DB=mariadb
- php: hhvm
sudo: true
dist: trusty
group: edge # until the next update
addons:
apt:
packages:
- mysql-server-5.6
- mysql-client-core-5.6
- mysql-client-5.6
services:
- mysql
env: DB=mysql
- php: hhvm
sudo: true
dist: trusty
group: edge # until the next update
services:
- postgresql
env: DB=pgsql
- php: hhvm
sudo: true
dist: trusty
group: edge # until the next update
env: DB=sqlite
allow_failures:
- php: nightly
- php: hhvm
cache:
directories:

View File

@ -1,3 +1,22 @@
# Upgrade to 2.6
## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction`
Method `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported`
now has a required parameter `$pathExpr`.
## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction`
Method `Doctrine\ORM\Query\Parser#isInternalFunction` was removed because
the distinction between internal function and user defined DQL was removed.
[#6500](https://github.com/doctrine/doctrine2/pull/6500)
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed`
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed` was
removed because of the choice to allow users to overwrite internal functions, ie
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/doctrine2/pull/6500)
# Upgrade to 2.5
## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()`

View File

@ -14,18 +14,18 @@
],
"minimum-stability": "dev",
"require": {
"php": "^7.0",
"php": "^7.1",
"ext-pdo": "*",
"doctrine/collections": "~1.3",
"doctrine/collections": "^1.4",
"doctrine/dbal": ">=2.5-dev,<2.7-dev",
"doctrine/instantiator": "~1.0.1",
"doctrine/common": "^2.7.1",
"doctrine/cache": "~1.5",
"doctrine/annotations": "~1.2",
"symfony/console": "~2.5|~3.0"
"doctrine/cache": "~1.6",
"doctrine/annotations": "~1.4",
"symfony/console": "~3.0|~4.0"
},
"require-dev": {
"symfony/yaml": "~2.3|~3.0",
"symfony/yaml": "~3.0|~4.0",
"phpunit/phpunit": "^6.0"
},
"suggest": {

@ -1 +1 @@
Subproject commit dc294be1dbcf9abde82d24aea1902898d6ee6314
Subproject commit 6f1bc8bead17b8032389659c0b071d00f2c58328

View File

@ -132,7 +132,7 @@ dql statement.
The ``ArithmeticPrimary`` method call is the most common
denominator of valid EBNF tokens taken from the
`DQL EBNF grammar <http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf>`_
`DQL EBNF grammar <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#ebnf>`_
that matches our requirements for valid input into the DateDiff Dql
function. Picking the right tokens for your methods is a tricky
business, but the EBNF grammar is pretty helpful finding it, as is

View File

@ -4,7 +4,7 @@ Implementing Wakeup or Clone
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
`restrictions for entity classes in the manual <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/architecture.html#entities>`_,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe

View File

@ -111,7 +111,7 @@ APC, get rid of EchoSqlLogger, and turn off
autoGenerateProxyClasses.
For more details, consult the
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
`Doctrine 2 Configuration documentation <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html>`_.
Now to use it
-------------

View File

@ -24,6 +24,9 @@ One tip for working with relations is to read the relation from left to right, w
See below for all the possible relations.
An association is considered to be unidirectional if only one side of the association has
a property referring to the other side.
To gain a full understanding of associations you should also read about :doc:`owning and
inverse sides of associations <unitofwork-associations>`
@ -105,9 +108,7 @@ One-To-One, Unidirectional
--------------------------
Here is an example of a one-to-one association with a ``Product`` entity that
references one ``Shipping`` entity. The ``Shipping`` does not reference back to
the ``Product`` so that the reference is said to be unidirectional, in one
direction only.
references one ``Shipment`` entity.
.. configuration-block::
@ -120,17 +121,17 @@ direction only.
// ...
/**
* One Product has One Shipping.
* @OneToOne(targetEntity="Shipping")
* @JoinColumn(name="shipping_id", referencedColumnName="id")
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipping;
private $shipment;
// ...
}
/** @Entity */
class Shipping
class Shipment
{
// ...
}
@ -139,8 +140,8 @@ direction only.
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipping" target-entity="Shipping">
<join-column name="shipping_id" referenced-column-name="id" />
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" />
</one-to-one>
</entity>
</doctrine-mapping>
@ -150,10 +151,10 @@ direction only.
Product:
type: entity
oneToOne:
shipping:
targetEntity: Shipping
shipment:
targetEntity: Shipment
joinColumn:
name: shipping_id
name: shipment_id
referencedColumnName: id
Note that the @JoinColumn is not really necessary in this example,
@ -165,15 +166,15 @@ Generated MySQL Schema:
CREATE TABLE Product (
id INT AUTO_INCREMENT NOT NULL,
shipping_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipping_id),
shipment_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipment_id),
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Shipping (
CREATE TABLE Shipment (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id);
ALTER TABLE Product ADD FOREIGN KEY (shipment_id) REFERENCES Shipment(id);
One-To-One, Bidirectional
-------------------------
@ -182,6 +183,10 @@ 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.
Here we see the ``mappedBy`` and ``inversedBy`` annotations for the first time.
They are used to tell Doctrine which property on the other side refers to the
object.
.. configuration-block::
.. code-block:: php
@ -263,8 +268,9 @@ Generated MySQL Schema:
) 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``.
We had a choice of sides on which to place the ``mappedBy`` attribute. Because it
is on the ``Cart``, that is the owning side of the relation, and thus holds the
foreign key.
One-To-One, Self-referencing
----------------------------
@ -307,15 +313,16 @@ With the generated MySQL Schema:
One-To-Many, Bidirectional
--------------------------
A one-to-many association has to be bidirectional, unless you are using an
additional join-table. This is necessary, because of the foreign key
in a one-to-many association being defined on the "many" side. Doctrine
needs a many-to-one association that defines the mapping of this
foreign key.
A one-to-many association has to be bidirectional, unless you are using a
join table. This is because the many side in a one-to-many association holds
the foreign key, making it the owning side. Doctrine needs the many side
defined in order to understand the association.
This bidirectional mapping requires the ``mappedBy`` attribute on the
``OneToMany`` association and the ``inversedBy`` attribute on the ``ManyToOne``
association.
"one" side and the ``inversedBy`` attribute on the "many" side.
This means there is no difference between a bidirectional one-to-many and a
bidirectional many-to-one.
.. configuration-block::
@ -775,14 +782,14 @@ one is bidirectional.
The MySQL schema is exactly the same as for the Many-To-Many
uni-directional case above.
Owning and Inverse Side on a ManyToMany association
Owning and Inverse Side on a ManyToMany Association
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For Many-To-Many associations you can chose which entity is the
owning and which the inverse side. There is a very simple semantic
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
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
@ -790,7 +797,7 @@ 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
and allow specifying the tags directly. This is why you should pick
the Article as owning side, as it makes the code more
understandable:
@ -906,14 +913,14 @@ As an example, consider this mapping:
.. code-block:: php
<?php
/** @OneToOne(targetEntity="Shipping") */
private $shipping;
/** @OneToOne(targetEntity="Shipment") */
private $shipment;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipping" target-entity="Shipping" />
<one-to-one field="shipment" target-entity="Shipment" />
</entity>
</doctrine-mapping>
@ -922,8 +929,8 @@ As an example, consider this mapping:
Product:
type: entity
oneToOne:
shipping:
targetEntity: Shipping
shipment:
targetEntity: Shipment
This is essentially the same as the following, more verbose,
mapping:
@ -934,18 +941,18 @@ mapping:
<?php
/**
* One Product has One Shipping.
* @OneToOne(targetEntity="Shipping")
* @JoinColumn(name="shipping_id", referencedColumnName="id")
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipping;
private $shipment;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipping" target-entity="Shipping">
<join-column name="shipping_id" referenced-column-name="id" />
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" />
</one-to-one>
</entity>
</doctrine-mapping>
@ -955,10 +962,10 @@ mapping:
Product:
type: entity
oneToOne:
shipping:
targetEntity: Shipping
shipment:
targetEntity: Shipment
joinColumn:
name: shipping_id
name: shipment_id
referencedColumnName: id
The @JoinTable definition used for many-to-many mappings has

View File

@ -1380,7 +1380,13 @@ Given that there are 10 users and corresponding addresses in the database the ex
SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
.. note::
Changing the fetch mode during a query is only possible for one-to-one and many-to-one relations.
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
  all the necessary IDs are available after the root entity (``user`` in the above example) has been loaded. So, one
  query per association can be executed to fetch all the referred-to entities (``address``).
For one-to-many relations, changing the fetch mode to eager will cause to execute one query **for every root entity
  loaded**. This gives no improvement over the ``lazy`` fetch mode which will also initialize the associations on
a one-by-one basis once they are accessed.
EBNF

View File

@ -455,6 +455,7 @@ Things to note:
- The association type *CANNOT* be changed.
- The override could redefine the joinTables or joinColumns depending on the association type.
- The override could redefine inversedBy to reference more than one extended entity.
- The override could redefine fetch to modify the fetch strategy of the extended entity.
Attribute Override
~~~~~~~~~~~~~~~~~~~~

View File

@ -20,7 +20,7 @@ You can specify a different strategy by calling ``Doctrine\ORM\Configuration#set
<?php
$namingStrategy = new MyNamingStrategy();
$configuration()->setNamingStrategy($namingStrategy);
$configuration->setNamingStrategy($namingStrategy);
Underscore naming strategy
---------------------------
@ -31,7 +31,7 @@ Underscore naming strategy
<?php
$namingStrategy = new \Doctrine\ORM\Mapping\UnderscoreNamingStrategy(CASE_UPPER);
$configuration()->setNamingStrategy($namingStrategy);
$configuration->setNamingStrategy($namingStrategy);
For SomeEntityName the strategy will generate the table SOME_ENTITY_NAME with the
``CASE_UPPER`` option, or some_entity_name with the ``CASE_LOWER`` option.

View File

@ -126,7 +126,7 @@ Here is a complete list of helper methods available in ``QueryBuilder``:
// Example - $qb->select(array('u', 'p'))
// Example - $qb->select($qb->expr()->select('u', 'p'))
public function select($select = null);
// addSelect does not override previous calls to select
//
// Example - $qb->select('u');
@ -580,4 +580,3 @@ same query of example 6 written using
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.

View File

@ -97,7 +97,7 @@ Defines a contract for accessing a particular region.
Defines a contract for accessing a particular cache region.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.Region.html/>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.Region.html>`_.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
@ -111,7 +111,7 @@ If you want to use an ``READ_WRITE`` cache, you should consider providing your o
Defines contract for concurrently managed data region.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html/>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html>`_.
Timestamp region
~~~~~~~~~~~~~~~~
@ -120,7 +120,7 @@ Timestamp region
Tracks the timestamps of the most recent updates to particular entity.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html/>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html>`_.
.. _reference-second-level-cache-mode:
@ -209,7 +209,7 @@ It allows you to provide a specific implementation of the following components :
* ``EntityHydrator`` Transform an entity into a cache entry and cache entry into entities
* ``CollectionHydrator`` Transform a collection into a cache entry and cache entry into collection
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html/>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html>`_.
Region Lifetime
~~~~~~~~~~~~~~~
@ -270,7 +270,7 @@ By providing a cache logger you should be able to get information about all cach
If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``.
and collect all information you want.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.CacheLogger.html/>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.CacheLogger.html>`_.
Entity cache definition

View File

@ -205,7 +205,7 @@ tables of the current model to clean up with orphaned tables.
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.
``ClassMetadataInfo`` instances.
.. code-block:: php

View File

@ -214,8 +214,8 @@ example we'll use an integer.
type: entity
fields:
version:
version:
type: integer
type: integer
version: true
Alternatively a datetime type can be used (which maps to a SQL
timestamp or datetime):
@ -247,8 +247,8 @@ timestamp or datetime):
type: entity
fields:
version:
version:
type: datetime
type: datetime
version: true
Version numbers (not timestamps) should however be preferred as
they can not potentially conflict in a highly concurrent

View File

@ -716,6 +716,8 @@ methods:
* ``in($field, array $values)``
* ``notIn($field, array $values)``
* ``contains($field, $value)``
* ``startsWith($field, $value)``
* ``endsWith($field, $value)``
.. note::

View File

@ -14,7 +14,7 @@ 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
before. There are some prerequisites for the tutorial that have to be
installed:
- PHP (latest stable version)
@ -118,8 +118,8 @@ Add the following directories:
Obtaining the EntityManager
---------------------------
Doctrine's public interface is the EntityManager, it provides the
access point to the complete lifecycle management of your entities
Doctrine's public interface is through the ``EntityManager``. This class
provides access points to the complete lifecycle management for 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
@ -150,8 +150,8 @@ step:
// 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 require_once statement sets up the class autoloading for Doctrine and
its dependencies using Composer's autoloader.
The second block consists of the instantiation of the ORM
``Configuration`` object using the Setup helper. It assumes a bunch
@ -159,10 +159,10 @@ 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
The third block shows the configuration options required to connect to
a database. In this case, we'll use a file-based SQLite database. All the
configuration options for all the shipped drivers are given in the
`DBAL Configuration section of the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/dbal>`_.
`DBAL Configuration section of the manual <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/>`_.
The last block shows how the ``EntityManager`` is obtained from a
factory method.
@ -170,15 +170,10 @@ 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:
Doctrine has a command-line interface that allows you to access the SchemaTool,
a component that can generate a relational database schema based entirely on the
defined entity classes and their metadata. For this tool to work, a
cli-config.php file must exist in the project root directory:
.. code-block:: php
@ -188,40 +183,38 @@ doctrine command. Its a fairly simple file:
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
You can then change into your project directory and call the
Doctrine command-line tool:
Change into your project directory and call the Doctrine command-line tool:
::
$ cd project/
$ vendor/bin/doctrine orm:schema-tool:create
At this point no entity metadata exists in `src` so you will see a message like
"No Metadata Classes to process." Don't worry, we'll create a Product entity and
corresponding metadata in the next section.
Since we haven't added any entity metadata in `src` yet, you'll see a message
stating "No Metadata Classes to process." In the next section, we'll create a
Product entity along with the corresponding metadata, and run this command again.
You should be aware that during the development process you'll periodically need
to update your database schema to be in sync with your Entities metadata.
You can easily recreate the database:
Note that as you modify your entities' metadata during the development process,
you'll need to update your database schema to stay in sync with the metadata.
You can rasily recreate the database using the following commands:
::
$ vendor/bin/doctrine orm:schema-tool:drop --force
$ vendor/bin/doctrine orm:schema-tool:create
Or use the update functionality:
Or you can use the update functionality:
::
$ vendor/bin/doctrine orm:schema-tool:update --force
The updating of databases uses a Diff Algorithm for a given
Database Schema, a cornerstone of the ``Doctrine\DBAL`` package,
Database Schema. This is a cornerstone of the ``Doctrine\DBAL`` package,
which can even be used without the Doctrine ORM package.
Starting with the Product
-------------------------
Starting with the Product Entity
--------------------------------
We start with the simplest entity, the Product. Create a ``src/Product.php`` file to contain the ``Product``
entity definition:
@ -257,9 +250,9 @@ entity definition:
}
}
Note that all fields are set to protected (not public) with a
mutator (getter and setter) defined for every field except $id.
The use of mutators allows Doctrine to hook into calls which
When creating entity classes, all of the fields should be protected or private
(not public), with getter and setter methods for each one (except $id).
The use of mutators allows Doctrine to hook into calls which
manipulate the entities in ways that it could not if you just
directly set the values with ``entity#field = foo;``
@ -274,9 +267,10 @@ 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.
Metadata for an Entity can be configured using DocBlock annotations directly
in the Entity class itself, or in an external XML or YAML file. This Getting
Started guide will demonstrate metadata mappings using all three methods,
but you only need to choose one.
.. configuration-block::
@ -332,27 +326,28 @@ References in the text will be made to the XML mapping.
The top-level ``entity`` definition tag specifies information about
the class and table-name. The primitive type ``Product#name`` is
defined as a ``field`` attribute. The ``id`` property is defined with
the ``id`` tag, this 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
the ``id`` tag. It has a ``generator`` tag nested inside, which
specifies that the primary key generation mechanism should automatically
use the database platform's native id generation strategy (for
example, AUTO INCREMENT in the case of MySql, or Sequences in the
case of PostgreSql and Oracle).
Now that we have defined our first entity, let's update the database:
Now that we have defined our first entity and its metadata,
let's update the database schema:
::
$ vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
Specifying both flags ``--force`` and ``--dump-sql`` prints and executes the DDL
statements.
Specifying both flags ``--force`` and ``--dump-sql`` will cause the DDL
statements to be executed and then printed to the screen.
Now create a new script that will insert products into the database:
Now, we'll create a new script to insert products into the database:
.. code-block:: php
<?php
// create_product.php
// create_product.php <name>
require_once "bootstrap.php";
$newProductName = $argv[1];
@ -372,22 +367,19 @@ 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.
What is happening here? Using the ``Product`` class 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 initiate a transaction to actually perform
the insertion, You have to explicitly call ``flush()`` on the ``EntityManager``.
notify the EntityManager that a new entity should be inserted into the database,
you have to call ``persist()``. To initiate 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.
This distinction between persist and flush is what allows the aggregation of
all database 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 in which writes are performed on
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 knows about them.
As a next step we want to fetch a list of all the Products. Let's create a
Next, we'll fetch a list of all the Products in the database. Let's create a
new script for this:
.. code-block:: php
@ -404,10 +396,10 @@ new script for this:
}
The ``EntityManager#getRepository()`` method can create a finder object (called
a repository) for every entity. It is provided by Doctrine and contains some
finder methods such as ``findAll()``.
a repository) for every type of entity. It is provided by Doctrine and contains
some finder methods like ``findAll()``.
Let's continue with displaying the name of a product based on its ID:
Let's continue by creating a script to display the name of a product based on its ID:
.. code-block:: php
@ -425,9 +417,13 @@ Let's continue with displaying the name of a product based on its ID:
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:
Next we'll update a product's name, given its id. This simple example will
help demonstrate Doctrine's implementation of the UnitOfWork pattern. Doctrine
keeps track of all the entities that were retrieved from the Entity Manager,
and can detect when any of those entities' properties have been modified.
As a result, rather than needing to call ``persist($entity)`` for each individual
entity whose properties were changed, a single call to ``flush()`` at the end of a
request is sufficient to update the database for all of the modified entities.
.. code-block:: php
@ -455,9 +451,8 @@ 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.
We continue with the bug tracker example by creating the ``Bug`` and ``User``
classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
.. code-block:: php
@ -561,14 +556,15 @@ We continue with the bug tracker domain, by creating the missing classes
}
}
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. Next we will model the dynamic relationships between the entities
by defining the references between entities.
All of the properties we've seen so far are of simple types (integer, string,
and datetime). But now, we'll add properties that will store objects of
specific *entity types* in order to model the relationships between different
entities.
References between objects are foreign keys in the database. You never have to
(and never should) work with the foreign keys directly, only with the objects
that represent the foreign key through their own identity.
At the database level, relationships between entities are represented by foreign
keys. But with Doctrine, you'll never have to (and never should) work with
the foreign keys directly. You should only work with objects that represent
foreign keys through their own identities.
For every foreign key you either have a Doctrine ManyToOne or OneToOne
association. On the inverse sides of these foreign keys you can have
@ -602,6 +598,7 @@ domain model to match the requirements:
<?php
// src/User.php
use Doctrine\Common\Collections\ArrayCollection;
class User
{
// ... (previous code)
@ -616,12 +613,13 @@ domain model to match the requirements:
}
}
You use Doctrine's ArrayCollections in your Doctrine models, rather
than plain PHP arrays, so that Doctrine can watch what happens with
them and act appropriately. Note that if you dump your entities,
you'll see a "PersistentCollection" in place of your ArrayCollection,
which is just an
internal Doctrine class with the same interface.
.. note::
Whenever an entity is created from the database, a ``Collection``
implementation of the type ``PersistentCollection`` will be injected into
your entity instead of an ``ArrayCollection``. This helps Doctrine ORM
understand the changes that have happened to the collection that are
noteworthy for persistence.
.. warning::
@ -644,24 +642,22 @@ 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.
- 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-one relation, the Many-side is the owning side by
default because it holds the foreign key. Accordingly, the One-side
is the inverse side by default.
- In a many-to-one relation, the One-side can only be the owning side if
the relation is implemented as a ManyToMany with a join table, and the
One-side is restricted to allow only UNIQUE values per database constraint.
- 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 side is allowed to be the owning side.
- 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::
@ -686,13 +682,13 @@ the bi-directional reference:
protected $engineer;
protected $reporter;
public function setEngineer($engineer)
public function setEngineer(User $engineer)
{
$engineer->assignedToBug($this);
$this->engineer = $engineer;
}
public function setReporter($reporter)
public function setReporter(User $reporter)
{
$reporter->addReportedBug($this);
$this->reporter = $reporter;
@ -717,15 +713,15 @@ the bi-directional reference:
{
// ... (previous code)
protected $reportedBugs = null;
protected $assignedBugs = null;
protected $reportedBugs;
protected $assignedBugs;
public function addReportedBug($bug)
public function addReportedBug(Bug $bug)
{
$this->reportedBugs[] = $bug;
}
public function assignedToBug($bug)
public function assignedToBug(Bug $bug)
{
$this->assignedBugs[] = $bug;
}
@ -741,7 +737,7 @@ 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
calling Doctrine for persistence would not update the Collections'
representation in the database.
Only using ``Bug#setEngineer()`` or ``Bug#setReporter()``
@ -749,7 +745,7 @@ correctly saves the relation information.
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
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
@ -758,7 +754,7 @@ 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
Bugs reference Products by a uni-directional ManyToMany relation in
the database that points from Bugs to Products.
.. code-block:: php
@ -771,7 +767,7 @@ the database that points from Bugs to Products.
protected $products = null;
public function assignToProduct($product)
public function assignToProduct(Product $product)
{
$this->products[] = $product;
}
@ -783,7 +779,7 @@ the database that points from Bugs to Products.
}
We are now finished with the domain model given the requirements.
Lets add metadata mappings for the ``User`` and ``Bug`` as we did for
Lets add metadata mappings for the ``Bug`` entity, as we did for
the ``Product`` before:
.. configuration-block::
@ -890,13 +886,13 @@ For the "created" field we have used the ``datetime`` type,
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
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``
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
@ -907,7 +903,7 @@ holds all products where the specific bug occurs. Again
you have to define the ``target-entity`` and ``field`` attributes
on the ``many-to-many`` tag.
The last missing definition is that of the User entity:
Finally, we'll add metadata mappings for the ``User`` entity.
.. configuration-block::
@ -934,13 +930,13 @@ The last missing definition is that of the User entity:
/**
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
* @var Bug[]
* @var Bug[] An ArrayCollection of Bug objects.
**/
protected $reportedBugs = null;
/**
* @OneToMany(targetEntity="Bug", mappedBy="engineer")
* @var Bug[]
* @var Bug[] An ArrayCollection of Bug objects.
**/
protected $assignedBugs = null;
@ -996,10 +992,7 @@ 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.
Update your database running:
Update your database schema by running:
::
$ vendor/bin/doctrine orm:schema-tool:update --force
@ -1008,7 +1001,8 @@ Update your database running:
Implementing more Requirements
------------------------------
For starters we need to create user entities:
So far, we've seen the most basic features of the metadata definition language.
To explore additional functionality, let's first create new ``User`` entities:
.. code-block:: php
@ -1032,23 +1026,22 @@ 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:
We now have the necessary data to create a new Bug entity:
.. code-block:: php
<?php
// create_bug.php
// create_bug.php <reporter-id> <engineer-id> <product-ids>
require_once "bootstrap.php";
$theReporterId = $argv[1];
$theDefaultEngineerId = $argv[2];
$reporterId = $argv[1];
$engineerId = $argv[2];
$productIds = explode(",", $argv[3]);
$reporter = $entityManager->find("User", $theReporterId);
$engineer = $entityManager->find("User", $theDefaultEngineerId);
$reporter = $entityManager->find("User", $reporterId);
$engineer = $entityManager->find("User", $engineerId);
if (!$reporter || !$engineer) {
echo "No reporter and/or engineer found for the input.\n";
echo "No reporter and/or engineer found for the given id(s).\n";
exit(1);
}
@ -1070,22 +1063,17 @@ like this:
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:
Since we only have one user and product, probably with the ID of 1, we can
call this script as follows:
::
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 relationship when flush is
called and relate them in the database appropriately.
See how simple it is to relate a Bug, Reporter, Engineer and Products?
Also recall that thanks to the UnitOfWork pattern, Doctrine will detect
these relations and update all of the modified entities in the database
automatically when ``flush()`` is called.
Queries for Application Use-Cases
---------------------------------
@ -1094,7 +1082,7 @@ 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
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:
@ -1225,7 +1213,7 @@ write scenarios:
.. code-block:: php
<?php
// show_bug.php
// show_bug.php <id>
require_once "bootstrap.php";
$theBugId = $argv[1];
@ -1300,7 +1288,7 @@ and usage of bound parameters:
.. code-block:: php
<?php
// dashboard.php
// dashboard.php <user-id>
require_once "bootstrap.php";
$theUserId = $argv[1];
@ -1366,7 +1354,7 @@ should be able to close a bug. This looks like:
.. code-block:: php
<?php
// close_bug.php
// close_bug.php <bug-id>
require_once "bootstrap.php";
$theBugId = $argv[1];

View File

@ -167,7 +167,7 @@
</xs:complexType>
<xs:complexType name="entity">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:element name="indexes" type="orm:indexes" minOccurs="0"/>
@ -189,9 +189,9 @@
<xs:element name="association-overrides" type="orm:association-overrides" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="attribute-overrides" type="orm:attribute-overrides" minOccurs="0" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:choice>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="table" type="xs:NMTOKEN" />
<xs:attribute name="table" type="orm:tablename" />
<xs:attribute name="schema" type="xs:NMTOKEN" />
<xs:attribute name="repository-class" type="xs:string"/>
<xs:attribute name="inheritance-type" type="orm:inheritance-type"/>
@ -200,6 +200,13 @@
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:simpleType name="tablename" id="tablename">
<xs:restriction base="xs:token">
<xs:pattern value="[a-zA-Z_u01-uff.]+" id="tablename.pattern">
</xs:pattern>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="option" mixed="true">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="option" type="orm:option"/>
@ -412,9 +419,16 @@
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="class" type="xs:string" use="required" />
<xs:attribute name="class" type="orm:fqcn" use="required" />
</xs:complexType>
<xs:simpleType name="fqcn" id="fqcn">
<xs:restriction base="xs:token">
<xs:pattern value="[a-zA-Z_u01-uff][a-zA-Z0-9_u01-uff]+" id="fqcn.pattern">
</xs:pattern>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="inverse-join-columns">
<xs:sequence>
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
@ -567,6 +581,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="fetch" type="orm:fetch-type" use="optional" />
</xs:complexType>
<xs:complexType name="inversed-by-override">

View File

@ -27,9 +27,6 @@ use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query\ResultSetMapping;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
*

View File

@ -30,7 +30,6 @@ use Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister;
use Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\Region\DefaultMultiGetRegion;
use Doctrine\ORM\Cache\Region\DefaultRegion;
use Doctrine\ORM\Cache\Region\FileLockRegion;

View File

@ -122,11 +122,9 @@ class DefaultQueryCache implements QueryCache
// @TODO - move to cache hydration component
foreach ($entry->result as $index => $entry) {
$entityEntry = is_array($entries) && array_key_exists($index, $entries) ? $entries[$index] : null;
if ($entityEntry === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
}
@ -139,7 +137,6 @@ class DefaultQueryCache implements QueryCache
}
if ( ! $hasRelation) {
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
continue;
@ -178,14 +175,20 @@ class DefaultQueryCache implements QueryCache
continue;
}
$collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
$generateKeys = function ($id) use ($assocMetadata): EntityCacheKey {
return new EntityCacheKey($assocMetadata->rootEntityName, $id);
};
$collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
$assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
$assocEntries = $assocRegion->getMultiple($assocKeys);
foreach ($assoc['list'] as $assocIndex => $assocId) {
$assocEntry = is_array($assocEntries) && array_key_exists($assocIndex, $assocEntries) ? $assocEntries[$assocIndex] : null;
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId))) === null) {
if ($assocEntry === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
}
$this->uow->hydrationComplete();
@ -198,7 +201,7 @@ class DefaultQueryCache implements QueryCache
$collection->hydrateSet($assocIndex, $element);
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
}
}

View File

@ -21,7 +21,6 @@
namespace Doctrine\ORM\Cache\Region;
use Doctrine\Common\Cache\MultiGetCache;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\CollectionCacheEntry;
/**

View File

@ -420,15 +420,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string|callable $className Class name or a callable that returns the function.
*
* @return void
*
* @throws ORMException
*/
public function addCustomStringFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customStringFunctions'][strtolower($name)] = $className;
}
@ -478,15 +472,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string|callable $className Class name or a callable that returns the function.
*
* @return void
*
* @throws ORMException
*/
public function addCustomNumericFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customNumericFunctions'][strtolower($name)] = $className;
}
@ -536,15 +524,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string|callable $className Class name or a callable that returns the function.
*
* @return void
*
* @throws ORMException
*/
public function addCustomDatetimeFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className;
}

View File

@ -19,7 +19,6 @@
namespace Doctrine\ORM;
use Doctrine\ORM\Mapping\MappingException;
use Exception;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;

View File

@ -76,7 +76,8 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
$conn = $em->getConnection();
$sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName);
$this->_nextValue = (int) $conn->fetchColumn($sql);
// Using `query` to force usage of the master server in MasterSlaveConnection
$this->_nextValue = (int) $conn->query($sql)->fetchColumn();
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
}

View File

@ -211,6 +211,11 @@ abstract class AbstractHydrator
$this->_rsm = null;
$this->_cache = [];
$this->_metadataCache = [];
$this
->_em
->getEventManager()
->removeEventListener([Events::onClear], $this);
}
/**

View File

@ -358,8 +358,8 @@ class ObjectHydrator extends AbstractHydrator
// 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)];
$objectClass = $this->resultPointers[$parentAlias];
$parentObject = $objectClass[key($objectClass)];
} else if (isset($this->resultPointers[$parentAlias])) {
$parentObject = $this->resultPointers[$parentAlias];
} else {
@ -433,7 +433,7 @@ class ObjectHydrator extends AbstractHydrator
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.
// we refresh the entity or its an uninitialized proxy.
if (isset($nonemptyComponents[$dqlAlias])) {
$element = $this->getEntity($data, $dqlAlias);
$reflField->setValue($parentObject, $element);

View File

@ -57,4 +57,13 @@ final class AssociationOverride implements Annotation
* @var string
*/
public $inversedBy;
/**
* The fetching strategy to use for the association.
*
* @var string
*
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
*/
public $fetch;
}

View File

@ -1376,7 +1376,7 @@ class ClassMetadataInfo implements ClassMetadata
*
* @param array $mapping The field mapping to validate & complete.
*
* @return array The validated and completed field mapping.
* @return void
*
* @throws MappingException
*/
@ -1547,11 +1547,11 @@ class ClassMetadataInfo implements ClassMetadata
}
$mapping['cascade'] = $cascades;
$mapping['isCascadeRemove'] = in_array('remove', $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);
$mapping['isCascadeMerge'] = in_array('merge', $cascades);
$mapping['isCascadeDetach'] = in_array('detach', $cascades);
return $mapping;
}
@ -1796,7 +1796,7 @@ class ClassMetadataInfo implements ClassMetadata
*
* @return string
*
* @throws MappingException If the class has a composite primary key.
* @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
*/
public function getSingleIdentifierFieldName()
{
@ -1804,6 +1804,10 @@ class ClassMetadataInfo implements ClassMetadata
throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name);
}
if ( ! isset($this->identifier[0])) {
throw MappingException::noIdDefined($this->name);
}
return $this->identifier[0];
}
@ -1813,7 +1817,7 @@ class ClassMetadataInfo implements ClassMetadata
*
* @return string
*
* @throws MappingException If the class has a composite primary key.
* @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
*/
public function getSingleIdentifierColumnName()
{
@ -1848,7 +1852,7 @@ class ClassMetadataInfo implements ClassMetadata
*/
public function hasField($fieldName)
{
return isset($this->fieldMappings[$fieldName]);
return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
}
/**
@ -2030,7 +2034,7 @@ class ClassMetadataInfo implements ClassMetadata
*
* @return \Doctrine\DBAL\Types\Type|string|null
*
* @deprecated 3.0 remove this. this method is bogous and unreliable, since it cannot resolve the type of a column
* @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column
* that is derived by a referenced field on a different entity.
*/
public function getTypeOfColumn($columnName)
@ -2149,6 +2153,10 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['joinTable'] = $overrideMapping['joinTable'];
}
if (isset($overrideMapping['fetch'])) {
$mapping['fetch'] = $overrideMapping['fetch'];
}
$mapping['joinColumnFieldNames'] = null;
$mapping['joinTableColumns'] = null;
$mapping['sourceToTargetKeyColumns'] = null;
@ -2299,6 +2307,10 @@ class ClassMetadataInfo implements ClassMetadata
$this->table['name'] = $table['name'];
}
if (isset($table['quoted'])) {
$this->table['quoted'] = $table['quoted'];
}
if (isset($table['schema'])) {
$this->table['schema'] = $table['schema'];
}

View File

@ -470,6 +470,11 @@ class AnnotationDriver extends AbstractAnnotationDriver
$override['inversedBy'] = $associationOverride->inversedBy;
}
// Check for `fetch`
if ($associationOverride->fetch) {
$override['fetch'] = constant(Mapping\ClassMetadata::class . '::FETCH_' . $associationOverride->fetch);
}
$metadata->setAssociationOverride($fieldName, $override);
}
}

View File

@ -393,7 +393,7 @@ class DatabaseDriver implements MappingDriver
'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false),
'columnName' => $column->getName(),
'type' => $column->getType()->getName(),
'nullable' => ( ! $column->getNotNull()),
'nullable' => ( ! $column->getNotnull()),
];
// Type specific elements
@ -482,7 +482,7 @@ class DatabaseDriver implements MappingDriver
}
/**
* Retreive schema table definition foreign keys.
* Retrieve schema table definition foreign keys.
*
* @param \Doctrine\DBAL\Schema\Table $table
*

View File

@ -24,6 +24,7 @@ use Doctrine\Common\Persistence\Mapping\Driver\FileDriver;
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Mapping\ClassMetadata as Metadata;
/**
* XmlDriver is a metadata driver that enables mapping through XML files.
@ -165,7 +166,7 @@ class XmlDriver extends FileDriver
$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) {
if ($metadata->inheritanceType != Metadata::INHERITANCE_TYPE_NONE) {
// Evaluate <discriminator-column...>
if (isset($xmlRoot->{'discriminator-column'})) {
$discrColumn = $xmlRoot->{'discriminator-column'};
@ -621,6 +622,11 @@ class XmlDriver extends FileDriver
$override['inversedBy'] = (string) $overrideElement->{'inversed-by'}['name'];
}
// Check for `fetch`
if (isset($overrideElement['fetch'])) {
$override['fetch'] = constant(Metadata::class . '::FETCH_' . (string) $overrideElement['fetch']);
}
$metadata->setAssociationOverride($fieldName, $override);
}
}

View File

@ -22,6 +22,7 @@ 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\ClassMetadata as Metadata;
use Doctrine\ORM\Mapping\MappingException;
use Symfony\Component\Yaml\Yaml;
@ -174,7 +175,7 @@ class YamlDriver extends FileDriver
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) {
if ($metadata->inheritanceType != Metadata::INHERITANCE_TYPE_NONE) {
// Evaluate discriminatorColumn
if (isset($element['discriminatorColumn'])) {
$discrColumn = $element['discriminatorColumn'];
@ -622,6 +623,11 @@ class YamlDriver extends FileDriver
$override['inversedBy'] = (string) $associationOverrideElement['inversedBy'];
}
// Check for `fetch`
if (isset($associationOverrideElement['fetch'])) {
$override['fetch'] = constant(Metadata::class . '::FETCH_' . $associationOverrideElement['fetch']);
}
$metadata->setAssociationOverride($fieldName, $override);
}
}
@ -800,6 +806,10 @@ class YamlDriver extends FileDriver
*/
protected function loadMappingFile($file)
{
if (defined(Yaml::class . '::PARSE_KEYS_AS_STRINGS')) {
return Yaml::parse(file_get_contents($file), Yaml::PARSE_KEYS_AS_STRINGS);
}
return Yaml::parse(file_get_contents($file));
}
}

View File

@ -424,6 +424,16 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self('Single id is not allowed on composite primary key in entity '.$entity);
}
/**
* @param string $entity
*
* @return MappingException
*/
public static function noIdDefined($entity)
{
return new self('No ID defined for entity ' . $entity);
}
/**
* @param string $entity
* @param string $fieldName

View File

@ -52,8 +52,10 @@ interface NamingStrategy
/**
* Returns a column name for an embedded property.
*
* @param string $propertyName
* @param string $embeddedColumnName
* @param string $propertyName
* @param string $embeddedColumnName
* @param string $className
* @param string $embeddedClassName
*
* @return string
*/
@ -70,12 +72,10 @@ interface NamingStrategy
* Returns a join column name for a property.
*
* @param string $propertyName A property name.
* @param string|null $className The fully-qualified class name.
* This parameter is omitted from the signature due to BC
*
* @return string A join column name.
*/
function joinColumnName($propertyName/*, $className = null*/);
function joinColumnName($propertyName);
/**
* Returns a join table name.

View File

@ -323,16 +323,6 @@ class ORMException extends Exception
);
}
/**
* @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.");
}
/**
* @return ORMException
*/

View File

@ -82,16 +82,18 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
/**
* @param string $tableAlias
* @param string $joinColumnName
* @param string $quotedColumnName
*
* @param string $type
*
* @return string
*/
protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $type)
protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $quotedColumnName, $type)
{
$columnAlias = $this->getSQLColumnAlias($joinColumnName);
$this->currentPersisterContext->rsm->addMetaResult('r', $columnAlias, $joinColumnName, false, $type);
return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias;
return $tableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
}
}

View File

@ -87,16 +87,18 @@ class BasicEntityPersister implements EntityPersister
* @var array
*/
static private $comparisonMap = [
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',
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',
Comparison::STARTS_WITH => 'LIKE %s',
Comparison::ENDS_WITH => 'LIKE %s',
];
/**
@ -783,7 +785,7 @@ class BasicEntityPersister implements EntityPersister
// 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] =
$identifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
unset($identifier[$targetKeyColumn]);
@ -1062,11 +1064,11 @@ class BasicEntityPersister implements EntityPersister
switch ($lockMode) {
case LockMode::PESSIMISTIC_READ:
$lockSql = ' ' . $this->platform->getReadLockSql();
$lockSql = ' ' . $this->platform->getReadLockSQL();
break;
case LockMode::PESSIMISTIC_WRITE:
$lockSql = ' ' . $this->platform->getWriteLockSql();
$lockSql = ' ' . $this->platform->getWriteLockSQL();
break;
}
@ -1327,7 +1329,7 @@ class BasicEntityPersister implements EntityPersister
$resultColumnName = $this->getSQLColumnAlias($joinColumn['name']);
$type = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
$this->currentPersisterContext->rsm->addMetaResult($alias, $resultColumnName, $quotedColumn, $isIdentifier, $type);
$this->currentPersisterContext->rsm->addMetaResult($alias, $resultColumnName, $joinColumn['name'], $isIdentifier, $type);
$columnList[] = sprintf('%s.%s AS %s', $sqlTableAlias, $quotedColumn, $resultColumnName);
}
@ -1518,12 +1520,12 @@ class BasicEntityPersister implements EntityPersister
switch ($lockMode) {
case LockMode::PESSIMISTIC_READ:
$lockSql = $this->platform->getReadLockSql();
$lockSql = $this->platform->getReadLockSQL();
break;
case LockMode::PESSIMISTIC_WRITE:
$lockSql = $this->platform->getWriteLockSql();
$lockSql = $this->platform->getWriteLockSQL();
break;
}
@ -1868,8 +1870,9 @@ class BasicEntityPersister implements EntityPersister
/**
* Infers field types to be used by parameter type casting.
*
* @param string $field
* @param mixed $value
* @param string $field
* @param mixed $value
* @param ClassMetadata $class
*
* @return array
*

View File

@ -341,13 +341,13 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
switch ($lockMode) {
case LockMode::PESSIMISTIC_READ:
$lockSql = ' ' . $this->platform->getReadLockSql();
$lockSql = ' ' . $this->platform->getReadLockSQL();
break;
case LockMode::PESSIMISTIC_WRITE:
$lockSql = ' ' . $this->platform->getWriteLockSql();
$lockSql = ' ' . $this->platform->getWriteLockSQL();
break;
}
@ -462,21 +462,14 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
? $this->getSQLTableAlias($mapping['inherited'])
: $baseTableAlias;
foreach ($mapping['targetToSourceKeyColumns'] as $srcColumn) {
$className = isset($mapping['inherited'])
? $mapping['inherited']
: $this->class->name;
$targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
$targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
foreach ($mapping['joinColumns'] as $joinColumn) {
$columnList[] = $this->getSelectJoinColumnSQL(
$tableAlias,
$srcColumn,
PersisterHelper::getTypeOfColumn(
$mapping['sourceToTargetKeyColumns'][$srcColumn],
$targetClass,
$this->em
)
$joinColumn['name'],
$this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform),
PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em)
);
}
}
@ -510,21 +503,14 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
continue;
}
foreach ($mapping['targetToSourceKeyColumns'] as $srcColumn) {
$className = isset($mapping['inherited'])
? $mapping['inherited']
: $subClass->name;
$targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
$targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
foreach ($mapping['joinColumns'] as $joinColumn) {
$columnList[] = $this->getSelectJoinColumnSQL(
$tableAlias,
$srcColumn,
PersisterHelper::getTypeOfColumn(
$mapping['sourceToTargetKeyColumns'][$srcColumn],
$targetClass,
$this->em
)
$joinColumn['name'],
$this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform),
PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em)
);
}
}

View File

@ -89,15 +89,12 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
foreach ($assoc['joinColumns'] as $joinColumn) {
$columnList[] = $this->getSelectJoinColumnSQL(
$tableAlias,
$srcColumn,
PersisterHelper::getTypeOfColumn(
$assoc['sourceToTargetKeyColumns'][$srcColumn],
$targetClass,
$this->em
)
$joinColumn['name'],
$this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform),
PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em)
);
}
}

View File

@ -24,6 +24,9 @@ use Doctrine\ORM\ORMException;
class PersisterException extends ORMException
{
/**
* @param string $class
* @param string $associationName
*
* @return PersisterException
*/
static public function matchingAssocationFieldRequiresObject($class, $associationName)

View File

@ -46,7 +46,7 @@ class SqlValueVisitor extends ExpressionVisitor
*
* @param \Doctrine\Common\Collections\Expr\Comparison $comparison
*
* @return mixed
* @return void
*/
public function walkComparison(Comparison $comparison)
{
@ -69,7 +69,7 @@ class SqlValueVisitor extends ExpressionVisitor
*
* @param \Doctrine\Common\Collections\Expr\CompositeExpression $expr
*
* @return mixed
* @return void
*/
public function walkCompositeExpression(CompositeExpression $expr)
{
@ -111,8 +111,18 @@ class SqlValueVisitor extends ExpressionVisitor
{
$value = $comparison->getValue()->getValue();
return $comparison->getOperator() == Comparison::CONTAINS
? "%{$value}%"
: $value;
switch ($comparison->getOperator()) {
case Comparison::CONTAINS:
return "%{$value}%";
case Comparison::STARTS_WITH:
return "{$value}%";
case Comparison::ENDS_WITH:
return "%{$value}";
default:
return $value;
}
}
}

View File

@ -20,6 +20,7 @@
namespace Doctrine\ORM;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\ParserResult;
use Doctrine\ORM\Query\QueryException;
@ -202,7 +203,7 @@ final class Query extends AbstractQuery
*/
public function getSQL()
{
return $this->_parse()->getSQLExecutor()->getSQLStatements();
return $this->_parse()->getSqlExecutor()->getSqlStatements();
}
/**
@ -322,9 +323,36 @@ final class Query extends AbstractQuery
list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
$this->evictResultSetCache(
$executor,
$sqlParams,
$types,
$this->_em->getConnection()->getParams()
);
return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
}
private function evictResultSetCache(
AbstractSqlExecutor $executor,
array $sqlParams,
array $types,
array $connectionParams
) {
if (null === $this->_queryCacheProfile || ! $this->getExpireResultCache()) {
return;
}
$cacheDriver = $this->_queryCacheProfile->getResultCacheDriver();
$statements = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array
foreach ($statements as $statement) {
$cacheKeys = $this->_queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams);
$cacheDriver->delete(reset($cacheKeys));
}
}
/**
* Evict entity cache region
*/
@ -343,6 +371,25 @@ final class Query extends AbstractQuery
$this->_em->getCache()->evictEntityRegion($className);
}
private function getAllDiscriminators(ClassMetadata $classMetadata)
{
// FIXME: this code is copied from SqlWalker->getAllDiscriminators()
$hierarchyClasses = $classMetadata->subClasses;
$hierarchyClasses[] = $classMetadata->name;
$discriminators = [];
foreach ($hierarchyClasses as $class) {
$currentMetadata = $this->getEntityManager()->getClassMetadata($class);
$currentDiscriminator = $currentMetadata->discriminatorValue;
if (null !== $currentDiscriminator) {
$discriminators[$currentDiscriminator] = null;
}
}
return $discriminators;
}
/**
* Processes query parameter mappings.
*
@ -370,6 +417,10 @@ final class Query extends AbstractQuery
$value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
}
if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) {
$value = array_keys($this->getAllDiscriminators($value));
}
$value = $this->processParameterValue($value);
$type = ($parameter->getValue() === $value)
? $parameter->getType()
@ -709,7 +760,7 @@ final class Query extends AbstractQuery
->getName();
return md5(
$this->getDql() . serialize($this->_hints) .
$this->getDQL() . serialize($this->_hints) .
'&platform=' . $platform .
($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') .
'&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .

View File

@ -41,6 +41,7 @@ class AbsFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -51,6 +52,7 @@ class AbsFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -0,0 +1,54 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\AggregateExpression;
/**
* "AVG" "(" ["DISTINCT"] StringPrimary ")"
*
* @since 2.6
* @author Mathew Davies <thepixeldeveloper@icloud.com>
*/
final class AvgFunction extends FunctionNode
{
/**
* @var AggregateExpression
*/
private $aggregateExpression;
/**
* @inheritDoc
*/
public function getSql(SqlWalker $sqlWalker): string
{
return $this->aggregateExpression->dispatch($sqlWalker);
}
/**
* @inheritDoc
*/
public function parse(Parser $parser): void
{
$this->aggregateExpression = $parser->AggregateExpression();
}
}

View File

@ -36,6 +36,7 @@ class BitAndFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -49,6 +50,7 @@ class BitAndFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -36,6 +36,7 @@ class BitOrFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -49,6 +50,7 @@ class BitOrFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -42,6 +42,7 @@ class ConcatFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -58,6 +59,7 @@ class ConcatFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -0,0 +1,54 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\AggregateExpression;
/**
* "COUNT" "(" ["DISTINCT"] StringPrimary ")"
*
* @since 2.6
* @author Mathew Davies <thepixeldeveloper@icloud.com>
*/
final class CountFunction extends FunctionNode
{
/**
* @var AggregateExpression
*/
private $aggregateExpression;
/**
* @inheritDoc
*/
public function getSql(SqlWalker $sqlWalker): string
{
return $this->aggregateExpression->dispatch($sqlWalker);
}
/**
* @inheritDoc
*/
public function parse(Parser $parser): void
{
$this->aggregateExpression = $parser->AggregateExpression();
}
}

View File

@ -36,6 +36,7 @@ class CurrentDateFunction extends FunctionNode
{
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -44,6 +45,7 @@ class CurrentDateFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -36,6 +36,7 @@ class CurrentTimeFunction extends FunctionNode
{
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -44,6 +45,7 @@ class CurrentTimeFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -36,6 +36,7 @@ class CurrentTimestampFunction extends FunctionNode
{
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -44,6 +45,7 @@ class CurrentTimestampFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -41,6 +41,7 @@ class DateAddFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(SqlWalker $sqlWalker)
{
@ -77,6 +78,7 @@ class DateAddFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(Parser $parser)
{

View File

@ -38,6 +38,7 @@ class DateDiffFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(SqlWalker $sqlWalker)
{
@ -49,6 +50,7 @@ class DateDiffFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(Parser $parser)
{

View File

@ -35,6 +35,7 @@ class DateSubFunction extends DateAddFunction
{
/**
* @override
* @inheritdoc
*/
public function getSql(SqlWalker $sqlWalker)
{

View File

@ -38,6 +38,7 @@ class LengthFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -48,6 +49,7 @@ class LengthFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -44,6 +44,7 @@ class LocateFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -60,6 +61,7 @@ class LocateFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -38,6 +38,7 @@ class LowerFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -48,6 +49,7 @@ class LowerFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -0,0 +1,54 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\AggregateExpression;
/**
* "MAX" "(" ["DISTINCT"] StringPrimary ")"
*
* @since 2.6
* @author Mathew Davies <thepixeldeveloper@icloud.com>
*/
final class MaxFunction extends FunctionNode
{
/**
* @var AggregateExpression
*/
private $aggregateExpression;
/**
* @inheritDoc
*/
public function getSql(SqlWalker $sqlWalker): string
{
return $this->aggregateExpression->dispatch($sqlWalker);
}
/**
* @inheritDoc
*/
public function parse(Parser $parser): void
{
$this->aggregateExpression = $parser->AggregateExpression();
}
}

View File

@ -0,0 +1,54 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\AggregateExpression;
/**
* "MIN" "(" ["DISTINCT"] StringPrimary ")"
*
* @since 2.6
* @author Mathew Davies <thepixeldeveloper@icloud.com>
*/
final class MinFunction extends FunctionNode
{
/**
* @var AggregateExpression
*/
private $aggregateExpression;
/**
* @inheritDoc
*/
public function getSql(SqlWalker $sqlWalker): string
{
return $this->aggregateExpression->dispatch($sqlWalker);
}
/**
* @inheritDoc
*/
public function parse(Parser $parser): void
{
$this->aggregateExpression = $parser->AggregateExpression();
}
}

View File

@ -46,6 +46,7 @@ class ModFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -57,6 +58,7 @@ class ModFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -41,6 +41,7 @@ class SizeFunction extends FunctionNode
/**
* @override
* @inheritdoc
* @todo If the collection being counted is already joined, the SQL can be simpler (more efficient).
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
@ -110,6 +111,7 @@ class SizeFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -41,6 +41,7 @@ class SqrtFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -51,6 +52,7 @@ class SqrtFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -48,6 +48,7 @@ class SubstringFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -65,6 +66,7 @@ class SubstringFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -0,0 +1,54 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\AggregateExpression;
/**
* "SUM" "(" ["DISTINCT"] StringPrimary ")"
*
* @since 2.6
* @author Mathew Davies <thepixeldeveloper@icloud.com>
*/
final class SumFunction extends FunctionNode
{
/**
* @var AggregateExpression
*/
private $aggregateExpression;
/**
* @inheritDoc
*/
public function getSql(SqlWalker $sqlWalker): string
{
return $this->aggregateExpression->dispatch($sqlWalker);
}
/**
* @inheritDoc
*/
public function parse(Parser $parser): void
{
$this->aggregateExpression = $parser->AggregateExpression();
}
}

View File

@ -38,6 +38,7 @@ class UpperFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -48,6 +49,7 @@ class UpperFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{

View File

@ -97,7 +97,7 @@ class OrderBy
/**
* @return string
*/
public function __tostring()
public function __toString()
{
return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator;
}

View File

@ -65,6 +65,13 @@ class Parser
'date_diff' => Functions\DateDiffFunction::class,
'bit_and' => Functions\BitAndFunction::class,
'bit_or' => Functions\BitOrFunction::class,
// Aggregate functions
'min' => Functions\MinFunction::class,
'max' => Functions\MaxFunction::class,
'avg' => Functions\AvgFunction::class,
'sum' => Functions\SumFunction::class,
'count' => Functions\CountFunction::class,
];
/**
@ -171,23 +178,6 @@ class Parser
*/
private $identVariableExpressions = [];
/**
* 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.
*
@ -197,7 +187,7 @@ class Parser
{
$this->query = $query;
$this->em = $query->getEntityManager();
$this->lexer = new Lexer($query->getDql());
$this->lexer = new Lexer($query->getDQL());
$this->parserResult = new ParserResult();
}
@ -277,7 +267,7 @@ class Parser
}
if ($this->deferredPathExpressions) {
$this->processDeferredPathExpressions($AST);
$this->processDeferredPathExpressions();
}
if ($this->deferredResultVariables) {
@ -482,7 +472,7 @@ class Parser
$distance = 12;
// Find a position of a final word to display in error string
$dql = $this->query->getDql();
$dql = $this->query->getDQL();
$length = strlen($dql);
$pos = $token['position'] + $distance;
$pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
@ -749,11 +739,9 @@ class Parser
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
* CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
*
* @param mixed $AST
*
* @return void
*/
private function processDeferredPathExpressions($AST)
private function processDeferredPathExpressions()
{
foreach ($this->deferredPathExpressions as $deferredItem) {
$pathExpression = $deferredItem['expression'];
@ -1504,7 +1492,7 @@ class Parser
$glimpse = $this->lexer->glimpse();
switch (true) {
case ($this->isFunction($peek)):
case ($this->isFunction()):
$expr = $this->FunctionDeclaration();
break;
@ -1724,9 +1712,15 @@ class Parser
* RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
*
* @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
*
* @throws QueryException
*/
public function RangeVariableDeclaration()
{
if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()['type'] === Lexer::T_SELECT) {
$this->semanticalError('Subquery is not supported here', $this->lexer->token);
}
$abstractSchemaName = $this->AbstractSchemaName();
$this->validateAbstractSchemaName($abstractSchemaName);
@ -1795,7 +1789,7 @@ class Parser
* PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
* PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
*
* @return array
* @return \Doctrine\ORM\Query\AST\PartialObjectExpression
*/
public function PartialObjectExpression()
{
@ -1980,9 +1974,6 @@ class Parser
// 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();
@ -2211,11 +2202,6 @@ class Parser
$expression = $this->ScalarExpression();
break;
case ($this->isAggregateFunction($lookaheadType)):
// COUNT(u.id)
$expression = $this->AggregateExpression();
break;
default:
// IDENTITY(u)
$expression = $this->FunctionDeclaration();
@ -2860,10 +2846,6 @@ class Parser
$peek = $this->lexer->glimpse();
if ($peek['value'] == '(') {
if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
return $this->AggregateExpression();
}
return $this->FunctionDeclaration();
}
@ -2934,11 +2916,6 @@ class Parser
case Lexer::T_COALESCE:
case Lexer::T_NULLIF:
return $this->CaseExpression();
default:
if ($this->isAggregateFunction($lookaheadType)) {
return $this->AggregateExpression();
}
}
$this->syntaxError(
@ -3238,10 +3215,6 @@ class Parser
$expr = $this->CoalesceExpression();
break;
case $this->isAggregateFunction($this->lexer->lookahead['type']):
$expr = $this->AggregateExpression();
break;
case $this->isFunction():
$expr = $this->FunctionDeclaration();
break;
@ -3378,8 +3351,13 @@ class Parser
$token = $this->lexer->lookahead;
$funcName = strtolower($token['value']);
// Check for built-in functions first!
$customFunctionDeclaration = $this->CustomFunctionDeclaration();
// Check for custom functions functions first!
switch (true) {
case $customFunctionDeclaration !== null:
return $customFunctionDeclaration;
case (isset(self::$_STRING_FUNCTIONS[$funcName])):
return $this->FunctionsReturningStrings();
@ -3390,7 +3368,7 @@ class Parser
return $this->FunctionsReturningDatetime();
default:
return $this->CustomFunctionDeclaration();
$this->syntaxError('known function', $token);
}
}
@ -3418,7 +3396,7 @@ class Parser
return $this->CustomFunctionsReturningDatetime();
default:
$this->syntaxError('known function', $token);
return null;
}
}

View File

@ -19,6 +19,8 @@
namespace Doctrine\ORM\Query;
use Doctrine\ORM\Query\AST\PathExpression;
/**
* Description of QueryException.
*
@ -204,13 +206,15 @@ class QueryException extends \Doctrine\ORM\ORMException
}
/**
* @param PathExpression $pathExpr
*
* @return QueryException
*/
public static function associationPathInverseSideNotSupported()
public static function associationPathInverseSideNotSupported(PathExpression $pathExpr)
{
return new self(
"A single-valued association path expression to an inverse side is not supported".
" in DQL queries. Use an explicit join instead."
'A single-valued association path expression to an inverse side is not supported in DQL queries. ' .
'Instead of "' . $pathExpr->identificationVariable . '.' . $pathExpr->field . '" use an explicit join.'
);
}

View File

@ -185,6 +185,16 @@ class QueryExpressionVisitor extends ExpressionVisitor
$parameter->setValue('%' . $parameter->getValue() . '%', $parameter->getType());
$this->parameters[] = $parameter;
return $this->expr->like($field, $placeholder);
case Comparison::STARTS_WITH:
$parameter->setValue($parameter->getValue() . '%', $parameter->getType());
$this->parameters[] = $parameter;
return $this->expr->like($field, $placeholder);
case Comparison::ENDS_WITH:
$parameter->setValue('%' . $parameter->getValue(), $parameter->getType());
$this->parameters[] = $parameter;
return $this->expr->like($field, $placeholder);
default:
$operator = self::convertComparisonOperator($comparison->getOperator());

View File

@ -168,6 +168,13 @@ class ResultSetMapping
*/
public $metadataParameterMapping = [];
/**
* Contains query parameter names to be resolved as discriminator values
*
* @var array
*/
public $discriminatorParameters = [];
/**
* Adds an entity result to this ResultSetMapping.
*

View File

@ -643,6 +643,7 @@ class SqlWalker implements TreeWalker
{
$sql = '';
/* @var $pathExpr Query\AST\PathExpression */
switch ($pathExpr->type) {
case AST\PathExpression::TYPE_STATE_FIELD:
$fieldName = $pathExpr->field;
@ -670,7 +671,7 @@ class SqlWalker implements TreeWalker
$assoc = $class->associationMappings[$fieldName];
if ( ! $assoc['isOwningSide']) {
throw QueryException::associationPathInverseSideNotSupported();
throw QueryException::associationPathInverseSideNotSupported($pathExpr);
}
// COMPOSITE KEYS NOT (YET?) SUPPORTED
@ -763,7 +764,8 @@ class SqlWalker implements TreeWalker
$columnAlias = $this->getSQLColumnAlias($columnName);
$columnType = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
$sqlSelectExpressions[] = $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
$quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
$sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
$this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
}
@ -791,7 +793,8 @@ class SqlWalker implements TreeWalker
$columnAlias = $this->getSQLColumnAlias($columnName);
$columnType = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
$sqlSelectExpressions[] = $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
$quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform);
$sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
$this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
}
@ -1499,8 +1502,8 @@ class SqlWalker implements TreeWalker
}
/**
* @param AST\NewObjectExpression $newObjectExpression
*
* @param AST\NewObjectExpression $newObjectExpression
* @param null|string $newObjectResultAlias
* @return string The SQL.
*/
public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)
@ -1576,12 +1579,6 @@ class SqlWalker implements TreeWalker
$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++;
@ -1758,7 +1755,7 @@ class SqlWalker implements TreeWalker
public function walkWhereClause($whereClause)
{
$condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
$discrSql = $this->_generateDiscriminatorColumnConditionSql($this->rootAliases);
$discrSql = $this->_generateDiscriminatorColumnConditionSQL($this->rootAliases);
if ($this->em->hasFilters()) {
$filterClauses = [];
@ -2273,42 +2270,29 @@ class SqlWalker implements TreeWalker
}
/**
* @param ClassMetadataInfo $discrClass
* @param ClassMetadataInfo $rootClass
* @param AST\InstanceOfExpression $instanceOfExpr
* @return string The list in parentheses of valid child discriminators from the given class
* @throws QueryException
*/
private function getChildDiscriminatorsFromClassMetadata(ClassMetadataInfo $discrClass, AST\InstanceOfExpression $instanceOfExpr)
private function getChildDiscriminatorsFromClassMetadata(ClassMetadataInfo $rootClass, AST\InstanceOfExpression $instanceOfExpr)
{
$sqlParameterList = [];
$discriminators = [];
foreach ($instanceOfExpr->value as $parameter) {
if ($parameter instanceof AST\InputParameter) {
$this->rsm->addMetadataParameterMapping($parameter->name, 'discriminatorValue');
$sqlParameterList[] = $this->walkInputParameter($parameter);
$this->rsm->discriminatorParameters[$parameter->name] = $parameter->name;
$sqlParameterList[] = $this->walkInParameter($parameter);
continue;
}
$metadata = $this->em->getClassMetadata($parameter);
if ($metadata->getName() !== $discrClass->name && ! $metadata->getReflectionClass()->isSubclassOf($discrClass->name)) {
throw QueryException::instanceOfUnrelatedClass($parameter, $discrClass->name);
if ($metadata->getName() !== $rootClass->name && ! $metadata->getReflectionClass()->isSubclassOf($rootClass->name)) {
throw QueryException::instanceOfUnrelatedClass($parameter, $rootClass->name);
}
// Include discriminators for parameter class and its subclass
$hierarchyClasses = $metadata->subClasses;
$hierarchyClasses[] = $metadata->name;
foreach ($hierarchyClasses as $class) {
$currentMetadata = $this->em->getClassMetadata($class);
$currentDiscriminator = $currentMetadata->discriminatorValue;
if (null !== $currentDiscriminator) {
$discriminators[$currentDiscriminator] = null;
}
}
$discriminators = $discriminators + $this->getAllDiscriminators($metadata);
}
foreach (array_keys($discriminators) as $dis) {
@ -2317,4 +2301,23 @@ class SqlWalker implements TreeWalker
return '(' . implode(', ', $sqlParameterList) . ')';
}
private function getAllDiscriminators(ClassMetadata $classMetadata)
{
// FIXME: this code is identical to Query->getAllDiscriminators()
$hierarchyClasses = $classMetadata->subClasses;
$hierarchyClasses[] = $classMetadata->name;
$discriminators = [];
foreach ($hierarchyClasses as $class) {
$currentMetadata = $this->em->getClassMetadata($class);
$currentDiscriminator = $currentMetadata->discriminatorValue;
if (null !== $currentDiscriminator) {
$discriminators[$currentDiscriminator] = null;
}
}
return $discriminators;
}
}

View File

@ -526,7 +526,7 @@ class QueryBuilder
*
* @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
* @param string|integer|null $type PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant
*
* @return self
*/

View File

@ -21,7 +21,6 @@ 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;

View File

@ -185,7 +185,7 @@ EOT
if (count($matches) > 1) {
throw new \InvalidArgumentException(sprintf(
'Entity name "%s" is ambigous, possible matches: "%s"',
'Entity name "%s" is ambiguous, possible matches: "%s"',
$entityName, implode(', ', $matches)
));
}

View File

@ -84,10 +84,6 @@ class MetadataFilter extends \FilterIterator implements \Countable
);
}
if ($pregResult === 0) {
return false;
}
if ($pregResult) {
return true;
}

View File

@ -143,7 +143,7 @@ class DebugUnitOfWorkListener
if (is_object($var)) {
$refl = new \ReflectionObject($var);
return $refl->getShortname();
return $refl->getShortName();
}
return gettype($var);
@ -176,7 +176,7 @@ class DebugUnitOfWorkListener
$idstring .= " [REMOVED]";
} elseif ($state == UnitOfWork::STATE_MANAGED) {
$idstring .= " [MANAGED]";
} elseif ($state == UnitOfwork::STATE_DETACHED) {
} elseif ($state == UnitOfWork::STATE_DETACHED) {
$idstring .= " [DETACHED]";
}

View File

@ -1731,8 +1731,12 @@ public function __construct(<params>)
$embedded = ['class="' . $embeddedClass['class'] . '"'];
if (isset($fieldMapping['columnPrefix'])) {
$embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
if (isset($embeddedClass['columnPrefix'])) {
if (is_string($embeddedClass['columnPrefix'])) {
$embedded[] = 'columnPrefix="' . $embeddedClass['columnPrefix'] . '"';
} else {
$embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
}
}
$lines[] = $this->spaces . ' * @' .

View File

@ -91,6 +91,14 @@ class CountOutputWalker extends SqlWalker
$sql = parent::walkSelectStatement($AST);
if ($AST->groupByClause) {
return sprintf(
'SELECT %s AS dctrn_count FROM (%s) dctrn_table',
$this->platform->getCountExpression('*'),
$sql
);
}
// 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

View File

@ -40,6 +40,7 @@ class RowNumberOverFunction extends FunctionNode
/**
* @override
* @inheritdoc
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
@ -50,6 +51,7 @@ class RowNumberOverFunction extends FunctionNode
/**
* @override
* @inheritdoc
*
* @throws ORMException
*/

View File

@ -80,7 +80,7 @@ class ResolveTargetEntityListener implements EventSubscriber
$args->setFoundMetadata(
$args
->getObjectManager()
->getClassMetadata($this->resolveTargetEntities[$args->getClassname()]['targetEntity'])
->getClassMetadata($this->resolveTargetEntities[$args->getClassName()]['targetEntity'])
);
}
}

View File

@ -351,15 +351,14 @@ class SchemaTool
* @param ClassMetadata $class
* @param Table $table
*
* @return array The portable column definition of the discriminator column as required by
* the DBAL.
* @return void
*/
private function addDiscriminatorColumnDefinition($class, Table $table)
{
$discrColumn = $class->discriminatorColumn;
if ( ! isset($discrColumn['type']) ||
(strtolower($discrColumn['type']) == 'string' && $discrColumn['length'] === null)
(strtolower($discrColumn['type']) == 'string' && ! isset($discrColumn['length']))
) {
$discrColumn['type'] = 'string';
$discrColumn['length'] = 255;
@ -384,7 +383,7 @@ class SchemaTool
* @param ClassMetadata $class
* @param Table $table
*
* @return array The list of portable column definitions as required by the DBAL.
* @return void
*/
private function gatherColumns($class, Table $table)
{
@ -401,12 +400,6 @@ class SchemaTool
$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);
}
}
/**
@ -416,7 +409,7 @@ class SchemaTool
* @param array $mapping The field mapping.
* @param Table $table
*
* @return array The portable column definition as required by the DBAL.
* @return void
*/
private function gatherColumn($class, array $mapping, Table $table)
{

View File

@ -90,7 +90,7 @@ class SchemaValidator
foreach ($class->fieldMappings as $fieldName => $mapping) {
if (!Type::hasType($mapping['type'])) {
$ce[] = "The field '" . $class->name . "#" . $fieldName."' uses a non-existant type '" . $mapping['type'] . "'.";
$ce[] = "The field '" . $class->name . "#" . $fieldName."' uses a non-existent type '" . $mapping['type'] . "'.";
}
}

View File

@ -7,12 +7,14 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
class NegativeToPositiveType extends Type
{
const NAME = 'negative_to_positive';
/**
* {@inheritdoc}
*/
public function getName()
{
return 'negative_to_positive';
return self::NAME;
}
/**

View File

@ -7,12 +7,14 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
class UpperCaseStringType extends StringType
{
const NAME = 'upper_case_string';
/**
* {@inheritdoc}
*/
public function getName()
{
return 'upper_case_string';
return self::NAME;
}
/**

View File

@ -1,7 +1,9 @@
<?php
namespace Doctrine\Tests\Mocks;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Statement;
/**
* Mock class for Connection.
@ -13,6 +15,16 @@ class ConnectionMock extends Connection
*/
private $_fetchOneResult;
/**
* @var \Exception|null
*/
private $_fetchOneException;
/**
* @var Statement|null
*/
private $_queryResult;
/**
* @var DatabasePlatformMock
*/
@ -86,9 +98,21 @@ class ConnectionMock extends Connection
*/
public function fetchColumn($statement, array $params = [], $colnum = 0, array $types = [])
{
if (null !== $this->_fetchOneException) {
throw $this->_fetchOneException;
}
return $this->_fetchOneResult;
}
/**
* {@inheritdoc}
*/
public function query() : Statement
{
return $this->_queryResult;
}
/**
* {@inheritdoc}
*/
@ -112,6 +136,16 @@ class ConnectionMock extends Connection
$this->_fetchOneResult = $fetchOneResult;
}
/**
* @param \Exception|null $exception
*
* @return void
*/
public function setFetchOneException(\Exception $exception = null)
{
$this->_fetchOneException = $exception;
}
/**
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
*
@ -132,6 +166,14 @@ class ConnectionMock extends Connection
$this->_lastInsertId = $id;
}
/**
* @param Statement $result
*/
public function setQueryResult(Statement $result)
{
$this->_queryResult = $result;
}
/**
* @return array
*/

View File

@ -30,13 +30,12 @@ class HydratorMockStatement implements \IteratorAggregate, Statement
/**
* Fetches all rows from the result set.
*
* @param int|null $fetchStyle
* @param int|null $columnIndex
* @param int|null $fetchMode
* @param int|null $fetchArgument
* @param array|null $ctorArgs
*
* @return array
*/
public function fetchAll($fetchStyle = null, $columnIndex = null, array $ctorArgs = null)
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
{
return $this->_resultSet;
}
@ -55,7 +54,7 @@ class HydratorMockStatement implements \IteratorAggregate, Statement
/**
* {@inheritdoc}
*/
public function fetch($fetchStyle = null)
public function fetch($fetchStyle = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
$current = current($this->_resultSet);
next($this->_resultSet);
@ -108,7 +107,7 @@ class HydratorMockStatement implements \IteratorAggregate, Statement
/**
* {@inheritdoc}
*/
public function execute($params = [])
public function execute($params = null)
{
}

View File

@ -34,12 +34,12 @@ class StatementArrayMock extends StatementMock
}
}
public function fetchAll($fetchStyle = null)
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
{
return $this->_result;
}
public function fetch($fetchStyle = null)
public function fetch($fetchMode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
$current = current($this->_result);
next($this->_result);

View File

@ -75,14 +75,14 @@ class StatementMock implements \IteratorAggregate, Statement
/**
* {@inheritdoc}
*/
public function fetch($fetchStyle = null)
public function fetch($fetchMode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
}
/**
* {@inheritdoc}
*/
public function fetchAll($fetchStyle = null)
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
{
}

View File

@ -0,0 +1,53 @@
<?php
namespace Doctrine\Tests\Models\DDC5934;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\ManyToMany;
/**
* @Entity
*/
class DDC5934BaseContract
{
/**
* @Id()
* @Column(name="id", type="integer")
* @GeneratedValue()
*/
public $id;
/**
* @var ArrayCollection
*
* @ManyToMany(targetEntity="DDC5934Member", fetch="LAZY", inversedBy="contracts")
*/
public $members;
public function __construct()
{
$this->members = new ArrayCollection();
}
public static function loadMetadata(ClassMetadata $metadata)
{
$metadata->mapField([
'id' => true,
'fieldName' => 'id',
'type' => 'integer',
'columnName' => 'id',
]);
$metadata->mapManyToMany([
'fieldName' => 'members',
'targetEntity' => 'DDC5934Member',
]);
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Doctrine\Tests\Models\DDC5934;
use Doctrine\ORM\Mapping\AssociationOverride;
use Doctrine\ORM\Mapping\AssociationOverrides;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\ClassMetadata;
/**
* @Entity
* @AssociationOverrides(
* @AssociationOverride(name="members", fetch="EXTRA_LAZY")
* )
*/
class DDC5934Contract extends DDC5934BaseContract
{
public static function loadMetadata(ClassMetadata $metadata)
{
$metadata->setAssociationOverride('members', [
'fetch' => ClassMetadata::FETCH_EXTRA_LAZY,
]);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Doctrine\Tests\Models\DDC5934;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class DDC5934Member
{
/**
* @var ArrayCollection
*
* @ORM\ManyToMany(targetEntity="DDC5934BaseContract", mappedBy="members")
*/
public $contracts;
public function __construct()
{
$this->contracts = new ArrayCollection();
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Doctrine\Tests\Models\DDC6412;
/**
* @Entity
*/
class DDC6412File
{
/**
* @Column(type="integer")
* @GeneratedValue
*/
public $id;
/**
* @Column(length=50, name="file_name")
*/
public $name;
}

View File

@ -5,6 +5,9 @@ namespace Doctrine\Tests\Models\Quote;
/**
* @Entity
* @Table(name="`quote-address`")
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="type", type="string")
* @DiscriminatorMap({"simple" = Address::class, "full" = FullAddress::class})
*/
class Address
{
@ -51,4 +54,4 @@ class Address
return $this->user;
}
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Doctrine\Tests\Models\Quote;
/**
* @Entity
* @Table(name="`quote-city`")
*/
class City
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer", name="`city-id`")
*/
public $id;
/**
* @Column(name="`city-name`")
*/
public $name;
public function __construct(string $name)
{
$this->name = $name;
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Doctrine\Tests\Models\Quote;
/**
* @Entity
*/
class FullAddress extends Address
{
/**
* @OneToOne(targetEntity=City::class, cascade={"persist"})
* @JoinColumn(name="`city-id`", referencedColumnName="`city-id`")
*
* @var City
*/
public $city;
}

View File

@ -125,8 +125,7 @@ class DefaultQueryCacheTest extends OrmTestCase
$stateClass = $this->em->getClassMetadata(State::class);
$rsm->addRootEntityFromClassMetadata(City::class, 'c');
$rsm->addJoinedEntityFromClassMetadata(State::class, 's', 'c', 'state', ['id'=>'state_id', 'name'=>'state_name']
);
$rsm->addJoinedEntityFromClassMetadata(State::class, 's', 'c', 'state', ['id'=>'state_id', 'name'=>'state_name']);
for ($i = 0; $i < 4; $i++) {
$state = new State("State $i");
@ -283,8 +282,47 @@ class DefaultQueryCacheTest extends OrmTestCase
$key = new QueryCacheKey('query.key1', 0);
$entry = new QueryCacheEntry(
[
['identifier' => ['id' => 1]],
['identifier' => ['id' => 2]]
['identifier' => ['id' => 1]],
['identifier' => ['id' => 2]]
]
);
$data = [
['id'=>1, 'name' => 'Foo'],
['id'=>2, 'name' => 'Bar']
];
$this->region->addReturn('get', $entry);
$this->region->addReturn(
'getMultiple',
[
new EntityCacheEntry(Country::class, $data[0]),
new EntityCacheEntry(Country::class, $data[1])
]
);
$rsm->addRootEntityFromClassMetadata(Country::class, 'c');
$result = $this->queryCache->get($key, $rsm);
$this->assertCount(2, $result);
$this->assertInstanceOf(Country::class, $result[0]);
$this->assertInstanceOf(Country::class, $result[1]);
$this->assertEquals(1, $result[0]->getId());
$this->assertEquals(2, $result[1]->getId());
$this->assertEquals('Foo', $result[0]->getName());
$this->assertEquals('Bar', $result[1]->getName());
}
public function testGetWithAssociation()
{
$rsm = new ResultSetMappingBuilder($this->em);
$key = new QueryCacheKey('query.key1', 0);
$entry = new QueryCacheEntry(
[
['identifier' => ['id' => 1]],
['identifier' => ['id' => 2]]
]
);

View File

@ -2,9 +2,9 @@
namespace Doctrine\Tests\ORM\Cache;
use Doctrine\Common\Cache\ApcCache;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\ORM\Cache\CollectionCacheEntry;
use Doctrine\ORM\Cache\Region\DefaultRegion;
use Doctrine\Tests\Mocks\CacheEntryMock;
@ -28,14 +28,11 @@ class DefaultRegionTest extends AbstractRegionTest
public function testSharedRegion()
{
if ( ! extension_loaded('apc') || false === @apc_cache_info()) {
$this->markTestSkipped('The ' . __CLASS__ .' requires the use of APC');
}
$cache = new SharedArrayCache();
$key = new CacheKeyMock('key');
$entry = new CacheEntryMock(['value' => 'foo']);
$region1 = new DefaultRegion('region1', new ApcCache());
$region2 = new DefaultRegion('region2', new ApcCache());
$region1 = new DefaultRegion('region1', $cache->createChild());
$region2 = new DefaultRegion('region2', $cache->createChild());
$this->assertFalse($region1->contains($key));
$this->assertFalse($region2->contains($key));
@ -99,3 +96,60 @@ class DefaultRegionTest extends AbstractRegionTest
$this->assertEquals($value2, $actual[1]);
}
}
/**
* Cache provider that offers child cache items (sharing the same array)
*
* Declared as a different class for readability purposes and kept in this file
* to keep its monstrosity contained.
*
* @internal
*/
final class SharedArrayCache extends ArrayCache
{
public function createChild(): Cache
{
return new class ($this) extends CacheProvider
{
/**
* @var ArrayCache
*/
private $parent;
public function __construct(ArrayCache $parent)
{
$this->parent = $parent;
}
protected function doFetch($id)
{
return $this->parent->doFetch($id);
}
protected function doContains($id)
{
return $this->parent->doContains($id);
}
protected function doSave($id, $data, $lifeTime = 0)
{
return $this->parent->doSave($id, $data, $lifeTime);
}
protected function doDelete($id)
{
return $this->parent->doDelete($id);
}
protected function doFlush()
{
return $this->parent->doFlush();
}
protected function doGetStats()
{
return $this->parent->doGetStats();
}
};
}
}

View File

@ -179,6 +179,8 @@ class ConfigurationTest extends TestCase
{
$this->setProductionSettings();
$this->configuration->ensureProductionSettings();
$this->addToAssertionCount(1);
}
public function testEnsureProductionSettingsQueryCache()
@ -263,8 +265,6 @@ class ConfigurationTest extends TestCase
$this->assertSame(null, $this->configuration->getCustomStringFunction('NonExistingFunction'));
$this->configuration->setCustomStringFunctions(['OtherFunctionName' => __CLASS__]);
$this->assertSame(__CLASS__, $this->configuration->getCustomStringFunction('OtherFunctionName'));
$this->expectException(ORMException::class);
$this->configuration->addCustomStringFunction('concat', __CLASS__);
}
public function testAddGetCustomNumericFunction()
@ -274,8 +274,6 @@ class ConfigurationTest extends TestCase
$this->assertSame(null, $this->configuration->getCustomNumericFunction('NonExistingFunction'));
$this->configuration->setCustomNumericFunctions(['OtherFunctionName' => __CLASS__]);
$this->assertSame(__CLASS__, $this->configuration->getCustomNumericFunction('OtherFunctionName'));
$this->expectException(ORMException::class);
$this->configuration->addCustomNumericFunction('abs', __CLASS__);
}
public function testAddGetCustomDatetimeFunction()
@ -285,8 +283,6 @@ class ConfigurationTest extends TestCase
$this->assertSame(null, $this->configuration->getCustomDatetimeFunction('NonExistingFunction'));
$this->configuration->setCustomDatetimeFunctions(['OtherFunctionName' => __CLASS__]);
$this->assertSame(__CLASS__, $this->configuration->getCustomDatetimeFunction('OtherFunctionName'));
$this->expectException(ORMException::class);
$this->configuration->addCustomDatetimeFunction('date_add', __CLASS__);
}
public function testAddGetCustomHydrationMode()

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