1
0
mirror of synced 2024-12-05 03:06:05 +03:00

Merge branch 'master' of github.com:doctrine/doctrine2 into ValueObjects

Conflicts:
	UPGRADE.md
	lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php
This commit is contained in:
Johannes Schmitt 2014-01-04 17:52:32 +01:00
commit 4f585a3d63
220 changed files with 19339 additions and 804 deletions

View File

@ -4,11 +4,15 @@ php:
- 5.3
- 5.4
- 5.5
- hhvm
env:
- DB=mysql
- DB=pgsql
- DB=sqlite
- DB=mysql ENABLE_SECOND_LEVEL_CACHE=1
- DB=pgsql ENABLE_SECOND_LEVEL_CACHE=1
- DB=sqlite ENABLE_SECOND_LEVEL_CACHE=1
- DB=mysql ENABLE_SECOND_LEVEL_CACHE=0
- DB=pgsql ENABLE_SECOND_LEVEL_CACHE=0
- DB=sqlite ENABLE_SECOND_LEVEL_CACHE=0
before_script:
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests;' -U postgres; fi"
@ -18,7 +22,11 @@ before_script:
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
- composer install --prefer-dist --dev
script: phpunit --configuration tests/travis/$DB.travis.xml
script: phpunit -v --configuration tests/travis/$DB.travis.xml
after_script:
- php vendor/bin/coveralls -v
matrix:
allow_failures:
- php: hhvm

View File

@ -8,7 +8,7 @@ unified and future proof.
## We only accept PRs to "master"
Our branching strategy is summed up with "everything to master first", even
Our branching strategy is "everything to master first", even
bugfixes and we then merge them into the stable branches. You should only
open pull requests against the master branch. Otherwise we cannot accept the PR.
@ -33,7 +33,7 @@ with some exceptions/differences:
## Unit-Tests
Always add a test for your pull-request.
Please try to add a test for your pull-request.
* If you want to fix a bug or provide a reproduce case, create a test file in
``tests/Doctrine/Tests/ORM/Functional/Ticket`` with the name of the ticket,
@ -50,6 +50,12 @@ take a look at the ``tests/travis`` folder for some examples. Then run:
phpunit -c mysql.phpunit.xml
Tips for creating unittests:
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
See `https://github.com/doctrine/doctrine2/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
example.
## Travis
We automatically run your pull request through [Travis CI](http://www.travis-ci.org)

View File

@ -1,6 +1,7 @@
# Doctrine 2 ORM
Master: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=master)](http://travis-ci.org/doctrine/doctrine2)
2.4: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.4)](http://travis-ci.org/doctrine/doctrine2)
2.3: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.3)](http://travis-ci.org/doctrine/doctrine2)
2.2: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.2)](http://travis-ci.org/doctrine/doctrine2)
2.1: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.1.x)](http://travis-ci.org/doctrine/doctrine2)

View File

@ -5,6 +5,15 @@
This method generates the column name for fields of embedded objects. If you implement your custom NamingStrategy, you
now also need to implement this new method.
## Updates on entities scheduled for deletion are no longer processed
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
produce an UPDATE statement to be executed right before the DELETE statement. The entity in question
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
calculation logic is optimized away.
# Upgrade to 2.4

363
docs/LICENSE.md Normal file
View File

@ -0,0 +1,363 @@
The Doctrine2 documentation is licensed under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
Creative Commons Legal Code
Attribution-NonCommercial-ShareAlike 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and
other pre-existing works, such as a translation, adaptation,
derivative work, arrangement of music or other alterations of a
literary or artistic work, or phonogram or performance and includes
cinematographic adaptations or any other form in which the Work may be
recast, transformed, or adapted including in any form recognizably
derived from the original, except that a work that constitutes a
Collection will not be considered an Adaptation for the purpose of
this License. For the avoidance of doubt, where the Work is a musical
work, performance or phonogram, the synchronization of the Work in
timed-relation with a moving image ("synching") will be considered an
Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or
broadcasts, or other works or subject matter other than works listed
in Section 1(g) below, which, by reason of the selection and
arrangement of their contents, constitute intellectual creations, in
which the Work is included in its entirety in unmodified form along
with one or more other contributions, each constituting separate and
independent works in themselves, which together are assembled into a
collective whole. A work that constitutes a Collection will not be
considered an Adaptation (as defined above) for the purposes of this
License.
c. "Distribute" means to make available to the public the original and
copies of the Work or Adaptation, as appropriate, through sale or
other transfer of ownership.
d. "License Elements" means the following high-level license attributes
as selected by Licensor and indicated in the title of this License:
Attribution, Noncommercial, ShareAlike.
e. "Licensor" means the individual, individuals, entity or entities that
offer(s) the Work under the terms of this License.
f. "Original Author" means, in the case of a literary or artistic work,
the individual, individuals, entity or entities who created the Work
or if no individual or entity can be identified, the publisher; and in
addition (i) in the case of a performance the actors, singers,
musicians, dancers, and other persons who act, sing, deliver, declaim,
play in, interpret or otherwise perform literary or artistic works or
expressions of folklore; (ii) in the case of a phonogram the producer
being the person or legal entity who first fixes the sounds of a
performance or other sounds; and, (iii) in the case of broadcasts, the
organization that transmits the broadcast.
g. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the
literary, scientific and artistic domain, whatever may be the mode or
form of its expression including digital form, such as a book,
pamphlet and other writing; a lecture, address, sermon or other work
of the same nature; a dramatic or dramatico-musical work; a
choreographic work or entertainment in dumb show; a musical
composition with or without words; a cinematographic work to which are
assimilated works expressed by a process analogous to cinematography;
a work of drawing, painting, architecture, sculpture, engraving or
lithography; a photographic work to which are assimilated works
expressed by a process analogous to photography; a work of applied
art; an illustration, map, plan, sketch or three-dimensional work
relative to geography, topography, architecture or science; a
performance; a broadcast; a phonogram; a compilation of data to the
extent it is protected as a copyrightable work; or a work performed by
a variety or circus performer to the extent it is not otherwise
considered a literary or artistic work.
h. "You" means an individual or entity exercising rights under this
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
i. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
j. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation,
including any translation in any medium, takes reasonable steps to
clearly label, demarcate or otherwise identify that changes were made
to the original Work. For example, a translation could be marked "The
original work was translated from English to Spanish," or a
modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated
in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats. Subject to Section 8(f), all rights not expressly
granted by Licensor are hereby reserved, including but not limited to the
rights described in Section 4(e).
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(d), as requested. If You create an
Adaptation, upon notice from any Licensor You must, to the extent
practicable, remove from the Adaptation any credit as required by
Section 4(d), as requested.
b. You may Distribute or Publicly Perform an Adaptation only under: (i)
the terms of this License; (ii) a later version of this License with
the same License Elements as this License; (iii) a Creative Commons
jurisdiction license (either this or a later license version) that
contains the same License Elements as this License (e.g.,
Attribution-NonCommercial-ShareAlike 3.0 US) ("Applicable License").
You must include a copy of, or the URI, for Applicable License with
every copy of each Adaptation You Distribute or Publicly Perform. You
may not offer or impose any terms on the Adaptation that restrict the
terms of the Applicable License or the ability of the recipient of the
Adaptation to exercise the rights granted to that recipient under the
terms of the Applicable License. You must keep intact all notices that
refer to the Applicable License and to the disclaimer of warranties
with every copy of the Work as included in the Adaptation You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Adaptation, You may not impose any effective technological
measures on the Adaptation that restrict the ability of a recipient of
the Adaptation from You to exercise the rights granted to that
recipient under the terms of the Applicable License. This Section 4(b)
applies to the Adaptation as incorporated in a Collection, but this
does not require the Collection apart from the Adaptation itself to be
made subject to the terms of the Applicable License.
c. You may not exercise any of the rights granted to You in Section 3
above in any manner that is primarily intended for or directed toward
commercial advantage or private monetary compensation. The exchange of
the Work for other copyrighted works by means of digital file-sharing
or otherwise shall not be considered to be intended for or directed
toward commercial advantage or private monetary compensation, provided
there is no payment of any monetary compensation in con-nection with
the exchange of copyrighted works.
d. If You Distribute, or Publicly Perform the Work or any Adaptations or
Collections, You must, unless a request has been made pursuant to
Section 4(a), keep intact all copyright notices for the Work and
provide, reasonable to the medium or means You are utilizing: (i) the
name of the Original Author (or pseudonym, if applicable) if supplied,
and/or if the Original Author and/or Licensor designate another party
or parties (e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor's copyright notice,
terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the
extent reasonably practicable, the URI, if any, that Licensor
specifies to be associated with the Work, unless such URI does not
refer to the copyright notice or licensing information for the Work;
and, (iv) consistent with Section 3(b), in the case of an Adaptation,
a credit identifying the use of the Work in the Adaptation (e.g.,
"French translation of the Work by Original Author," or "Screenplay
based on original Work by Original Author"). The credit required by
this Section 4(d) may be implemented in any reasonable manner;
provided, however, that in the case of a Adaptation or Collection, at
a minimum such credit will appear, if a credit for all contributing
authors of the Adaptation or Collection appears, then as part of these
credits and in a manner at least as prominent as the credits for the
other contributing authors. For the avoidance of doubt, You may only
use the credit required by this Section for the purpose of attribution
in the manner set out above and, by exercising Your rights under this
License, You may not implicitly or explicitly assert or imply any
connection with, sponsorship or endorsement by the Original Author,
Licensor and/or Attribution Parties, as appropriate, of You or Your
use of the Work, without the separate, express prior written
permission of the Original Author, Licensor and/or Attribution
Parties.
e. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme cannot be waived, the Licensor
reserves the exclusive right to collect such royalties for any
exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme can be waived, the Licensor reserves
the exclusive right to collect such royalties for any exercise by
You of the rights granted under this License if Your exercise of
such rights is for a purpose or use which is otherwise than
noncommercial as permitted under Section 4(c) and otherwise waives
the right to collect royalties through any statutory or compulsory
licensing scheme; and,
iii. Voluntary License Schemes. The Licensor reserves the right to
collect royalties, whether individually or, in the event that the
Licensor is a member of a collecting society that administers
voluntary licensing schemes, via that society, from any exercise
by You of the rights granted under this License that is for a
purpose or use which is otherwise than noncommercial as permitted
under Section 4(c).
f. Except as otherwise agreed in writing by the Licensor or as may be
otherwise permitted by applicable law, if You Reproduce, Distribute or
Publicly Perform the Work either by itself or as part of any
Adaptations or Collections, You must not distort, mutilate, modify or
take other derogatory action in relation to the Work which would be
prejudicial to the Original Author's honor or reputation. Licensor
agrees that in those jurisdictions (e.g. Japan), in which any exercise
of the right granted in Section 3(b) of this License (the right to
make Adaptations) would be deemed to be a distortion, mutilation,
modification or other derogatory action prejudicial to the Original
Author's honor and reputation, the Licensor will waive or not assert,
as appropriate, this Section, to the fullest extent permitted by the
applicable national law, to enable You to reasonably exercise Your
right under Section 3(b) of this License (right to make Adaptations)
but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING AND TO THE
FULLEST EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR OFFERS THE WORK AS-IS
AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE
WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT
LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT
DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED
WARRANTIES, SO THIS EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate
automatically upon any breach by You of the terms of this License.
Individuals or entities who have received Adaptations or Collections
from You under this License, however, will not have their licenses
terminated provided such individuals or entities remain in full
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work).
Notwithstanding the above, Licensor reserves the right to release the
Work under different license terms or to stop distributing the Work at
any time; provided, however that any such election will not serve to
withdraw this License (or any other license that has been, or is
required to be, granted under the terms of this License), and this
License will continue in full force and effect unless terminated as
stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same
terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
offers to the recipient a license to the original Work on the same
terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this License, and without further action
by the parties to this agreement, such provision shall be reformed to
the minimum extent necessary to make such provision valid and
enforceable.
d. No term or provision of this License shall be deemed waived and no
breach consented to unless such waiver or consent shall be in writing
and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings,
agreements or representations with respect to the Work not specified
here. Licensor shall not be bound by any additional provisions that
may appear in any communication from You. This License may not be
modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this
License were drafted utilizing the terminology of the Berne Convention
for the Protection of Literary and Artistic Works (as amended on
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
and the Universal Copyright Convention (as revised on July 24, 1971).
These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced
according to the corresponding provisions of the implementation of
those treaty provisions in the applicable national law. If the
standard suite of rights granted under applicable copyright law
includes additional rights not granted under this License, such
additional rights are deemed to be included in the License; this
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, Creative Commons does not authorize
the use by either party of the trademark "Creative Commons" or any
related trademark or logo of Creative Commons without the prior
written consent of Creative Commons. Any permitted use will be in
compliance with Creative Commons' then-current trademark usage
guidelines, as may be published on its website or otherwise made
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of this License.
Creative Commons may be contacted at http://creativecommons.org/.

View File

@ -67,7 +67,7 @@ Advanced Topics
* :doc:`Architecture <reference/architecture>`
* :doc:`Advanced Configuration <reference/advanced-configuration>`
* :doc:`Limitations and knowns issues <reference/limitations-and-known-issues>`
* :doc:`Limitations and known issues <reference/limitations-and-known-issues>`
* :doc:`Commandline Tools <reference/tools>`
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
@ -78,6 +78,8 @@ Advanced Topics
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
* :doc:`Best Practices <reference/best-practices>`
* :doc:`Metadata Drivers <reference/metadata-drivers>`
* :doc:`Batch Processing <reference/batch-processing>`
* :doc:`Second Level Cache <reference/second-level-cache>`
Tutorials
---------

View File

@ -35,6 +35,7 @@ Index
- :ref:`@Column <annref_column>`
- :ref:`@ColumnResult <annref_column_result>`
- :ref:`@Cache <annref_cache>`
- :ref:`@ChangeTrackingPolicy <annref_changetrackingpolicy>`
- :ref:`@DiscriminatorColumn <annref_discriminatorcolumn>`
- :ref:`@DiscriminatorMap <annref_discriminatormap>`
@ -152,6 +153,17 @@ Required attributes:
- **name**: The name of a column in the SELECT clause of a SQL query
.. _annref_cache:
@Cache
~~~~~~~~~~~~~~
Add caching strategy to a root entity or a collection.
Optional attributes:
- **usage**: One of ``READ_ONLY``, ``READ_READ_WRITE`` or ``NONSTRICT_READ_WRITE``, By default this is ``READ_ONLY``.
- **region**: An specific region name
.. _annref_changetrackingpolicy:
@ChangeTrackingPolicy
@ -519,7 +531,7 @@ details of the database join table. If you do not specify
@JoinTable on these relations reasonable mapping defaults apply
using the affected table and the column names.
Required attributes:
Optional attributes:
- **name**: Database name of the join-table

View File

@ -176,7 +176,7 @@ default.
length: 140
postedAt:
type: datetime
name: posted_at
column: posted_at
When we don't explicitly specify a column name via the ``name`` option, Doctrine
assumes the field name is also the column name. This means that:
@ -340,15 +340,16 @@ Here is the list of possible generation strategies:
- ``AUTO`` (default): Tells Doctrine to pick the strategy that is
preferred by the used database platform. The preferred strategies
are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle
and PostgreSQL. This strategy provides full portability.
are IDENTITY for MySQL, SQLite, MsSQL and SQL Anywhere and SEQUENCE
for Oracle and PostgreSQL. This strategy provides full portability.
- ``SEQUENCE``: Tells Doctrine to use a database sequence for ID
generation. This strategy does currently not provide full
portability. Sequences are supported by Oracle and PostgreSql.
portability. Sequences are supported by Oracle, PostgreSql and
SQL Anywhere.
- ``IDENTITY``: Tells Doctrine to use special identity columns in
the database that generate a value on insertion of a row. This
strategy does currently not provide full portability and is
supported by the following platforms: MySQL/SQLite
supported by the following platforms: MySQL/SQLite/SQL Anywhere
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
- ``TABLE``: Tells Doctrine to use a separate table for ID
generation. This strategy provides full portability.
@ -391,7 +392,7 @@ besides specifying the sequence's name:
</id>
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:

View File

@ -65,7 +65,7 @@ Memcache
In order to use the Memcache cache driver you must have it compiled
and enabled in your php.ini. You can read about Memcache
` on the PHP website <http://us2.php.net/memcache>`_. It will
` on the PHP website <http://php.net/memcache>`_. It will
give you a little background information about what it is and how
you can use it as well as how to install it.
@ -82,6 +82,31 @@ driver by itself.
$cacheDriver->setMemcache($memcache);
$cacheDriver->save('cache_id', 'my_data');
Memcached
~~~~~~~~
Memcached is a more recent and complete alternative extension to
Memcache.
In order to use the Memcached cache driver you must have it compiled
and enabled in your php.ini. You can read about Memcached
` on the PHP website <http://php.net/memcached>`_. It will
give you a little background information about what it is and how
you can use it as well as how to install it.
Below is a simple example of how you could use the Memcached cache
driver by itself.
.. code-block:: php
<?php
$memcached = new Memcached();
$memcached->addServer('memcache_host', 11211);
$cacheDriver = new \Doctrine\Common\Cache\MemcachedCache();
$cacheDriver->setMemcached($memcached);
$cacheDriver->save('cache_id', 'my_data');
Xcache
~~~~~~

View File

@ -1370,6 +1370,9 @@ Given that there are 10 users and corresponding addresses in the database the ex
SELECT * FROM users;
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.
EBNF
----

View File

@ -558,3 +558,22 @@ Things to note:
- The "attribute override" specifies the overrides base on the property name.
- The column type *CANNOT* be changed. if the column type is not equals you got a ``MappingException``
- The override can redefine all the column except the type.
Query the Type
--------------
It may happen that the entities of a special type should be queried. Because there
is no direct access to the discriminator column, Doctrine provides the
``INSTANCE OF`` construct.
The following example shows how to use ``INSTANCE OF``. There is a three level hierarchy
with a base entity ``NaturalPerson`` which is extended by ``Staff`` which in turn
is extended by ``Technician``.
Querying for the staffs without getting any technicians can be achieved by this DQL:
.. code-block:: php
<?php
$query = $em->createQuery("SELECT staff FROM MyProject\Model\Staff staff WHERE staff INSTANCE OF MyProject\Model\Staff");
$staffs = $query->getResult();

View File

@ -0,0 +1,731 @@
The Second Level Cache
======================
.. note::
The second level cache functionality is marked as experimental for now. It
is a very complex feature and we cannot guarantee yet that it works stable
in all cases.
The Second Level Cache is designed to reduce the amount of necessary database access.
It sits between your application and the database to avoid the number of database hits as much as possible.
When turned on, entities will be first searched in cache and if they are not found,
a database query will be fired an then the entity result will be stored in a cache provider.
There are some flavors of caching available, but is better to cache read-only data.
Be aware that caches are not aware of changes made to the persistent store by another application.
They can, however, be configured to regularly expire cached data.
Caching Regions
---------------
Second level cache does not store instances of an entity, instead it caches only entity identifier and values.
Each entity class, collection association and query has its region, where values of each instance are stored.
Caching Regions are specific region into the cache provider that might store entities, collection or queries.
Each cache region resides in a specific cache namespace and has its own lifetime configuration.
Notice that when caching collection and queries only identifiers are stored.
The entity values will be stored in its own region
Something like below for an entity region :
.. code-block:: php
<?php
[
'region_name:entity_1_hash' => ['id'=> 1, 'name' => 'FooBar', 'associationName'=>null],
'region_name:entity_2_hash' => ['id'=> 2, 'name' => 'Foo', 'associationName'=>['id'=>11]],
'region_name:entity_3_hash' => ['id'=> 3, 'name' => 'Bar', 'associationName'=>['id'=>22]]
];
If the entity holds a collection that also needs to be cached.
An collection region could look something like :
.. code-block:: php
<?php
[
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId'=> 1, 'list' => [1, 2, 3]],
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId'=> 2, 'list' => [2, 3]],
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId'=> 3, 'list' => [2, 4]]
];
A query region might be something like :
.. code-block:: php
<?php
[
'region_name:query_1_hash' => ['list' => [1, 2, 3]],
'region_name:query_2_hash' => ['list' => [2, 3]],
'region_name:query_3_hash' => ['list' => [2, 4]]
];
.. note::
The following data structures represents now the cache will looks like, this is not actual cached data.
.. _reference-second-level-cache-regions:
Cache Regions
-------------
``Doctrine\ORM\Cache\Region\DefaultRegion`` It's the default implementation.
A simplest cache region compatible with all doctrine-cache drivers but does not support locking.
``Doctrine\ORM\Cache\Region`` and ``Doctrine\ORM\Cache\ConcurrentRegion``
Defines contracts that should be implemented by a cache provider.
It allows you to provide your own cache implementation that might take advantage of specific cache driver.
If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegion``; ``CacheRegion`` otherwise.
Cache region
~~~~~~~~~~~~
Defines a contract for accessing a particular region.
``Doctrine\ORM\Cache\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/>`_.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
A ``Doctrine\ORM\Cache\ConcurrentRegion`` is designed to store concurrently managed data region.
By default, Doctrine provides a very simple implementation based on file locks ``Doctrine\ORM\Cache\Region\FileLockRegion``.
If you want to use an ``READ_WRITE`` cache, you should consider providing your own cache region.
``Doctrine\ORM\Cache\ConcurrentRegion``
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/>`_.
Cache region
~~~~~~~~~~~~
``Doctrine\ORM\Cache\TimestampRegion``
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/>`_.
.. _reference-second-level-cache-mode:
Caching mode
------------
* ``READ_ONLY`` (DEFAULT)
* Can do reads, inserts and deletes, cannot perform updates or employ any locks.
* Useful for data that is read frequently but never updated.
* Best performer.
* It is Simple.
* ``NONSTRICT_READ_WRITE``
* Read Write Cache doesnt employ any locks but can do reads, inserts, updates and deletes.
* Good if the application needs to update data rarely.
* ``READ_WRITE``
* Read Write cache employs locks before update/delete.
* Use if data needs to be updated.
* Slowest strategy.
* To use it a the cache region implementation must support locking.
Built-in cached persisters
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cached persisters are responsible to access cache regions.
+-----------------------+-------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+===============================================================================+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCacheCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------+
Configuration
-------------
Doctrine allows you to specify configurations and some points of extension for the second-level-cache
Enable Second Level Cache Enabled
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To enable the second-level-cache, you should provide a cache factory
``\Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
.. code-block:: php
<?php
/* var $config \Doctrine\ORM\Cache\RegionsConfiguration */
/* var $cache \Doctrine\Common\Cache\CacheProvider */
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($config, $cache);
//Enable second-level-cache
$config->setSecondLevelCacheEnabled();
//Cache factory
$config->getSecondLevelCacheConfiguration()
->setCacheFactory($factory);
Cache Factory
~~~~~~~~~~~~~
Cache Factory is the main point of extension.
It allows you to provide a specific implementation of the following components :
* ``QueryCache`` Store and retrieve query cache results.
* ``CachedEntityPersister`` Store and retrieve entity results.
* ``CachedCollectionPersister`` Store and retrieve query results.
* ``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/>`_.
Region Lifetime
~~~~~~~~~~~~~~~
To specify a default lifetime for all regions or specify a different lifetime for a specific region.
.. code-block:: php
<?php
/* var $config \Doctrine\ORM\Configuration */
/* var $cacheConfig \Doctrine\ORM\Configuration */
$cacheConfig = $config->getSecondLevelCacheConfiguration();
$regionConfig = $cacheConfig->getRegionsConfiguration();
//Cache Region lifetime
$regionConfig->setLifetime('my_entity_region', 3600); // Time to live for a specific region; In seconds
$regionConfig->setDefaultLifetime(7200); // Default time to live; In seconds
Cache Log
~~~~~~~~~
By providing a cache logger you should be able to get information about all cache operations such as hits, misses and puts.
``\Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
.. code-block:: php
<?php
/* var $config \Doctrine\ORM\Configuration */
$logger = \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
//Cache logger
$config->setSecondLevelCacheEnabled(true);
$config->getSecondLevelCacheConfiguration()
->setCacheLogger($logger);
// Collect cache statistics
// Get the number of entries successfully retrieved from a specific region.
$logger->getRegionHitCount('my_entity_region');
// Get the number of cached entries *not* found in a specific region.
$logger->getRegionMissCount('my_entity_region');
// Get the number of cacheable entries put in cache.
$logger->getRegionPutCount('my_entity_region');
// Get the total number of put in all regions.
$logger->getPutCount();
// Get the total number of entries successfully retrieved from all regions.
$logger->getHitCount();
// Get the total number of cached entries *not* found in all regions.
$logger->getMissCount();
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/>`_.
Entity cache definition
-----------------------
* Entity cache configuration allows you to define the caching strategy and region for an entity.
* ``usage`` Specifies the caching strategy: ``READ_ONLY``, ``NONSTRICT_READ_WRITE``, ``READ_WRITE``. see :ref:`reference-second-level-cache-mode`
* ``region`` Optional value that specifies the name of the second level cache region.
.. configuration-block::
.. code-block:: php
<?php
/**
* @Entity
* @Cache(usage="READ_ONLY", region="my_entity_region")
*/
class Country
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
/**
* @Column(unique=true)
*/
protected $name;
// other properties and methods
}
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Country">
<cache usage="READ_ONLY" region="my_entity_region" />
<id name="id" type="integer" column="id">
<generator strategy="IDENTITY"/>
</id>
<field name="name" type="string" column="name"/>
</entity>
</doctrine-mapping>
.. code-block:: yaml
Country:
type: entity
cache:
usage : READ_ONLY
region : my_entity_region
id:
id:
type: integer
id: true
generator:
strategy: IDENTITY
fields:
name:
type: string
Association cache definition
----------------------------
The most common use case is to cache entities. But we can also cache relationships.
It caches the primary keys of association and cache each element will be cached into its region.
.. configuration-block::
.. code-block:: php
<?php
/**
* @Entity
* @Cache("NONSTRICT_READ_WRITE")
*/
class State
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
/**
* @Column(unique=true)
*/
protected $name;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @ManyToOne(targetEntity="Country")
* @JoinColumn(name="country_id", referencedColumnName="id")
*/
protected $country;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state")
*/
protected $cities;
// other properties and methods
}
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="State">
<cache usage="NONSTRICT_READ_WRITE" />
<id name="id" type="integer" column="id">
<generator strategy="IDENTITY"/>
</id>
<field name="name" type="string" column="name"/>
<many-to-one field="country" target-entity="Country">
<cache usage="NONSTRICT_READ_WRITE" />
<join-columns>
<join-column name="country_id" referenced-column-name="id"/>
</join-columns>
</many-to-one>
<one-to-many field="cities" target-entity="City" mapped-by="state">
<cache usage="NONSTRICT_READ_WRITE"/>
</one-to-many>
</entity>
</doctrine-mapping>
.. code-block:: yaml
State:
type: entity
cache:
usage : NONSTRICT_READ_WRITE
id:
id:
type: integer
id: true
generator:
strategy: IDENTITY
fields:
name:
type: string
manyToOne:
state:
targetEntity: Country
joinColumns:
country_id:
referencedColumnName: id
cache:
usage : NONSTRICT_READ_WRITE
oneToMany:
cities:
targetEntity:City
mappedBy: state
cache:
usage : NONSTRICT_READ_WRITE
Cache usage
~~~~~~~~~~~
Basic entity cache
.. code-block:: php
<?php
$em->persist(new Country($name));
$em->flush(); // Hit database to insert the row and put into cache
$em->clear(); // Clear entity manager
$country1 = $em->find('Country', 1); // Retrieve item from cache
$country->setName("New Name");
$em->persist($state);
$em->flush(); // Hit database to update the row and update cache
$em->clear(); // Clear entity manager
$country2 = $em->find('Country', 1); // Retrieve item from cache
// Notice that $country1 and $country2 are not the same instance.
Association cache
.. code-block:: php
<?php
// Hit database to insert the row and put into cache
$em->persist(new State($name, $country));
$em->flush();
// Clear entity manager
$em->clear();
// Retrieve item from cache
$state = $em->find('State', 1);
// Hit database to update the row and update cache entry
$state->setName("New Name");
$em->persist($state);
$em->flush();
// Create a new collection item
$city = new City($name, $state);
$state->addCity($city);
// Hit database to insert new collection item,
// put entity and collection cache into cache.
$em->persist($city);
$em->persist($state);
$em->flush();
// Clear entity manager
$em->clear();
// Retrieve item from cache
$state = $em->find('State', 1);
// Retrieve association from cache
$country = $state->getCountry();
// Retrieve collection from cache
$cities = $state->getCities();
echo $country->getName();
echo $state->getName();
// Retrieve each collection item from cache
foreach ($cities as $city) {
echo $city->getName();
}
.. note::
Notice that all entities should be marked as cacheable.
Using the query cache
---------------------
The second level cache stores the entities, associations and collections.
The query cache stores the results of the query but as identifiers, entity values are actually stored in the 2nd level cache.
.. note::
Query cache should always be used in conjunction with the second-level-cache for those entities which should be cached.
.. code-block:: php
<?php
/* var $em \Doctrine\ORM\EntityManager */
// Execute database query, store query cache and entity cache
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheable(true)
->getResult();
$em->clear()
// Check if query result is valid and load entities from cache
$result2 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheable(true)
->getResult();
Cache mode
~~~~~~~~~~
The Cache Mode controls how a particular query interacts with the second-level cache:
* ``Cache::MODE_GET`` - May read items from the cache, but will not add items.
* ``Cache::MODE_PUT`` - Will never read items from the cache, but will add items to the cache as it reads them from the database.
* ``Cache::MODE_NORMAL`` - May read items from the cache, and add items to the cache.
* ``Cache::MODE_REFRESH`` - The query will never read items from the cache, but will refresh items to the cache as it reads them from the database.
.. code-block:: php
<?php
/** var $em \Doctrine\ORM\EntityManager */
// Will refresh the query cache and all entities the cache as it reads from the database.
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheMode(Cache::MODE_GET)
->setCacheable(true)
->getResult();
.. note::
The the default query cache mode is ```Cache::MODE_NORMAL```
DELETE / UPDATE queries
~~~~~~~~~~~~~~~~~~~~~~~
DQL UPDATE / DELETE statements are ported directly into a database and bypass the second-level cache,
Entities that are already cached will NOT be invalidated.
However the cached data could be evicted using the cache API or an special query hint.
Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_CACHE_EVICT``
.. code-block:: php
<?php
// Execute and invalidate
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->setHint(Query::HINT_CACHE_EVICT, true)
->execute();
Execute the ``UPDATE`` and invalidate ``all cache entries`` using the cache API
.. code-block:: php
<?php
// Execute
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->execute();
// Invoke Cache API
$em->getCache()->evictEntityRegion('Entity\Country');
Execute the ``UPDATE`` and invalidate ``a specific cache entry`` using the cache API
.. code-block:: php
<?php
// Execute
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->execute();
// Invoke Cache API
$em->getCache()->evictEntity('Entity\Country', 1);
Using the repository query cache
---------------------
As well as ``Query Cache`` all persister queries store only identifier values for an individual query.
All persister use a single timestamps cache region keeps track of the last update for each persister,
When a query is loaded from cache, the timestamp region is checked for the last update for that persister.
Using the last update timestamps as part of the query key invalidate the cache key when an update occurs.
.. code-block:: php
<?php
// load from database and store cache query key hashing the query + parameters + last timestamp cache region..
$entities = $em->getRepository('Entity\Country')->findAll();
// load from query and entities from cache..
$entities = $em->getRepository('Entity\Country')->findAll();
// update the timestamp cache region for Country
$em->persist(new Country('zombieland'));
$em->flush();
$em->clear();
// Reload from database.
// At this point the query cache key if not logger valid, the select goes straight
$entities = $em->getRepository('Entity\Country')->findAll();
Cache API
---------
Caches are not aware of changes made by another application.
However, you can use the cache API to check / invalidate cache entries.
.. code-block:: php
<?php
/* var $cache \Doctrine\ORM\Cache */
$cache = $em->getCache();
$cache->containsEntity('Entity\State', 1) // Check if the cache exists
$cache->evictEntity('Entity\State', 1); // Remove an entity from cache
$cache->evictEntityRegion('Entity\State'); // Remove all entities from cache
$cache->containsCollection('Entity\State', 'cities', 1); // Check if the cache exists
$cache->evictCollection('Entity\State', 'cities', 1); // Remove an entity collection from cache
$cache->evictCollectionRegion('Entity\State', 'cities'); // Remove all collections from cache
Limitations
-----------
Composite primary key
~~~~~~~~~~~~~~~~~~~~~
Composite primary key are supported by second level cache,
however when one of the keys is an association the cached entity should always be retrieved using the association identifier.
For performance reasons the cache API does not extract from composite primary key.
.. code-block:: php
<?php
/**
* @Entity
*/
class Reference
{
/**
* @Id
* @ManyToOne(targetEntity="Article", inversedBy="references")
* @JoinColumn(name="source_id", referencedColumnName="article_id")
*/
private $source;
/**
* @Id
* @ManyToOne(targetEntity="Article")
* @JoinColumn(name="target_id", referencedColumnName="article_id")
*/
private $target;
}
// Supported
/** @var $article Article */
$article = $em->find('Article', 1);
// Supported
/** @var $article Article */
$article = $em->find('Article', $article);
// Supported
$id = array('source' => 1, 'target' => 2);
$reference = $em->find('Reference', $id);
// NOT Supported
$id = array('source' => new Article(1), 'target' => new Article(2));
$reference = $em->find('Reference', $id);
Distribute environments
~~~~~~~~~~~~~~~~~~~~~~~
Some cache driver are not meant to be used in a distribute environment
Load-balancer for distributing workloads across multiple computing resources
should be used in conjunction with distributed caching system such as memcached, redis, riak ...
Caches should be used with care when using a load-balancer if you don't share the cache.
While using APC or any file based cache update occurred in a specific machine would not reflect to the cache in other machines.

View File

@ -403,7 +403,7 @@ There are two approaches to handle this problem in your code:
Transitive persistence / Cascade Operations
-------------------------------------------
Persisting, removing, detaching and merging individual entities can
Persisting, removing, detaching, refreshing and merging individual entities can
become pretty cumbersome, especially when a highly interweaved object graph
is involved. Therefore Doctrine 2 provides a
mechanism for transitive persistence through cascading of these
@ -419,7 +419,8 @@ The following cascade options exist:
- remove : Cascades remove operations to the associated entities.
- merge : Cascades merge operations to the associated entities.
- detach : Cascades detach operations to the associated entities.
- all : Cascades persist, remove, merge and detach operations to
- refresh : Cascades refresh operations to the associated entities.
- all : Cascades persist, remove, merge, refresh and detach operations to
associated entities.
.. note::

View File

@ -72,7 +72,9 @@ of several common elements:
# Doctrine.Tests.ORM.Mapping.User.dcm.yml
Doctrine\Tests\ORM\Mapping\User:
type: entity
repositoryClass: Doctrine\Tests\ORM\Mapping\UserRepository
table: cms_users
readOnly: true
indexes:
name_index:
columns: [ name ]
@ -85,6 +87,13 @@ of several common elements:
name:
type: string
length: 50
email:
type: string
length: 32
column: user_email
unique: true
options:
fixed: true
oneToOne:
address:
targetEntity: Address

View File

@ -54,10 +54,13 @@ Reference Guide
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
reference/filters.rst
reference/namingstrategy.rst
reference/advanced-configuration.rst
tutorials/pagination
reference/filters
reference/namingstrategy
reference/installation
reference/advanced-configuration
reference/second-level-cache
Cookbook
--------
@ -82,4 +85,5 @@ Cookbook
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session
cookbook/resolve-target-entity-listener

View File

@ -66,17 +66,17 @@ Bug Tracker domain model from the
documentation. Reading their documentation we can extract the
requirements:
- A Bugs has a description, creation date, status, reporter and
- A Bug has a description, creation date, status, reporter and
engineer
- A bug can occur on different products (platforms)
- Products have a name.
- Bug Reporter and Engineers are both Users of the System.
- A user can create new bugs.
- The assigned engineer can close a bug.
- A user can see all his reported or assigned bugs.
- A Bug can occur on different Products (platforms)
- A Product has a name.
- Bug reporters and engineers are both Users of the system.
- A User can create new Bugs.
- The assigned engineer can close a Bug.
- A User can see all his reported or assigned Bugs.
- Bugs can be paginated through a list-view.
Setup Project
Project Setup
-------------
Create a new empty folder for this tutorial project, for example
@ -170,9 +170,9 @@ factory method.
Generating the Database Schema
------------------------------
Now that we have defined the Metadata Mappings and bootstrapped the
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
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.
@ -218,15 +218,13 @@ Or use the update functionality:
The updating of databases uses a Diff Algorithm for a given
Database Schema, a cornerstone of the ``Doctrine\DBAL`` package,
which can even be used without the Doctrine ORM package. However
its not available in SQLite since it does not support ALTER TABLE.
which can even be used without the Doctrine ORM package.
Starting with the Product
-------------------------
We start with the Product entity requirements, because it is the most simple one
to get started. Create a ``src/Product.php`` file and put the ``Product``
entity definition in there:
We start with the simplest entity, the Product. Create a ``src/Product.php`` file to contain the ``Product``
entity definition:
.. code-block:: php
@ -259,10 +257,16 @@ entity definition in there:
}
}
Note how the properties have getter and setter methods defined except
``$id``. To access data from entities Doctrine 2 uses the Reflection API, so it
is possible for Doctrine to access the value of ``$id``. You don't have to
take Doctrine into account when designing access to the state of your objects.
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
manipulate the entities in ways that it could not if you just
directly set the values with ``entity#field = foo;``
The id field has no setter since, generally speaking, your code
should not set this value since it represents a database id value.
(Note that Doctrine itself can still set the value using the
Reflection API instead of a defined setter function)
The next step for persistence with Doctrine is to describe the
structure of the ``Product`` entity to Doctrine using a metadata
@ -326,15 +330,15 @@ References in the text will be made to the XML mapping.
type: string
The top-level ``entity`` definition tag specifies information about
the class and table-name. The primitive type ``Product::$name`` is
defined as ``field`` attributes. The Id property is defined with
the ``id`` tag. The id has a ``generator`` tag nested inside which
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
uses the database platforms native id generation strategy (for
example AUTO INCREMENT in the case of MySql or Sequences in the
case of PostgreSql and Oracle.
case of PostgreSql and Oracle).
You have to update the database now, because we have a first Entity now:
Now that we have defined our first entity, lets update the database:
::
@ -361,7 +365,7 @@ Now create a new script that will insert products into the database:
echo "Created Product with ID " . $product->getId() . "\n";
Call this script from the command line to see how new products are created:
Call this script from the command-line to see how new products are created:
::
@ -381,9 +385,9 @@ better than in a scenario where updates are done for each entity in isolation.
Doctrine follows the UnitOfWork pattern which additionally detects all entities
that were fetched and have changed during the request. You don't have to keep track of
entities yourself, when Doctrine already knowns about them.
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
As a next step we want to fetch a list of all the Products. Let's create a
new script for this:
.. code-block:: php
@ -400,7 +404,7 @@ new script for this:
}
The ``EntityManager#getRepository()`` method can create a finder object (called
repository) for every entity. It is provided by Doctrine and contains some
a repository) for every entity. It is provided by Doctrine and contains some
finder methods such as ``findAll()``.
Let's continue with displaying the name of a product based on its ID:
@ -559,12 +563,12 @@ 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. With just the scalar values this model cannot describe the dynamics that we want. We
want to model references between entities.
change dates. Next we will model the dynamic relationships between the entities
by defining the references between entities.
References between objects are foreign keys in the database. You never have to
work with the foreign keys directly, only with objects that represent the
foreign key through their own identity.
(and never should) work with the foreign keys directly, only with the objects
that represent the foreign key through their own identity.
For every foreign key you either have a Doctrine ManyToOne or OneToOne
association. On the inverse sides of these foreign keys you can have
@ -733,20 +737,20 @@ methods are only used for ensuring consistency of the references.
This approach is my personal preference, you can choose whatever
method to make this work.
You can see from ``User::addReportedBug()`` and
``User::assignedToBug()`` that using this method in userland alone
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
``Bug#reporter`` or ``Bug#engineer``. Using these methods and
calling Doctrine for persistence would not update the collections
representation in the database.
Only using ``Bug::setEngineer()`` or ``Bug::setReporter()``
Only using ``Bug#setEngineer()`` or ``Bug#setReporter()``
correctly saves the relation information. We also set both
collection instance variables to protected, however with PHP 5.3's
new features Doctrine is still able to use Reflection to set and
get values from protected and private properties.
The ``Bug::$reporter`` and ``Bug::$engineer`` properties are
The ``Bug#reporter`` and ``Bug#engineer`` properties are
Many-To-One relations, which point to a User. In a normalized
relational model the foreign key is saved on the Bug's table, hence
in our object-relation model the Bug is at the owning side of the
@ -782,8 +786,8 @@ the database that points from Bugs to Products.
}
We are now finished with the domain model given the requirements.
Now we continue adding metadata mappings for the ``User`` and ``Bug``
as we did for the ``Product`` before:
Lets add metadata mappings for the ``User`` and ``Bug`` as we did for
the ``Product`` before:
.. configuration-block::
.. code-block:: php
@ -885,11 +889,9 @@ as we did for the ``Product`` before:
Here we have the entity, id and primitive type definitions.
The column names are used from the Zend\_Db\_Table examples and
have different names than the properties on the Bug class.
Additionally for the "created" field it is specified that it is of
the Type "DATETIME", which translates the YYYY-mm-dd HH:mm:ss
Database format into a PHP DateTime instance and back.
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
user entity are defined. They are created by the ``many-to-one``
@ -903,14 +905,10 @@ side of the relationship. We will see in the next example that the ``inversed-by
attribute has a counterpart ``mapped-by`` which makes that
the inverse side.
The last missing property is the ``Bug::$products`` collection. It
holds all products where the specific bug is occurring in. Again
The last definition is for the ``Bug#products`` collection. It
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. Furthermore you have to specify the
details of the many-to-many join-table and its foreign key columns.
The definition is rather complex, however relying on the XML
auto-completion I got it working easily, although I forget the
schema details all the time.
on the ``many-to-many`` tag.
The last missing definition is that of the User entity:
@ -1133,7 +1131,7 @@ The console output of this script is then:
.. note::
**Dql is not Sql**
**DQL is not SQL**
You may wonder why we start writing SQL at the beginning of this
use-case. Don't we use an ORM to get rid of all the endless
@ -1146,6 +1144,7 @@ The console output of this script is then:
of Entity-Class and property. Using the Metadata we defined before
it allows for very short distinctive and powerful queries.
An important reason why DQL is favourable to the Query API of most
ORMs is its similarity to SQL. The DQL language allows query
constructs that most ORMs don't, GROUP BY even with HAVING,
@ -1155,30 +1154,31 @@ The console output of this script is then:
throw your ORM into the dumpster, because it doesn't support some
the more powerful SQL concepts.
Besides handwriting DQL you can however also use the
``QueryBuilder`` retrieved by calling
``$entityManager->createQueryBuilder()`` which is a Query Object
around the DQL language.
As a last resort you can however also use Native SQL and a
description of the result set to retrieve entities from the
database. DQL boils down to a Native SQL statement and a
``ResultSetMapping`` instance itself. Using Native SQL you could
even use stored procedures for data retrieval, or make use of
advanced non-portable database queries like PostgreSql's recursive
queries.
Instead of handwriting DQL you can use the ``QueryBuilder`` retrieved
by calling ``$entityManager->createQueryBuilder()``. There are more
details about this in the relevant part of the documentation.
As a last resort you can still use Native SQL and a description of the
result set to retrieve entities from the database. DQL boils down to a
Native SQL statement and a ``ResultSetMapping`` instance itself. Using
Native SQL you could even use stored procedures for data retrieval, or
make use of advanced non-portable database queries like PostgreSql's
recursive queries.
Array Hydration of the Bug List
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the previous use-case we retrieved the result as their
In the previous use-case we retrieved the results as their
respective object instances. We are not limited to retrieving
objects only from Doctrine however. For a simple list view like the
previous one we only need read access to our entities and can
switch the hydration from objects to simple PHP arrays instead.
This can obviously yield considerable performance benefits for
read-only requests.
Hydration can be an expensive process so only retrieving what you need can
yield considerable performance benefits for read-only requests.
Implementing the same list view as before using array hydration we
can rewrite our code:
@ -1232,7 +1232,7 @@ write scenarios:
echo "Bug: ".$bug->getDescription()."\n";
echo "Engineer: ".$bug->getEngineer()->getName()."\n";
The output of the engineers name is fetched from the database! What is happening?
The output of the engineers name is fetched from the database! What is happening?
Since we only retrieved the bug by primary key both the engineer and reporter
are not immediately loaded from the database but are replaced by LazyLoading
@ -1278,6 +1278,14 @@ The call prints:
Bug: Something does not work!
Engineer: beberlei
.. warning::
Lazy loading additional data can be very convenient but the additional
queries create an overhead. If you know that certain fields will always
(or usually) be required by the query then you will get better performance
by explicitly retrieving them all in the first query.
Dashboard of the User
---------------------

View File

@ -56,6 +56,14 @@
<xs:enumeration value="preFlush"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="cache-usage-type">
<xs:restriction base="xs:token">
<xs:enumeration value="READ_ONLY"/>
<xs:enumeration value="READ_WRITE"/>
<xs:enumeration value="NONSTRICT_READ_WRITE"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="lifecycle-callback">
<xs:sequence>
@ -153,8 +161,14 @@
</xs:sequence>
</xs:complexType>
<xs:complexType name="cache">
<xs:attribute name="usage" type="orm:cache-usage-type" />
<xs:attribute name="region" type="xs:string" />
</xs:complexType>
<xs:complexType name="entity">
<xs:sequence>
<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"/>
<xs:element name="unique-constraints" type="orm:unique-constraints" minOccurs="0"/>
@ -368,6 +382,7 @@
<xs:element name="generator" type="orm:generator" minOccurs="0" />
<xs:element name="sequence-generator" type="orm:sequence-generator" minOccurs="0" maxOccurs="1" />
<xs:element name="custom-id-generator" type="orm:custom-id-generator" minOccurs="0" maxOccurs="1" />
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
@ -463,6 +478,7 @@
<xs:complexType name="many-to-many">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
@ -480,6 +496,7 @@
<xs:complexType name="one-to-many">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
@ -495,6 +512,7 @@
<xs:complexType name="many-to-one">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:choice minOccurs="0" maxOccurs="1">
<xs:element name="join-column" type="orm:join-column"/>
@ -513,6 +531,7 @@
<xs:complexType name="one-to-one">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:choice minOccurs="0" maxOccurs="1">
<xs:element name="join-column" type="orm:join-column"/>

View File

@ -20,11 +20,14 @@
namespace Doctrine\ORM;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\ORMInvalidArgumentException;
@ -120,6 +123,42 @@ abstract class AbstractQuery
*/
protected $_hydrationCacheProfile;
/**
* Whether to use second level cache, if available.
*
* @var boolean
*/
protected $cacheable = false;
/**
* @var boolean
*/
protected $hasCache = false;
/**
* Second level cache region name.
*
* @var string|null
*/
protected $cacheRegion;
/**
* Second level query cache mode.
*
* @var integer|null
*/
protected $cacheMode;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
*/
protected $cacheLogger;
/**
* @var integer
*/
protected $lifetime = 0;
/**
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
*
@ -127,8 +166,108 @@ abstract class AbstractQuery
*/
public function __construct(EntityManager $em)
{
$this->_em = $em;
$this->parameters = new ArrayCollection();
$this->_em = $em;
$this->parameters = new ArrayCollection();
$this->hasCache = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
if ($this->hasCache) {
$this->cacheLogger = $em->getConfiguration()
->getSecondLevelCacheConfiguration()
->getCacheLogger();
}
}
/**
*
* Enable/disable second level query (result) caching for this query.
*
* @param boolean $cacheable
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setCacheable($cacheable)
{
$this->cacheable = (boolean) $cacheable;
return $this;
}
/**
* @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
*/
public function isCacheable()
{
return $this->cacheable;
}
/**
* @param string $cacheRegion
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setCacheRegion($cacheRegion)
{
$this->cacheRegion = (string) $cacheRegion;
return $this;
}
/**
* Obtain the name of the second level query cache region in which query results will be stored
*
* @return The cache region name; NULL indicates the default region.
*/
public function getCacheRegion()
{
return $this->cacheRegion;
}
/**
* @return boolean TRUE if the query cache and second level cache are enabled, FALSE otherwise.
*/
protected function isCacheEnabled()
{
return $this->cacheable && $this->hasCache;
}
/**
* @return integer
*/
public function getLifetime()
{
return $this->lifetime;
}
/**
* Sets the life-time for this query into second level cache.
*
* @param integer $lifetime
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setLifetime($lifetime)
{
$this->lifetime = (integer) $lifetime;
return $this;
}
/**
* @return integer
*/
public function getCacheMode()
{
return $this->cacheMode;
}
/**
* @param integer $cacheMode
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setCacheMode($cacheMode)
{
$this->cacheMode = (integer) $cacheMode;
return $this;
}
/**
@ -208,9 +347,7 @@ abstract class AbstractQuery
$parameterCollection = new ArrayCollection();
foreach ($parameters as $key => $value) {
$parameter = new Query\Parameter($key, $value);
$parameterCollection->add($parameter);
$parameterCollection->add(new Parameter($key, $value));
}
$parameters = $parameterCollection;
@ -249,9 +386,7 @@ abstract class AbstractQuery
return $this;
}
$parameter = new Query\Parameter($key, $value, $type);
$this->parameters->add($parameter);
$this->parameters->add(new Parameter($key, $value, $type));
return $this;
}
@ -263,10 +398,18 @@ abstract class AbstractQuery
*
* @return array
*
* @throws ORMInvalidArgumentException
* @throws \Doctrine\ORM\ORMInvalidArgumentException
*/
public function processParameterValue($value)
{
if (is_scalar($value)) {
return $value;
}
if ($value instanceof Collection) {
$value = $value->toArray();
}
if (is_array($value)) {
foreach ($value as $key => $paramValue) {
$paramValue = $this->processParameterValue($paramValue);
@ -306,6 +449,16 @@ abstract class AbstractQuery
return $this;
}
/**
* Gets the ResultSetMapping used for hydration.
*
* @return \Doctrine\ORM\Query\ResultSetMapping
*/
protected function getResultSetMapping()
{
return $this->_resultSetMapping;
}
/**
* Allows to translate entity namespaces to full qualified names.
*
@ -747,11 +900,10 @@ abstract class AbstractQuery
$this->setParameters($parameters);
}
$rsm = $this->getResultSetMapping();
$stmt = $this->_doExecute();
return $this->_em->newHydrator($this->_hydrationMode)->iterate(
$stmt, $this->_resultSetMapping, $this->_hints
);
return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);
}
/**
@ -763,6 +915,23 @@ abstract class AbstractQuery
* @return mixed
*/
public function execute($parameters = null, $hydrationMode = null)
{
if ($this->cacheable && $this->isCacheEnabled()) {
return $this->executeUsingQueryCache($parameters, $hydrationMode);
}
return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
}
/**
* Execute query ignoring second level cache.
*
* @param ArrayCollection|array|null $parameters
* @param integer|null $hydrationMode
*
* @return mixed
*/
private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
{
if ($hydrationMode !== null) {
$this->setHydrationMode($hydrationMode);
@ -804,15 +973,51 @@ abstract class AbstractQuery
return $stmt;
}
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
$rsm = $this->getResultSetMapping();
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
$setCacheEntry($data);
return $data;
}
/**
* Load from second level cache or executes the query and put into cache.
*
* @param ArrayCollection|array|null $parameters
* @param integer|null $hydrationMode
*
* @return mixed
*/
private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
{
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL);
$queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
$result = $queryCache->get($querykey, $rsm, $this->_hints);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey);
}
return $result;
}
$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
$cached = $queryCache->put($querykey, $rsm, $result, $this->_hints);
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey);
if ($cached) {
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey);
}
}
return $result;
}
/**
* Get the result cache id to use to store the result set cache entry.
* Will return the configured id if it exists otherwise a hash will be
@ -886,4 +1091,29 @@ abstract class AbstractQuery
$this->_hints = array();
}
/**
* Generates a string of currently query to use for the cache second level cache.
*
* @return string
*/
protected function getHash()
{
$self = $this;
$query = $this->getSQL();
$hints = $this->getHints();
$params = array_map(function(Parameter $parameter) use ($self) {
// Small optimization
// Does not invoke processParameterValue for scalar values
if (is_scalar($value = $parameter->getValue())) {
return $value;
}
return $self->processParameterValue($value);
}, $this->parameters->getValues());
ksort($hints);
return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
}
}

187
lib/Doctrine/ORM/Cache.php Normal file
View File

@ -0,0 +1,187 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM;
use Doctrine\ORM\EntityManagerInterface;
/**
* Provides an API for querying/managing the second level cache regions.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface Cache
{
const DEFAULT_QUERY_REGION_NAME = 'query_cache_region';
const DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region';
/**
* May read items from the cache, but will not add items.
*/
const MODE_GET = 1;
/**
* Will never read items from the cache,
* but will add items to the cache as it reads them from the database.
*/
const MODE_PUT = 2;
/**
* May read items from the cache, and add items to the cache.
*/
const MODE_NORMAL = 3;
/**
* The query will never read items from the cache,
* but will refresh items to the cache as it reads them from the database.
*/
const MODE_REFRESH = 4;
/**
* Construct
*
* @param \Doctrine\ORM\EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em);
/**
* @param string $className The entity class.
*
* @return \Doctrine\ORM\Cache\Region|null
*/
public function getEntityCacheRegion($className);
/**
* @param string $className The entity class.
* @param string $association The field name that represents the association.
*
* @return \Doctrine\ORM\Cache\Region|null
*/
public function getCollectionCacheRegion($className, $association);
/**
* Determine whether the cache contains data for the given entity "instance".
*
* @param string $className The entity class.
* @param mixed $identifier The entity identifier
*
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
*/
public function containsEntity($className, $identifier);
/**
* Evicts the entity data for a particular entity "instance".
*
* @param string $className The entity class.
* @param mixed $identifier The entity identifier.
*
* @return void
*/
public function evictEntity($className, $identifier);
/**
* Evicts all entity data from the given region.
*
* @param string $className The entity metadata.
*
* @return void
*/
public function evictEntityRegion($className);
/**
* Evict data from all entity regions.
*
* @return void
*/
public function evictEntityRegions();
/**
* Determine whether the cache contains data for the given collection.
*
* @param string $className The entity class.
* @param string $association The field name that represents the association.
* @param mixed $ownerIdentifier The identifier of the owning entity.
*
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
*/
public function containsCollection($className, $association, $ownerIdentifier);
/**
* Evicts the cache data for the given identified collection instance.
*
* @param string $className The entity class.
* @param string $association The field name that represents the association.
* @param mixed $ownerIdentifier The identifier of the owning entity.
*
* @return void
*/
public function evictCollection($className, $association, $ownerIdentifier);
/**
* Evicts all entity data from the given region.
*
* @param string $className The entity class.
* @param string $association The field name that represents the association.
*
* @return void
*/
public function evictCollectionRegion($className, $association);
/**
* Evict data from all collection regions.
*
* @return void
*/
public function evictCollectionRegions();
/**
* Determine whether the cache contains data for the given query.
*
* @param string $regionName The cache name given to the query.
*
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
*/
public function containsQuery($regionName);
/**
* Evicts all cached query results under the given name, or default query cache if the region name is NULL.
*
* @param string|null $regionName The cache name associated to the queries being cached.
*/
public function evictQueryRegion($regionName = null);
/**
* Evict data from all query regions.
*
* @return void
*/
public function evictQueryRegions();
/**
* Get query cache by region name or create a new one if none exist.
*
* @param string|null $regionName Query cache region name, or default query cache if the region name is NULL.
*
* @return \Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name.
*/
public function getQueryCache($regionName = null);
}

View File

@ -0,0 +1,159 @@
<?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\Cache;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Cache\CacheFactory;
use Doctrine\ORM\Cache\Logging\CacheLogger;
use Doctrine\ORM\Cache\QueryCacheValidator;
use Doctrine\ORM\Cache\TimestampQueryCacheValidator;
/**
* Configuration container for second-level cache.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CacheConfiguration
{
/**
* @var \Doctrine\ORM\Cache\CacheFactory|null
*/
private $cacheFactory;
/**
* @var \Doctrine\ORM\Cache\RegionsConfiguration|null
*/
private $regionsConfig;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
*/
private $cacheLogger;
/**
* @var \Doctrine\ORM\Cache\QueryCacheValidator|null
*/
private $queryValidator;
/**
* @var string
*/
private $cacheClassName = 'Doctrine\ORM\Cache\DefaultCache';
/**
* @return \Doctrine\ORM\Cache\CacheFactory|null
*/
public function getCacheFactory()
{
return $this->cacheFactory;
}
/**
* @param \Doctrine\ORM\Cache\CacheFactory $factory
*
* @return void
*/
public function setCacheFactory(CacheFactory $factory)
{
$this->cacheFactory = $factory;
}
/**
* @return \Doctrine\ORM\Cache\Logging\CacheLogger|null
*/
public function getCacheLogger()
{
return $this->cacheLogger;
}
/**
* @param \Doctrine\ORM\Cache\Logging\CacheLogger $logger
*/
public function setCacheLogger(CacheLogger $logger)
{
$this->cacheLogger = $logger;
}
/**
* @return \Doctrine\ORM\Cache\QueryCacheValidator
*/
public function getRegionsConfiguration()
{
if ($this->regionsConfig === null) {
$this->regionsConfig = new RegionsConfiguration();
}
return $this->regionsConfig;
}
/**
* @param \Doctrine\ORM\Cache\RegionsConfiguration $regionsConfig
*/
public function setRegionsConfiguration(RegionsConfiguration $regionsConfig)
{
$this->regionsConfig = $regionsConfig;
}
/**
* @return \Doctrine\ORM\Cache\QueryCacheValidator
*/
public function getQueryValidator()
{
if ($this->queryValidator === null) {
$this->queryValidator = new TimestampQueryCacheValidator();
}
return $this->queryValidator;
}
/**
* @param \Doctrine\ORM\Cache\QueryCacheValidator $validator
*/
public function setQueryValidator(QueryCacheValidator $validator)
{
$this->queryValidator = $validator;
}
/**
* @param string $className
*
* @throws \Doctrine\ORM\ORMException If is not a \Doctrine\ORM\Cache
*/
public function setCacheClassName($className)
{
$reflectionClass = new \ReflectionClass($className);
if ( ! $reflectionClass->implementsInterface('Doctrine\ORM\Cache')) {
throw ORMException::invalidSecondLevelCache($className);
}
$this->cacheClassName = $className;
}
/**
* @return string A \Doctrine\ORM\Cache class name
*/
public function getCacheClassName()
{
return $this->cacheClassName;
}
}

View File

@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@ -16,14 +17,20 @@
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Persisters;
namespace Doctrine\ORM\Cache;
/**
* Persister for collections of basic elements / value types.
* Cache entry interface
*
* @author robo
* @todo Implementation once support for collections of basic elements (i.e. strings) is added.
* <b>IMPORTANT NOTE:</b>
*
* Fields of classes that implement CacheEntry are public for performance reason.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
abstract class ElementCollectionPersister extends AbstractCollectionPersister
interface CacheEntry
{
}

View File

@ -0,0 +1,72 @@
<?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\Cache;
use Doctrine\ORM\ORMException;
/**
* Exception for cache.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CacheException extends ORMException
{
/**
* @param string $sourceEntity
* @param string $fieldName
*
* @return \Doctrine\ORM\Cache\CacheException
*/
public static function updateReadOnlyCollection($sourceEntity, $fieldName)
{
return new self(sprintf('Cannot update a readonly collection "%s#%s"', $sourceEntity, $fieldName));
}
/**
* @param string $entityName
*
* @return \Doctrine\ORM\Cache\CacheException
*/
public static function updateReadOnlyEntity($entityName)
{
return new self(sprintf('Cannot update a readonly entity "%s"', $entityName));
}
/**
* @param string $entityName
*
* @return \Doctrine\ORM\Cache\CacheException
*/
public static function nonCacheableEntity($entityName)
{
return new self(sprintf('Entity "%s" not configured as part of the second-level cache.', $entityName));
}
/**
* @param string $entityName
*
* @return \Doctrine\ORM\Cache\CacheException
*/
public static function nonCacheableEntityAssociation($entityName, $field)
{
return new self(sprintf('Entity association field "%s#%s" not configured as part of the second-level cache.', $entityName, $field));
}
}

View File

@ -0,0 +1,104 @@
<?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\Cache;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Persisters\CollectionPersister;
use Doctrine\ORM\Persisters\EntityPersister;
/**
* Contract for building second level cache regions components.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CacheFactory
{
/**
* Build an entity persister for the given entity metadata.
*
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Persisters\EntityPersister $persister The entity persister that will be cached.
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
*
* @return \Doctrine\ORM\Cache\Persister\CachedEntityPersister
*/
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata);
/**
* Build a collection persister for the given relation mapping.
*
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Persisters\CollectionPersister $persister The collection persister that will be cached.
* @param array $mapping The association mapping.
*
* @return \Doctrine\ORM\Cache\Persister\CachedCollectionPersister
*/
public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping);
/**
* Build a query cache based on the given region name
*
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
* @param string $regionName The region name.
*
* @return \Doctrine\ORM\Cache\QueryCache The built query cache.
*/
public function buildQueryCache(EntityManagerInterface $em, $regionName = null);
/**
* Build an entity hydrator
*
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
*
* @return \Doctrine\ORM\Cache\EntityHydrator The built entity hydrator.
*/
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata);
/**
* Build a collection hydrator
*
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
* @param array $mapping The association mapping.
*
* @return \Doctrine\ORM\Cache\CollectionHydrator The built collection hydrator.
*/
public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping);
/**
* Build a cache region
*
* @param array $cache The cache configuration.
*
* @return \Doctrine\ORM\Cache\Region The cache region.
*/
public function getRegion(array $cache);
/**
* Build timestamp cache region
*
* @return \Doctrine\ORM\Cache\TimestampRegion The timestamp region.
*/
public function getTimestampRegion();
}

View File

@ -0,0 +1,38 @@
<?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\Cache;
/**
* Defines entity / collection / query key to be stored in the cache region.
* Allows multiple roles to be stored in the same cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
abstract class CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string Unique identifier
*/
public $hash;
}

View File

@ -0,0 +1,57 @@
<?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\Cache;
/**
* Collection cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CollectionCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The list of entity identifiers hold by the collection
*/
public $identifiers;
/**
* @param array $identifiers List of entity identifiers hold by the collection
*/
public function __construct(array $identifiers)
{
$this->identifiers = $identifiers;
}
/**
* Creates a new CollectionCacheEntry
*
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*/
public static function __set_state(array $values)
{
return new self($values['identifiers']);
}
}

View File

@ -0,0 +1,66 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Cache;
/**
* Defines entity collection roles to be stored in the cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CollectionCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The owner entity identifier
*/
public $ownerIdentifier;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string The owner entity class
*/
public $entityClass;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string The association name
*/
public $association;
/**
* @param string $entityClass The entity class.
* @param string $association The field name that represents the association.
* @param array $ownerIdentifier The identifier of the owning entity.
*/
public function __construct($entityClass, $association, array $ownerIdentifier)
{
ksort($ownerIdentifier);
$this->ownerIdentifier = $ownerIdentifier;
$this->entityClass = (string) $entityClass;
$this->association = (string) $association;
$this->hash = str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association;
}
}

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\Cache;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\CollectionCacheEntry;
/**
* Hydrator cache entry for collections
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CollectionHydrator
{
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key.
* @param array|\Doctrine\Common\Collections\Collection $collection The collection.
*
* @return \Doctrine\ORM\Cache\CollectionCacheEntry
*/
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection);
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The owning entity metadata.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key.
* @param \Doctrine\ORM\Cache\CollectionCacheEntry $entry The cached collection entry.
* @param \Doctrine\ORM\PersistentCollection $collection The collection to load the cache into.
*
* @return array
*/
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection);
}

View File

@ -0,0 +1,59 @@
<?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\Cache;
use Doctrine\ORM\Cache\Lock;
/**
* Defines contract for concurrently managed data region.
* It should be able to lock an specific cache entry in an atomic operation.
*
* When a entry is locked another process should not be able to read or write the entry.
* All evict operation should not consider locks, even though an entry is locked evict should be able to delete the entry and its lock.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface ConcurrentRegion extends Region
{
/**
* Attempts to read lock the mapping for the given key.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock.
*
* @return \Doctrine\ORM\Cache\Lock A lock instance or NULL if the lock already exists.
*
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
*/
public function lock(CacheKey $key);
/**
* Attempts to read unlock the mapping for the given key.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock.
* @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link readLock}
*
* @return void
*
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
*/
public function unlock(CacheKey $key, Lock $lock);
}

View File

@ -0,0 +1,342 @@
<?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\Cache;
use Doctrine\ORM\Cache;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\ORMInvalidArgumentException;
/**
* Provides an API for querying/managing the second level cache regions.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultCache implements Cache
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
private $uow;
/**
* @var \Doctrine\ORM\Cache\CacheFactory
*/
private $cacheFactory;
/**
* @var \Doctrine\ORM\Cache\QueryCache[]
*/
private $queryCaches = array();
/**
* @var \Doctrine\ORM\Cache\QueryCache
*/
private $defaultQueryCache;
/**
* {@inheritdoc}
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->uow = $em->getUnitOfWork();
$this->cacheFactory = $em->getConfiguration()
->getSecondLevelCacheConfiguration()
->getCacheFactory();
}
/**
* {@inheritdoc}
*/
public function getEntityCacheRegion($className)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
return null;
}
return $persister->getCacheRegion();
}
/**
* {@inheritdoc}
*/
public function getCollectionCacheRegion($className, $association)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
return null;
}
return $persister->getCacheRegion();
}
/**
* {@inheritdoc}
*/
public function containsEntity($className, $identifier)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
return false;
}
return $persister->getCacheRegion()->contains($this->buildEntityCacheKey($metadata, $identifier));
}
/**
* {@inheritdoc}
*/
public function evictEntity($className, $identifier)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evict($this->buildEntityCacheKey($metadata, $identifier));
}
/**
* {@inheritdoc}
*/
public function evictEntityRegion($className)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evictAll();
}
/**
* {@inheritdoc}
*/
public function evictEntityRegions()
{
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
foreach ($metadatas as $metadata) {
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
continue;
}
$persister->getCacheRegion()->evictAll();
}
}
/**
* {@inheritdoc}
*/
public function containsCollection($className, $association, $ownerIdentifier)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
return false;
}
return $persister->getCacheRegion()->contains($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
}
/**
* {@inheritdoc}
*/
public function evictCollection($className, $association, $ownerIdentifier)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evict($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
}
/**
* {@inheritdoc}
*/
public function evictCollectionRegion($className, $association)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evictAll();
}
/**
* {@inheritdoc}
*/
public function evictCollectionRegions()
{
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
foreach ($metadatas as $metadata) {
foreach ($metadata->associationMappings as $association) {
if ( ! $association['type'] & ClassMetadata::TO_MANY) {
continue;
}
$persister = $this->uow->getCollectionPersister($association);
if ( ! ($persister instanceof CachedPersister)) {
continue;
}
$persister->getCacheRegion()->evictAll();
}
}
}
/**
* {@inheritdoc}
*/
public function containsQuery($regionName)
{
return isset($this->queryCaches[$regionName]);
}
/**
* {@inheritdoc}
*/
public function evictQueryRegion($regionName = null)
{
if ($regionName === null && $this->defaultQueryCache !== null) {
$this->defaultQueryCache->clear();
return;
}
if (isset($this->queryCaches[$regionName])) {
$this->queryCaches[$regionName]->clear();
}
}
/**
* {@inheritdoc}
*/
public function evictQueryRegions()
{
$this->getQueryCache()->clear();
foreach ($this->queryCaches as $queryCache) {
$queryCache->clear();
}
}
/**
* {@inheritdoc}
*/
public function getQueryCache($regionName = null)
{
if ($regionName === null) {
return $this->defaultQueryCache ?:
$this->defaultQueryCache = $this->cacheFactory->buildQueryCache($this->em);
}
if ( ! isset($this->queryCaches[$regionName])) {
$this->queryCaches[$regionName] = $this->cacheFactory->buildQueryCache($this->em, $regionName);
}
return $this->queryCaches[$regionName];
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param mixed $identifier The entity identifier.
*
* @return \Doctrine\ORM\Cache\EntityCacheKey
*/
private function buildEntityCacheKey(ClassMetadata $metadata, $identifier)
{
if ( ! is_array($identifier)) {
$identifier = $this->toIdentifierArray($metadata, $identifier);
}
return new EntityCacheKey($metadata->rootEntityName, $identifier);
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $association The field name that represents the association.
* @param mixed $ownerIdentifier The identifier of the owning entity.
*
* @return \Doctrine\ORM\Cache\CollectionCacheKey
*/
private function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier)
{
if ( ! is_array($ownerIdentifier)) {
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);;
}
return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier);
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param mixed $identifier The entity identifier.
*
* @return array
*/
private function toIdentifierArray(ClassMetadata $metadata, $identifier)
{
if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) {
$identifier = $this->uow->getSingleIdentifierValue($identifier);
if ($identifier === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
}
return array($metadata->identifier[0] => $identifier);
}
}

View File

@ -0,0 +1,235 @@
<?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\Cache;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\TimestampRegion;
use Doctrine\ORM\Cache\RegionsConfiguration;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\Region\DefaultRegion;
use Doctrine\ORM\Cache\Region\FileLockRegion;
use Doctrine\ORM\Cache\Region\UpdateTimestampCache;
use Doctrine\ORM\Persisters\EntityPersister;
use Doctrine\ORM\Persisters\CollectionPersister;
use Doctrine\ORM\Cache\Persister\ReadOnlyCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\ReadOnlyCachedCollectionPersister;
use Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister;
use Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedCollectionPersister;
/**
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultCacheFactory implements CacheFactory
{
/**
* @var \Doctrine\Common\Cache\CacheProvider
*/
private $cache;
/**
* @var \Doctrine\ORM\Cache\RegionsConfiguration
*/
private $regionsConfig;
/**
* @var \Doctrine\ORM\Cache\TimestampRegion|null
*/
private $timestampRegion;
/**
* @var \Doctrine\ORM\Cache\Region[]
*/
private $regions = array();
/**
* @var string|null
*/
private $fileLockRegionDirectory;
/**
* @param \Doctrine\ORM\Cache\RegionsConfiguration $cacheConfig
* @param \Doctrine\Common\Cache\CacheProvider $cache
*/
public function __construct(RegionsConfiguration $cacheConfig, CacheProvider $cache)
{
$this->cache = $cache;
$this->regionsConfig = $cacheConfig;
}
/**
* @param string $fileLockRegionDirectory
*/
public function setFileLockRegionDirectory($fileLockRegionDirectory)
{
$this->fileLockRegionDirectory = (string) $fileLockRegionDirectory;
}
/**
* @return string
*/
public function getFileLockRegionDirectory()
{
return $this->fileLockRegionDirectory;
}
/**
* @param \Doctrine\ORM\Cache\Region $region
*/
public function setRegion(Region $region)
{
$this->regions[$region->getName()] = $region;
}
/**
* @param \Doctrine\ORM\Cache\TimestampRegion $region
*/
public function setTimestampRegion(TimestampRegion $region)
{
$this->timestampRegion = $region;
}
/**
* {@inheritdoc}
*/
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata)
{
$region = $this->getRegion($metadata->cache);
$usage = $metadata->cache['usage'];
if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata);
}
if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) {
return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
}
if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
}
throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage));
}
/**
* {@inheritdoc}
*/
public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping)
{
$usage = $mapping['cache']['usage'];
$region = $this->getRegion($mapping['cache']);
if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping);
}
if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) {
return new NonStrictReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
}
if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
}
throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage));
}
/**
* {@inheritdoc}
*/
public function buildQueryCache(EntityManagerInterface $em, $regionName = null)
{
return new DefaultQueryCache(
$em,
$this->getRegion(
array(
'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME,
'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE
)
)
);
}
/**
* {@inheritdoc}
*/
public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping)
{
return new DefaultCollectionHydrator($em);
}
/**
* {@inheritdoc}
*/
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata)
{
return new DefaultEntityHydrator($em);
}
/**
* {@inheritdoc}
*/
public function getRegion(array $cache)
{
if (isset($this->regions[$cache['region']])) {
return $this->regions[$cache['region']];
}
$region = new DefaultRegion($cache['region'], clone $this->cache, $this->regionsConfig->getLifetime($cache['region']));
if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
if ( ! $this->fileLockRegionDirectory) {
throw new \LogicException(
'If you what to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' .
'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you what to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). '
);
}
$directory = $this->fileLockRegionDirectory . DIRECTORY_SEPARATOR . $cache['region'];
$region = new FileLockRegion($region, $directory, $this->regionsConfig->getLockLifetime($cache['region']));
}
return $this->regions[$cache['region']] = $region;
}
/**
* {@inheritdoc}
*/
public function getTimestampRegion()
{
if ($this->timestampRegion === null) {
$name = Cache::DEFAULT_TIMESTAMP_REGION_NAME;
$lifetime = $this->regionsConfig->getLifetime($name);
$this->timestampRegion = new UpdateTimestampCache($name, clone $this->cache, $lifetime);
}
return $this->timestampRegion;
}
}

View File

@ -0,0 +1,103 @@
<?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\Cache;
use Doctrine\ORM\Query;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\CollectionCacheEntry;
/**
* Default hydrator cache for collections
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultCollectionHydrator implements CollectionHydrator
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
private $uow;
/**
* @var array
*/
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->uow = $em->getUnitOfWork();
}
/**
* {@inheritdoc}
*/
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection)
{
$data = array();
foreach ($collection as $index => $entity) {
$data[$index] = $this->uow->getEntityIdentifier($entity);
}
return new CollectionCacheEntry($data);
}
/**
* {@inheritdoc}
*/
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection)
{
$assoc = $metadata->associationMappings[$key->association];
$targetPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$targetRegion = $targetPersister->getCacheRegion();
$list = array();
foreach ($entry->identifiers as $index => $identifier) {
$entityEntry = $targetRegion->get(new EntityCacheKey($assoc['targetEntity'], $identifier));
if ($entityEntry === null) {
return null;
}
$list[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->data, self::$hints);
}
array_walk($list, function($entity, $index) use ($collection) {
$collection->hydrateSet($index, $entity);
});
return $list;
}
}

View File

@ -0,0 +1,146 @@
<?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\Cache;
use Doctrine\ORM\Query;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\EntityCacheEntry;
/**
* Default hydrator cache for entities
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultEntityHydrator implements EntityHydrator
{
/**
* @var \Doctrine\ORM\EntityManager
*/
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
private $uow;
/**
* @var array
*/
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->uow = $em->getUnitOfWork();
}
/**
* {@inheritdoc}
*/
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity)
{
$data = $this->uow->getOriginalEntityData($entity);
$data = array_merge($data, $key->identifier); // why update has no identifier values ?
foreach ($metadata->associationMappings as $name => $assoc) {
if ( ! isset($data[$name])) {
continue;
}
if ( ! isset($assoc['cache']) || ! ($assoc['type'] & ClassMetadata::TO_ONE)) {
unset($data[$name]);
continue;
}
if ( ! isset($assoc['id'])) {
$data[$name] = $this->uow->getEntityIdentifier($data[$name]);
continue;
}
// handle association identifier
$targetId = is_object($data[$name]) && $this->em->getMetadataFactory()->hasMetadataFor(get_class($data[$name]))
? $this->uow->getEntityIdentifier($data[$name])
: $data[$name];
// @TODO - fix it !
// handle UnitOfWork#createEntity hash generation
if ( ! is_array($targetId)) {
$data[reset($assoc['joinColumnFieldNames'])] = $targetId;
$targetEntity = $this->em->getClassMetadata($assoc['targetEntity']);
$targetId = array($targetEntity->identifier[0] => $targetId);
}
$data[$name] = $targetId;
}
return new EntityCacheEntry($metadata->name, $data);
}
/**
* {@inheritdoc}
*/
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null)
{
$data = $entry->data;
$hints = self::$hints;
if ($entity !== null) {
$hints[Query::HINT_REFRESH] = true;
$hints[Query::HINT_REFRESH_ENTITY] = $entity;
}
foreach ($metadata->associationMappings as $name => $assoc) {
if ( ! isset($assoc['cache']) || ! isset($data[$name])) {
continue;
}
$assocId = $data[$name];
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocRegion = $assocPersister->getCacheRegion();
$assocEntry = $assocRegion->get(new EntityCacheKey($assoc['targetEntity'], $assocId));
if ($assocEntry === null) {
return null;
}
$data[$name] = $assoc['fetch'] === ClassMetadata::FETCH_EAGER || ($assoc['type'] === ClassMetadata::ONE_TO_ONE && ! $assoc['isOwningSide'])
? $this->uow->createEntity($assocEntry->class, $assocEntry->data, $hints)
: $this->em->getReference($assocEntry->class, $assocId);
}
if ($entity !== null) {
$this->uow->registerManaged($entity, $key->identifier, $data);
}
return $this->uow->createEntity($entry->class, $data, $hints);
}
}

View File

@ -0,0 +1,338 @@
<?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\Cache;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Cache\QueryCacheEntry;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Cache\CacheException;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
/**
* Default query cache implementation.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultQueryCache implements QueryCache
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
private $uow;
/**
* @var \Doctrine\ORM\Cache\Region
*/
private $region;
/**
* @var \Doctrine\ORM\Cache\QueryCacheValidator
*/
private $validator;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
*/
protected $cacheLogger;
/**
* @var array
*/
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Cache\Region $region The query region.
*/
public function __construct(EntityManagerInterface $em, Region $region)
{
$cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration();
$this->em = $em;
$this->region = $region;
$this->uow = $em->getUnitOfWork();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->validator = $cacheConfig->getQueryValidator();
}
/**
* {@inheritdoc}
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array())
{
if ( ! ($key->cacheMode & Cache::MODE_GET)) {
return null;
}
$entry = $this->region->get($key);
if ( ! $entry instanceof QueryCacheEntry) {
return null;
}
if ( ! $this->validator->isValid($key, $entry)) {
$this->region->evict($key);
return null;
}
$result = array();
$entityName = reset($rsm->aliasMap);
$hasRelation = ( ! empty($rsm->relationMap));
$persister = $this->uow->getEntityPersister($entityName);
$region = $persister->getCacheRegion();
$regionName = $region->getName();
// @TODO - move to cache hydration component
foreach ($entry->result as $index => $entry) {
if (($entityEntry = $region->get($entityKey = new EntityCacheKey($entityName, $entry['identifier']))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($regionName, $entityKey);
}
return null;
}
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($regionName, $entityKey);
}
if ( ! $hasRelation) {
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->data, self::$hints);
continue;
}
$data = $entityEntry->data;
foreach ($entry['associations'] as $name => $assoc) {
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocRegion = $assocPersister->getCacheRegion();
if ($assoc['type'] & ClassMetadata::TO_ONE) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assoc['identifier']))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
}
return null;
}
$data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints);
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
}
continue;
}
if ( ! isset($assoc['list']) || empty($assoc['list'])) {
continue;
}
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
$collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection());
foreach ($assoc['list'] as $assocIndex => $assocId) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
}
return null;
}
$element = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints);
$collection->hydrateSet($assocIndex, $element);
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
}
}
$data[$name] = $collection;
$collection->setInitialized(true);
}
$result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
}
return $result;
}
/**
* {@inheritdoc}
*/
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array())
{
if ($rsm->scalarMappings) {
throw new CacheException("Second level cache does not support scalar results.");
}
if (count($rsm->entityMappings) > 1) {
throw new CacheException("Second level cache does not support multiple root entities.");
}
if ( ! $rsm->isSelect) {
throw new CacheException("Second-level cache query supports only select statements.");
}
if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD]) && $hints[Query::HINT_FORCE_PARTIAL_LOAD]) {
throw new CacheException("Second level cache does not support partial entities.");
}
if ( ! ($key->cacheMode & Cache::MODE_PUT)) {
return false;
}
$data = array();
$entityName = reset($rsm->aliasMap);
$hasRelation = ( ! empty($rsm->relationMap));
$metadata = $this->em->getClassMetadata($entityName);
$persister = $this->uow->getEntityPersister($entityName);
if ( ! ($persister instanceof CachedPersister)) {
throw CacheException::nonCacheableEntity($entityName);
}
$region = $persister->getCacheRegion();
foreach ($result as $index => $entity) {
$identifier = $this->uow->getEntityIdentifier($entity);
$data[$index]['identifier'] = $identifier;
$data[$index]['associations'] = array();
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) {
// Cancel put result if entity put fail
if ( ! $persister->storeEntityCache($entity, $entityKey)) {
return false;
}
}
if ( ! $hasRelation) {
continue;
}
// @TODO - move to cache hydration components
foreach ($rsm->relationMap as $name) {
$assoc = $metadata->associationMappings[$name];
if (($assocValue = $metadata->getFieldValue($entity, $name)) === null || $assocValue instanceof Proxy) {
continue;
}
if ( ! isset($assoc['cache'])) {
throw CacheException::nonCacheableEntityAssociation($entityName, $name);
}
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocRegion = $assocPersister->getCacheRegion();
$assocMetadata = $assocPersister->getClassMetadata();
// Handle *-to-one associations
if ($assoc['type'] & ClassMetadata::TO_ONE) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) {
// Cancel put result if association entity put fail
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
return false;
}
}
$data[$index]['associations'][$name] = array(
'targetEntity' => $assocMetadata->rootEntityName,
'identifier' => $assocIdentifier,
'type' => $assoc['type']
);
continue;
}
// Handle *-to-many associations
$list = array();
foreach ($assocValue as $assocItemIndex => $assocItem) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) {
// Cancel put result if entity put fail
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
return false;
}
}
$list[$assocItemIndex] = $assocIdentifier;
}
$data[$index]['associations'][$name] = array(
'targetEntity' => $assocMetadata->rootEntityName,
'type' => $assoc['type'],
'list' => $list,
);
}
}
return $this->region->put($key, new QueryCacheEntry($data));
}
/**
* {@inheritdoc}
*/
public function clear()
{
return $this->region->evictAll();
}
/**
* {@inheritdoc}
*/
public function getRegion()
{
return $this->region;
}
}

View File

@ -0,0 +1,66 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Cache;
/**
* Entity cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class EntityCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The entity map data
*/
public $data;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string The entity class name
*/
public $class;
/**
* @param string $class The entity class.
* @param array $data The entity data.
*/
public function __construct($class, array $data)
{
$this->class = $class;
$this->data = $data;
}
/**
* Creates a new EntityCacheEntry
*
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*/
public static function __set_state(array $values)
{
return new self($values['class'], $values['data']);
}
}

View File

@ -0,0 +1,57 @@
<?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\Cache;
/**
* Defines entity classes roles to be stored in the cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class EntityCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The entity identifier
*/
public $identifier;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string The entity class name
*/
public $entityClass;
/**
* @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class.
* @param array $identifier The entity identifier
*/
public function __construct($entityClass, array $identifier)
{
ksort($identifier);
$this->identifier = $identifier;
$this->entityClass = $entityClass;
$this->hash = str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier));
}
}

View File

@ -0,0 +1,51 @@
<?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\Cache;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Cache\EntityCacheEntry;
/**
* Hydrator cache entry for entities
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface EntityHydrator
{
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key.
* @param object $entity The entity.
*
* @return \Doctrine\ORM\Cache\EntityCacheEntry
*/
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity);
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key.
* @param \Doctrine\ORM\Cache\EntityCacheEntry $entry The entity cache entry.
* @param object $entity The entity to load the cache into. If not specified, a new entity is created.
*/
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null);
}

View File

@ -0,0 +1,58 @@
<?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\Cache;
/**
* Cache Lock
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class Lock
{
/**
* @var string
*/
public $value;
/**
* @var integer
*/
public $time;
/**
* @param string $value
* @param integer $time
*/
public function __construct($value, $time = null)
{
$this->value = $value;
$this->time = $time ? : time();
}
/**
* @return \Doctrine\ORM\Cache\Lock
*/
public static function createLockRead()
{
return new self(uniqid(time()));
}
}

View File

@ -1,4 +1,5 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@ -17,17 +18,15 @@
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping;
namespace Doctrine\ORM\Cache;
/**
* @Annotation
* @Target("ALL")
* @todo check available targets
* Lock exception for cache.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
final class ElementCollection implements Annotation
class LockException extends CacheException
{
/**
* @var string
*/
public $tableName;
}

View File

@ -0,0 +1,106 @@
<?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\Cache\Logging;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
/**
* Interface for logging.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CacheLogger
{
/**
* Log an entity put into second level cache.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
*/
public function entityCachePut($regionName, EntityCacheKey $key);
/**
* Log an entity get from second level cache resulted in a hit.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
*/
public function entityCacheHit($regionName, EntityCacheKey $key);
/**
* Log an entity get from second level cache resulted in a miss.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
*/
public function entityCacheMiss($regionName, EntityCacheKey $key);
/**
* Log an entity put into second level cache.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
*/
public function collectionCachePut($regionName, CollectionCacheKey $key);
/**
* Log an entity get from second level cache resulted in a hit.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
*/
public function collectionCacheHit($regionName, CollectionCacheKey $key);
/**
* Log an entity get from second level cache resulted in a miss.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
*/
public function collectionCacheMiss($regionName, CollectionCacheKey $key);
/**
* Log a query put into the query cache.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
*/
public function queryCachePut($regionName, QueryCacheKey $key);
/**
* Log a query get from the query cache resulted in a hit.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
*/
public function queryCacheHit($regionName, QueryCacheKey $key);
/**
* Log a query get from the query cache resulted in a miss.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
*/
public function queryCacheMiss($regionName, QueryCacheKey $key);
}

View File

@ -0,0 +1,156 @@
<?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\Cache\Logging;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
/**
* Cache logger chain
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CacheLoggerChain implements CacheLogger
{
/**
* @var array<\Doctrine\ORM\Cache\Logging\CacheLogger>
*/
private $loggers = array();
/**
* @param string $name
* @param \Doctrine\ORM\Cache\Logging\CacheLogger $logger
*/
public function setLogger($name, CacheLogger $logger)
{
$this->loggers[$name] = $logger;
}
/**
* @param string $name
*
* @return \Doctrine\ORM\Cache\Logging\CacheLogger|null
*/
public function getLogger($name)
{
return isset($this->loggers[$name]) ? $this->loggers[$name] : null;
}
/**
* @return array<\Doctrine\ORM\Cache\Logging\CacheLogger>
*/
public function getLoggers()
{
return $this->loggers;
}
/**
* {@inheritdoc}
*/
public function collectionCacheHit($regionName, CollectionCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->collectionCacheHit($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function collectionCacheMiss($regionName, CollectionCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->collectionCacheMiss($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function collectionCachePut($regionName, CollectionCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->collectionCachePut($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function entityCacheHit($regionName, EntityCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->entityCacheHit($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function entityCacheMiss($regionName, EntityCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->entityCacheMiss($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function entityCachePut($regionName, EntityCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->entityCachePut($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function queryCacheHit($regionName, QueryCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->queryCacheHit($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function queryCacheMiss($regionName, QueryCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->queryCacheMiss($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function queryCachePut($regionName, QueryCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->queryCachePut($regionName, $key);
}
}
}

View File

@ -0,0 +1,251 @@
<?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\Cache\Logging;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
/**
* Provide basic second level cache statistics.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class StatisticsCacheLogger implements CacheLogger
{
/**
* @var array
*/
private $cacheMissCountMap = array();
/**
* @var array
*/
private $cacheHitCountMap = array();
/**
* @var array
*/
private $cachePutCountMap = array();
/**
* {@inheritdoc}
*/
public function collectionCacheMiss($regionName, CollectionCacheKey $key)
{
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
? $this->cacheMissCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function collectionCacheHit($regionName, CollectionCacheKey $key)
{
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
? $this->cacheHitCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function collectionCachePut($regionName, CollectionCacheKey $key)
{
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
? $this->cachePutCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function entityCacheMiss($regionName, EntityCacheKey $key)
{
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
? $this->cacheMissCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function entityCacheHit($regionName, EntityCacheKey $key)
{
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
? $this->cacheHitCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function entityCachePut($regionName, EntityCacheKey $key)
{
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
? $this->cachePutCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function queryCacheHit($regionName, QueryCacheKey $key)
{
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
? $this->cacheHitCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function queryCacheMiss($regionName, QueryCacheKey $key)
{
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
? $this->cacheMissCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function queryCachePut($regionName, QueryCacheKey $key)
{
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
? $this->cachePutCountMap[$regionName] + 1
: 1;
}
/**
* Get the number of entries successfully retrieved from cache.
*
* @param string $regionName The name of the cache region.
*
* @return integer
*/
public function getRegionHitCount($regionName)
{
return isset($this->cacheHitCountMap[$regionName]) ? $this->cacheHitCountMap[$regionName] : 0;
}
/**
* Get the number of cached entries *not* found in cache.
*
* @param string $regionName The name of the cache region.
*
* @return integer
*/
public function getRegionMissCount($regionName)
{
return isset($this->cacheMissCountMap[$regionName]) ? $this->cacheMissCountMap[$regionName] : 0;
}
/**
* Get the number of cacheable entries put in cache.
*
* @param string $regionName The name of the cache region.
*
* @return integer
*/
public function getRegionPutCount($regionName)
{
return isset($this->cachePutCountMap[$regionName]) ? $this->cachePutCountMap[$regionName] : 0;
}
/**
* @return array
*/
public function getRegionsMiss()
{
return $this->cacheMissCountMap;
}
/**
* @return array
*/
public function getRegionsHit()
{
return $this->cacheHitCountMap;
}
/**
* @return array
*/
public function getRegionsPut()
{
return $this->cachePutCountMap;
}
/**
* Clear region statistics
*
* @param string $regionName The name of the cache region.
*/
public function clearRegionStats($regionName)
{
$this->cachePutCountMap[$regionName] = 0;
$this->cacheHitCountMap[$regionName] = 0;
$this->cacheMissCountMap[$regionName] = 0;
}
/**
* Clear all statistics
*/
public function clearStats()
{
$this->cachePutCountMap = array();
$this->cacheHitCountMap = array();
$this->cacheMissCountMap = array();
}
/**
* Get the total number of put in cache.
*
* @return integer
*/
public function getPutCount()
{
return array_sum($this->cachePutCountMap);
}
/**
* Get the total number of entries successfully retrieved from cache.
*
* @return integer
*/
public function getHitCount()
{
return array_sum($this->cacheHitCountMap);
}
/**
* Get the total number of cached entries *not* found in cache.
*
* @return integer
*/
public function getMissCount()
{
return array_sum($this->cacheMissCountMap);
}
}

View File

@ -0,0 +1,275 @@
<?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\Cache\Persister;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Persisters\CollectionPersister;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\Region;
use Doctrine\Common\Util\ClassUtils;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
abstract class AbstractCollectionPersister implements CachedCollectionPersister
{
/**
* @var \Doctrine\ORM\UnitOfWork
*/
protected $uow;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadataFactory
*/
protected $metadataFactory;
/**
* @var \Doctrine\ORM\Persisters\CollectionPersister
*/
protected $persister;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
protected $sourceEntity;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
protected $targetEntity;
/**
* @var array
*/
protected $association;
/**
* @var array
*/
protected $queuedCache = array();
/**
* @var \Doctrine\ORM\Cache\Region
*/
protected $region;
/**
* @var string
*/
protected $regionName;
/**
* @var \Doctrine\ORM\Cache\CollectionHydrator
*/
protected $hydrator;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
*/
protected $cacheLogger;
/**
* @param \Doctrine\ORM\Persisters\CollectionPersister $persister The collection persister that will be cached.
* @param \Doctrine\ORM\Cache\Region $region The collection region.
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param array $association The association mapping.
*/
public function __construct(CollectionPersister $persister, Region $region, EntityManagerInterface $em, array $association)
{
$configuration = $em->getConfiguration();
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$this->region = $region;
$this->persister = $persister;
$this->association = $association;
$this->regionName = $region->getName();
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->hydrator = $cacheFactory->buildCollectionHydrator($em, $association);
$this->sourceEntity = $em->getClassMetadata($association['sourceEntity']);
$this->targetEntity = $em->getClassMetadata($association['targetEntity']);
}
/**
* {@inheritdoc}
*/
public function getCacheRegion()
{
return $this->region;
}
/**
* {@inheritdoc}
*/
public function getSourceEntityMetadata()
{
return $this->sourceEntity;
}
/**
* {@inheritdoc}
*/
public function getTargetEntityMetadata()
{
return $this->targetEntity;
}
/**
* @param \Doctrine\ORM\PersistentCollection $collection
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key
*
* @return \Doctrine\ORM\PersistentCollection|null
*/
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key)
{
if (($cache = $this->region->get($key)) === null) {
return null;
}
if (($cache = $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection)) === null) {
return null;
}
return $cache;
}
/**
* {@inheritdoc}
*/
public function storeCollectionCache(CollectionCacheKey $key, $elements)
{
$targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName);
$targetRegion = $targetPersister->getCacheRegion();
$targetHydrator = $targetPersister->getEntityHydrator();
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
foreach ($entry->identifiers as $index => $identifier) {
$entityKey = new EntityCacheKey($this->targetEntity->rootEntityName, $identifier);
if ($targetRegion->contains($entityKey)) {
continue;
}
$class = $this->targetEntity;
$className = ClassUtils::getClass($elements[$index]);
if ($className !== $this->targetEntity->name) {
$class = $this->metadataFactory->getMetadataFor($className);
}
$entity = $elements[$index];
$entityEntry = $targetHydrator->buildCacheEntry($class, $entityKey, $entity);
$targetRegion->put($entityKey, $entityEntry);
}
$cached = $this->region->put($key, $entry);
if ($this->cacheLogger && $cached) {
$this->cacheLogger->collectionCachePut($this->regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function contains(PersistentCollection $collection, $element)
{
return $this->persister->contains($collection, $element);
}
/**
* {@inheritdoc}
*/
public function containsKey(PersistentCollection $collection, $key)
{
return $this->persister->containsKey($collection, $key);
}
/**
* {@inheritdoc}
*/
public function count(PersistentCollection $collection)
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId);
$entry = $this->region->get($key);
if ($entry !== null) {
return count($entry->identifiers);
}
return $this->persister->count($collection);
}
/**
* {@inheritdoc}
*/
public function deleteRows(PersistentCollection $collection)
{
$this->persister->deleteRows($collection);
}
/**
* {@inheritdoc}
*/
public function insertRows(PersistentCollection $collection)
{
$this->persister->insertRows($collection);
}
/**
* {@inheritdoc}
*/
public function get(PersistentCollection $collection, $index)
{
return $this->persister->get($collection, $index);
}
/**
* {@inheritdoc}
*/
public function removeElement(PersistentCollection $collection, $element)
{
return $this->persister->removeElement($collection, $element);
}
/**
* {@inheritdoc}
*/
public function removeKey(PersistentCollection $collection, $key)
{
return $this->persister->removeKey($collection, $key);
}
/**
* {@inheritdoc}
*/
public function slice(PersistentCollection $collection, $offset, $length = null)
{
return $this->persister->slice($collection, $offset, $length);
}
}

View File

@ -0,0 +1,581 @@
<?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\Cache\Persister;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Persisters\EntityPersister;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\Common\Collections\Criteria;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
abstract class AbstractEntityPersister implements CachedEntityPersister
{
/**
* @var \Doctrine\ORM\UnitOfWork
*/
protected $uow;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadataFactory
*/
protected $metadataFactory;
/**
* @var \Doctrine\ORM\Persisters\EntityPersister
*/
protected $persister;
/**
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
protected $class;
/**
* @var array
*/
protected $queuedCache = array();
/**
* @var \Doctrine\ORM\Cache\Region
*/
protected $region;
/**
* @var \Doctrine\ORM\Cache\TimestampRegion
*/
protected $timestampRegion;
/**
* @var \Doctrine\ORM\Cache\TimestampCacheKey
*/
protected $timestampKey;
/**
* @var \Doctrine\ORM\Cache\EntityHydrator
*/
protected $hydrator;
/**
* @var \Doctrine\ORM\Cache
*/
protected $cache;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
*/
protected $cacheLogger;
/**
* @var string
*/
protected $regionName;
/**
* Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
*
* @var array
*/
protected $joinedAssociations;
/**
* @param \Doctrine\ORM\Persisters\EntityPersister $persister The entity persister to cache.
* @param \Doctrine\ORM\Cache\Region $region The entity cache region.
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Mapping\ClassMetadata $class The entity metadata.
*/
public function __construct(EntityPersister $persister, Region $region, EntityManagerInterface $em, ClassMetadata $class)
{
$configuration = $em->getConfiguration();
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$this->class = $class;
$this->region = $region;
$this->persister = $persister;
$this->cache = $em->getCache();
$this->regionName = $region->getName();
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->timestampRegion = $cacheFactory->getTimestampRegion();
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
$this->timestampKey = new TimestampCacheKey($this->class->getTableName());
}
/**
* {@inheritdoc}
*/
public function addInsert($entity)
{
$this->persister->addInsert($entity);
}
/**
* {@inheritdoc}
*/
public function getInserts()
{
return $this->persister->getInserts();
}
/**
* {@inheritdoc}
*/
public function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
{
return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy);
}
/**
* {@inheritdoc}
*/
public function getInsertSQL()
{
return $this->persister->getInsertSQL();
}
/**
* {@inheritdoc}
*/
public function getResultSetMapping()
{
return $this->persister->getResultSetMapping();
}
/**
* {@inheritdoc}
*/
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null)
{
return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison);
}
/**
* {@inheritdoc}
*/
public function exists($entity, array $extraConditions = array())
{
if (empty($extraConditions)) {
$key = new EntityCacheKey($this->class->rootEntityName, $this->class->getIdentifierValues($entity));
if ($this->region->contains($key)) {
return true;
}
}
return $this->persister->exists($entity, $extraConditions);
}
/**
* {@inheritdoc}
*/
public function getCacheRegion()
{
return $this->region;
}
/**
* @return \Doctrine\ORM\Cache\EntityHydrator
*/
public function getEntityHydrator()
{
return $this->hydrator;
}
/**
* {@inheritdoc}
*/
public function storeEntityCache($entity, EntityCacheKey $key)
{
$class = $this->class;
$className = ClassUtils::getClass($entity);
if ($className !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($className);
}
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
if ($this->cacheLogger && $cached) {
$this->cacheLogger->entityCachePut($this->regionName, $key);
}
return $cached;
}
/**
* @param object $entity
*/
private function storeJoinedAssociations($entity)
{
if ($this->joinedAssociations === null) {
$associations = array();
foreach ($this->class->associationMappings as $name => $assoc) {
if (isset($assoc['cache']) &&
($assoc['type'] & ClassMetadata::TO_ONE) &&
($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ! $assoc['isOwningSide'])) {
$associations[] = $name;
}
}
$this->joinedAssociations = $associations;
}
foreach ($this->joinedAssociations as $name) {
$assoc = $this->class->associationMappings[$name];
$assocEntity = $this->class->getFieldValue($entity, $name);
if ($assocEntity === null) {
continue;
}
$assocId = $this->uow->getEntityIdentifier($assocEntity);
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocPersister->storeEntityCache($assocEntity, $assocKey);
}
}
/**
* Generates a string of currently query
*
* @param array $query
* @param string $criteria
* @param array $orderBy
* @param integer $limit
* @param integer $offset
* @param integer $timestamp
*
* @return string
*/
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null, $timestamp = null)
{
list($params) = $this->persister->expandParameters($criteria);
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $timestamp);
}
/**
* {@inheritdoc}
*/
public function expandParameters($criteria)
{
return $this->persister->expandParameters($criteria);
}
/**
* {@inheritdoc}
*/
public function getClassMetadata()
{
return $this->persister->getClassMetadata();
}
/**
* {@inheritdoc}
*/
public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{
return $this->persister->getManyToManyCollection($assoc, $sourceEntity, $offset, $limit);
}
/**
* {@inheritdoc}
*/
public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{
return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit);
}
/**
* {@inheritdoc}
*/
public function getOwningTable($fieldName)
{
return $this->persister->getOwningTable($fieldName);
}
/**
* {@inheritdoc}
*/
public function executeInserts()
{
$this->queuedCache['insert'] = $this->persister->getInserts();
return $this->persister->executeInserts();
}
/**
* {@inheritdoc}
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null, array $orderBy = null)
{
if ($entity !== null || $assoc !== null || ! empty($hints) || $lockMode !== 0) {
return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
}
//handle only EntityRepository#findOneBy
$timestamp = $this->timestampRegion->get($this->timestampKey);
$query = $this->persister->getSelectSQL($criteria, null, 0, $limit, 0, $orderBy);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryCache = $this->cache->getQueryCache($this->regionName);
$result = $queryCache->get($querykey, $rsm);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
}
return $result[0];
}
if (($result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy)) === null) {
return null;
}
$cached = $queryCache->put($querykey, $rsm, array($result));
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
}
}
return $result;
}
/**
* {@inheritdoc}
*/
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null)
{
$timestamp = $this->timestampRegion->get($this->timestampKey);
$query = $this->persister->getSelectSQL($criteria, null, 0, $limit, $offset, $orderBy);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryCache = $this->cache->getQueryCache($this->regionName);
$result = $queryCache->get($querykey, $rsm);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
}
return $result;
}
$result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset);
$cached = $queryCache->put($querykey, $rsm, $result);
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
}
}
return $result;
}
/**
* {@inheritdoc}
*/
public function loadById(array $identifier, $entity = null)
{
$cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier);
$cacheEntry = $this->region->get($cacheKey);
$class = $this->class;
if ($cacheEntry !== null) {
if ($cacheEntry->class !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($cacheEntry->class);
}
if (($entity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity)) !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->entityCacheHit($this->regionName, $cacheKey);
}
return $entity;
}
}
$entity = $this->persister->loadById($identifier, $entity);
if ($entity === null) {
return null;
}
$class = $this->class;
$className = ClassUtils::getClass($entity);
if ($className !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($className);
}
$cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity);
$cached = $this->region->put($cacheKey, $cacheEntry);
if ($cached && ($this->joinedAssociations === null || count($this->joinedAssociations) > 0)) {
$this->storeJoinedAssociations($entity);
}
if ($this->cacheLogger) {
if ($cached) {
$this->cacheLogger->entityCachePut($this->regionName, $cacheKey);
}
$this->cacheLogger->entityCacheMiss($this->regionName, $cacheKey);
}
return $entity;
}
/**
* {@inheritdoc}
*/
public function loadCriteria(Criteria $criteria)
{
return $this->persister->loadCriteria($criteria);
}
/**
* {@inheritdoc}
*/
public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
{
$persister = $this->uow->getCollectionPersister($assoc);
$hasCache = ($persister instanceof CachedPersister);
$key = null;
if ($hasCache) {
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
}
return $list;
}
}
$list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
if ($hasCache && ! empty($list)) {
$persister->storeCollectionCache($key, $list);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
}
}
return $list;
}
/**
* {@inheritdoc}
*/
public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
{
$persister = $this->uow->getCollectionPersister($assoc);
$hasCache = ($persister instanceof CachedPersister);
if ($hasCache) {
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
}
return $list;
}
}
$list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
if ($hasCache && ! empty($list)) {
$persister->storeCollectionCache($key, $list);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
}
}
return $list;
}
/**
* {@inheritdoc}
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
{
return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
}
/**
* {@inheritdoc}
*/
public function lock(array $criteria, $lockMode)
{
$this->persister->lock($criteria, $lockMode);
}
/**
* {@inheritdoc}
*/
public function refresh(array $id, $entity, $lockMode = 0)
{
$this->persister->refresh($id, $entity, $lockMode);
}
}

View File

@ -0,0 +1,64 @@
<?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\Cache\Persister;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Persisters\CollectionPersister;
use Doctrine\ORM\PersistentCollection;
/**
* Interface for second level cache collection persisters.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
interface CachedCollectionPersister extends CachedPersister, CollectionPersister
{
/**
* @return \Doctrine\ORM\Mapping\ClassMetadata
*/
public function getSourceEntityMetadata();
/**
* @return \Doctrine\ORM\Mapping\ClassMetadata
*/
public function getTargetEntityMetadata();
/**
* Loads a collection from cache
*
* @param \Doctrine\ORM\PersistentCollection $collection
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key
*
* @return \Doctrine\ORM\PersistentCollection|null
*/
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key);
/**
* Stores a collection into cache
*
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key
* @param array|\Doctrine\Common\Collections\Collection $elements
*
* @return void
*/
public function storeCollectionCache(CollectionCacheKey $key, $elements);
}

View File

@ -0,0 +1,45 @@
<?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\Cache\Persister;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Persisters\EntityPersister;
/**
* Interface for second level cache entity persisters.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
interface CachedEntityPersister extends CachedPersister, EntityPersister
{
/**
* @return \Doctrine\ORM\Cache\EntityHydrator
*/
public function getEntityHydrator();
/**
* @param object $entity
* @param \Doctrine\ORM\Cache\EntityCacheKey $key
* @return boolean
*/
public function storeEntityCache($entity, EntityCacheKey $key);
}

View File

@ -0,0 +1,46 @@
<?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\Cache\Persister;
/**
* Interface for persister that support second level cache.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CachedPersister
{
/**
* Perform whatever processing is encapsulated here after completion of the transaction.
*/
public function afterTransactionComplete();
/**
* Perform whatever processing is encapsulated here after completion of the rolled-back.
*/
public function afterTransactionRolledBack();
/**
* Gets the The region access.
*
* @return \Doctrine\ORM\Cache\Region
*/
public function getCacheRegion();
}

View File

@ -0,0 +1,104 @@
<?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\Cache\Persister;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\PersistentCollection;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
/**
* {@inheritdoc}
*/
public function afterTransactionComplete()
{
if (isset($this->queuedCache['update'])) {
foreach ($this->queuedCache['update'] as $item) {
$this->storeCollectionCache($item['key'], $item['list']);
}
}
if (isset($this->queuedCache['delete'])) {
foreach ($this->queuedCache['delete'] as $key) {
$this->region->evict($key);
}
}
$this->queuedCache = array();
}
/**
* {@inheritdoc}
*/
public function afterTransactionRolledBack()
{
$this->queuedCache = array();
}
/**
* {@inheritdoc}
*/
public function delete(PersistentCollection $collection)
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId);
$this->persister->delete($collection);
$this->queuedCache['delete'][spl_object_hash($collection)] = $key;
}
/**
* {@inheritdoc}
*/
public function update(PersistentCollection $collection)
{
$isInitialized = $collection->isInitialized();
$isDirty = $collection->isDirty();
if ( ! $isInitialized && ! $isDirty) {
return;
}
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId);
// Invalidate non initialized collections OR ordered collection
if ($isDirty && ! $isInitialized || isset($this->association['orderBy'])) {
$this->persister->update($collection);
$this->queuedCache['delete'][spl_object_hash($collection)] = $key;
return;
}
$this->persister->update($collection);
$this->queuedCache['update'][spl_object_hash($collection)] = array(
'key' => $key,
'list' => $collection
);
}
}

View File

@ -0,0 +1,124 @@
<?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\Cache\Persister;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\Common\Util\ClassUtils;
/**
* Specific non-strict read/write cached entity persister
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
{
/**
* {@inheritdoc}
*/
public function afterTransactionComplete()
{
$isChanged = false;
if (isset($this->queuedCache['insert'])) {
foreach ($this->queuedCache['insert'] as $entity) {
$class = $this->class;
$className = ClassUtils::getClass($entity);
if ($className !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($className);
}
$key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
$isChanged = $isChanged ?: $cached;
if ($this->cacheLogger && $cached) {
$this->cacheLogger->entityCachePut($this->regionName, $key);
}
}
}
if (isset($this->queuedCache['update'])) {
foreach ($this->queuedCache['update'] as $entity) {
$class = $this->class;
$className = ClassUtils::getClass($entity);
if ($className !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($className);
}
$key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
$isChanged = $isChanged ?: $cached;
if ($this->cacheLogger && $cached) {
$this->cacheLogger->entityCachePut($this->regionName, $key);
}
}
}
if (isset($this->queuedCache['delete'])) {
foreach ($this->queuedCache['delete'] as $key) {
$this->region->evict($key);
$isChanged = true;
}
}
if ($isChanged) {
$this->timestampRegion->update($this->timestampKey);
}
$this->queuedCache = array();
}
/**
* {@inheritdoc}
*/
public function afterTransactionRolledBack()
{
$this->queuedCache = array();
}
/**
* {@inheritdoc}
*/
public function delete($entity)
{
$this->persister->delete($entity);
$this->queuedCache['delete'][] = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
}
/**
* {@inheritdoc}
*/
public function update($entity)
{
$this->persister->update($entity);
$this->queuedCache['update'][] = $entity;
}
}

View File

@ -0,0 +1,44 @@
<?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\Cache\Persister;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Cache\CacheException;
use Doctrine\Common\Util\ClassUtils;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister
{
/**
* {@inheritdoc}
*/
public function update(PersistentCollection $collection)
{
if ($collection->isDirty() && count($collection->getSnapshot()) > 0) {
throw CacheException::updateReadOnlyCollection(ClassUtils::getClass($collection->getOwner()), $this->association['fieldName']);
}
parent::update($collection);
}
}

View File

@ -0,0 +1,41 @@
<?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\Cache\Persister;
use Doctrine\ORM\Cache\CacheException;
use Doctrine\Common\Util\ClassUtils;
/**
* Specific read-only region entity persister
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister
{
/**
* {@inheritdoc}
*/
public function update($entity)
{
throw CacheException::updateReadOnlyEntity(ClassUtils::getClass($entity));
}
}

View File

@ -0,0 +1,135 @@
<?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\Cache\Persister;
use Doctrine\ORM\Persisters\CollectionPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\ConcurrentRegion;
use Doctrine\ORM\PersistentCollection;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
/**
* @param \Doctrine\ORM\Persisters\CollectionPersister $persister The collection persister that will be cached.
* @param \Doctrine\ORM\Cache\ConcurrentRegion $region The collection region.
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param array $association The association mapping.
*/
public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association)
{
parent::__construct($persister, $region, $em, $association);
}
/**
* {@inheritdoc}
*/
public function afterTransactionComplete()
{
if (isset($this->queuedCache['update'])) {
foreach ($this->queuedCache['update'] as $item) {
$this->region->evict($item['key']);
}
}
if (isset($this->queuedCache['delete'])) {
foreach ($this->queuedCache['delete'] as $item) {
$this->region->evict($item['key']);
}
}
$this->queuedCache = array();
}
/**
* {@inheritdoc}
*/
public function afterTransactionRolledBack()
{
if (isset($this->queuedCache['update'])) {
foreach ($this->queuedCache['update'] as $item) {
$this->region->evict($item['key']);
}
}
if (isset($this->queuedCache['delete'])) {
foreach ($this->queuedCache['delete'] as $item) {
$this->region->evict($item['key']);
}
}
$this->queuedCache = array();
}
/**
* {@inheritdoc}
*/
public function delete(PersistentCollection $collection)
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId);
$lock = $this->region->lock($key);
$this->persister->delete($collection);
if ($lock === null) {
return;
}
$this->queuedCache['delete'][spl_object_hash($collection)] = array(
'key' => $key,
'lock' => $lock
);
}
/**
* {@inheritdoc}
*/
public function update(PersistentCollection $collection)
{
$isInitialized = $collection->isInitialized();
$isDirty = $collection->isDirty();
if ( ! $isInitialized && ! $isDirty) {
return;
}
$this->persister->update($collection);
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId);
$lock = $this->region->lock($key);
if ($lock === null) {
return;
}
$this->queuedCache['update'][spl_object_hash($collection)] = array(
'key' => $key,
'lock' => $lock
);
}
}

View File

@ -0,0 +1,138 @@
<?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\Cache\Persister;
use Doctrine\ORM\Persisters\EntityPersister;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\ConcurrentRegion;
use Doctrine\ORM\Cache\EntityCacheKey;
/**
* Specific read-write entity persister
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
class ReadWriteCachedEntityPersister extends AbstractEntityPersister
{
/**
* @param \Doctrine\ORM\Persister\EntityPersister $persister The entity persister to cache.
* @param \Doctrine\ORM\Cache\ConcurrentRegion $region The entity cache region.
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Mapping\ClassMetadata $class The entity metadata.
*/
public function __construct(EntityPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, ClassMetadata $class)
{
parent::__construct($persister, $region, $em, $class);
}
/**
* {@inheritdoc}
*/
public function afterTransactionComplete()
{
$isChanged = true;
if (isset($this->queuedCache['update'])) {
foreach ($this->queuedCache['update'] as $item) {
$this->region->evict($item['key']);
$isChanged = true;
}
}
if (isset($this->queuedCache['delete'])) {
foreach ($this->queuedCache['delete'] as $item) {
$this->region->evict($item['key']);
$isChanged = true;
}
}
if ($isChanged) {
$this->timestampRegion->update($this->timestampKey);
}
$this->queuedCache = array();
}
/**
* {@inheritdoc}
*/
public function afterTransactionRolledBack()
{
if (isset($this->queuedCache['update'])) {
foreach ($this->queuedCache['update'] as $item) {
$this->region->evict($item['key']);
}
}
if (isset($this->queuedCache['delete'])) {
foreach ($this->queuedCache['delete'] as $item) {
$this->region->evict($item['key']);
}
}
$this->queuedCache = array();
}
/**
* {@inheritdoc}
*/
public function delete($entity)
{
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$lock = $this->region->lock($key);
$this->persister->delete($entity);
if ($lock === null) {
return;
}
$this->queuedCache['delete'][] = array(
'lock' => $lock,
'key' => $key
);
}
/**
* {@inheritdoc}
*/
public function update($entity)
{
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$lock = $this->region->lock($key);
$this->persister->update($entity);
if ($lock === null) {
return;
}
$this->queuedCache['update'][] = array(
'lock' => $lock,
'key' => $key
);
}
}

View File

@ -0,0 +1,62 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Query\ResultSetMapping;
/**
* Defines the contract for caches capable of storing query results.
* These caches should only concern themselves with storing the matching result ids.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface QueryCache
{
/**
* @return boolean
*/
public function clear();
/**
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
* @param mixed $result
* @param array $hints
*
* @return boolean
*/
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array());
/**
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
* @param array $hints
*
* @return array|null
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array());
/**
* @return \Doctrine\ORM\Cache\Region
*/
public function getRegion();
}

View File

@ -0,0 +1,62 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Cache;
/**
* Query cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class QueryCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array List of entity identifiers
*/
public $result;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var integer Time creation of this cache entry
*/
public $time;
/**
* @param array $result
* @param integer $time
*/
public function __construct($result, $time = null)
{
$this->result = $result;
$this->time = $time ?: time();
}
/**
* @param array $values
*/
public static function __set_state(array $values)
{
return new self($values['result'], $values['time']);
}
}

View File

@ -0,0 +1,58 @@
<?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\Cache;
use Doctrine\ORM\Cache;
/**
* A cache key that identifies a particular query.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class QueryCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var integer Cache key lifetime
*/
public $lifetime;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var integer Cache mode (Doctrine\ORM\Cache::MODE_*)
*/
public $cacheMode;
/**
* @param string $hash Result cache id
* @param integer $lifetime Query lifetime
* @param integer $cacheMode Query cache mode
*/
public function __construct($hash, $lifetime = 0, $cacheMode = Cache::MODE_NORMAL)
{
$this->hash = $hash;
$this->lifetime = $lifetime;
$this->cacheMode = $cacheMode;
}
}

View File

@ -0,0 +1,42 @@
<?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\Cache;
use Doctrine\ORM\Cache\QueryCacheEntry;
/**
* Cache query validator interface.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface QueryCacheValidator
{
/**
* Checks if the query entry is valid
*
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
* @param \Doctrine\ORM\Cache\QueryCacheEntry $entry
*
* @return boolean
*/
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry);
}

View File

@ -0,0 +1,86 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Lock;
/**
* Defines a contract for accessing a particular named region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface Region
{
/**
* Retrieve the name of this region.
*
* @return string The region name
*/
public function getName();
/**
* Determine whether this region contains data for the given key.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The cache key
*
* @return boolean TRUE if the underlying cache contains corresponding data; FALSE otherwise.
*/
public function contains(CacheKey $key);
/**
* Get an item from the cache.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to be retrieved.
*
* @return \Doctrine\ORM\Cache\CacheEntry|null The cached entry or NULL
*
* @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the item or region.
*/
public function get(CacheKey $key);
/**
* Put an item into the cache.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item.
* @param \Doctrine\ORM\Cache\CacheEntry $entry The entry to cache.
* @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained.
*
* @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region.
*/
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null);
/**
* Remove an item from the cache.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item.
*
* @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region.
*/
public function evict(CacheKey $key);
/**
* Remove all contents of this particular cache region.
*
* @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region.
*/
public function evictAll();
}

View File

@ -0,0 +1,121 @@
<?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\Cache\Region;
use Doctrine\ORM\Cache\Lock;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\CacheKey;
use Doctrine\ORM\Cache\CacheEntry;
use Doctrine\Common\Cache\CacheProvider;
/**
* The simplest cache region compatible with all doctrine-cache drivers.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultRegion implements Region
{
/**
* @var \Doctrine\Common\Cache\CacheProvider
*/
protected $cache;
/**
* @var string
*/
protected $name;
/**
* @var integer
*/
protected $lifetime = 0;
/**
* @param string $name
* @param \Doctrine\Common\Cache\CacheProvider $cache
* @param integer $lifetime
*/
public function __construct($name, CacheProvider $cache, $lifetime = 0)
{
$this->cache = $cache;
$this->name = (string) $name;
$this->lifetime = (integer) $lifetime;
$this->cache->setNamespace($this->name);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* @return \Doctrine\Common\Cache\CacheProvider
*/
public function getCache()
{
return $this->cache;
}
/**
* {@inheritdoc}
*/
public function contains(CacheKey $key)
{
return $this->cache->contains($this->name . '_' . $key->hash);
}
/**
* {@inheritdoc}
*/
public function get(CacheKey $key)
{
return $this->cache->fetch($this->name . '_' . $key->hash) ?: null;
}
/**
* {@inheritdoc}
*/
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
{
return $this->cache->save($this->name . '_' . $key->hash, $entry, $this->lifetime);
}
/**
* {@inheritdoc}
*/
public function evict(CacheKey $key)
{
return $this->cache->delete($this->name . '_' . $key->hash);
}
/**
* {@inheritdoc}
*/
public function evictAll()
{
return $this->cache->deleteAll();
}
}

View File

@ -0,0 +1,245 @@
<?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\Cache\Region;
use Doctrine\ORM\Cache\Lock;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\CacheKey;
use Doctrine\ORM\Cache\CacheEntry;
use Doctrine\ORM\Cache\ConcurrentRegion;
/**
* Very naive concurrent region, based on file locks.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silvagmail.com>
*/
class FileLockRegion implements ConcurrentRegion
{
const LOCK_EXTENSION = 'lock';
/**
* var \Doctrine\ORM\Cache\Region
*/
private $region;
/**
* @var string
*/
private $directory;
/**
* var integer
*/
private $lockLifetime;
/**
* @param \Doctrine\ORM\Cache\Region $region
* @param string $directory
* @param string $lockLifetime
*
* @throws \InvalidArgumentException
*/
public function __construct(Region $region, $directory, $lockLifetime)
{
if ( ! is_dir($directory) && ! @mkdir($directory, 0777, true)) {
throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory));
}
if ( ! is_writable($directory)) {
throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable.', $directory));
}
$this->region = $region;
$this->directory = $directory;
$this->lockLifetime = $lockLifetime;
}
/**
* @param \Doctrine\ORM\Cache\CacheKey $key
* @param \Doctrine\ORM\Cache\Lock $lock
*
* @return boolean
*/
private function isLocked(CacheKey $key, Lock $lock = null)
{
$filename = $this->getLockFileName($key);
if ( ! is_file($filename)) {
return false;
}
$time = $this->getLockTime($filename);
$content = $this->getLockContent($filename);
if ( ! $content || ! $time) {
@unlink($filename);
return false;
}
if ($lock && $content === $lock->value) {
return false;
}
// outdated lock
if (($time + $this->lockLifetime) <= time()) {
@unlink($filename);
return false;
}
return true;
}
/**
* @param \Doctrine\ORM\Cache\CacheKey $key
*
* return string
*/
private function getLockFileName(CacheKey $key)
{
return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION;
}
/**
* @param string $filename
*
* return string
*/
private function getLockContent($filename)
{
return @file_get_contents($filename);
}
/**
* @param string $filename
*
* return integer
*/
private function getLockTime($filename)
{
return @fileatime($filename);
}
/**
* {inheritdoc}
*/
public function getName()
{
return $this->region->getName();
}
/**
* {inheritdoc}
*/
public function contains(CacheKey $key)
{
if ($this->isLocked($key)) {
return false;
}
return $this->region->contains($key);
}
/**
* {inheritdoc}
*/
public function get(CacheKey $key)
{
if ($this->isLocked($key)) {
return null;
}
return $this->region->get($key);
}
/**
* {inheritdoc}
*/
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
{
if ($this->isLocked($key, $lock)) {
return false;
}
return $this->region->put($key, $entry);
}
/**
* {inheritdoc}
*/
public function evict(CacheKey $key)
{
if ($this->isLocked($key)) {
@unlink($this->getLockFileName($key));
}
return $this->region->evict($key);
}
/**
* {inheritdoc}
*/
public function evictAll()
{
foreach (glob(sprintf("%s/*.%s" , $this->directory, self::LOCK_EXTENSION)) as $filename) {
@unlink($filename);
}
return $this->region->evictAll();
}
/**
* {inheritdoc}
*/
public function lock(CacheKey $key)
{
if ($this->isLocked($key)) {
return null;
}
$lock = Lock::createLockRead();
$filename = $this->getLockFileName($key);
if ( ! @file_put_contents($filename, $lock->value, LOCK_EX)) {
return null;
}
return $lock;
}
/**
* {inheritdoc}
*/
public function unlock(CacheKey $key, Lock $lock)
{
if ($this->isLocked($key, $lock)) {
return false;
}
if ( ! @unlink($this->getLockFileName($key))) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,42 @@
<?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\Cache\Region;
use Doctrine\ORM\Cache\TimestampCacheEntry;
use Doctrine\ORM\Cache\TimestampRegion;
use Doctrine\ORM\Cache\CacheKey;
/**
* Tracks the timestamps of the most recent updates to particular keys.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class UpdateTimestampCache extends DefaultRegion implements TimestampRegion
{
/**
* {@inheritdoc}
*/
public function update(CacheKey $key)
{
$this->put($key, new TimestampCacheEntry);
}
}

View File

@ -0,0 +1,134 @@
<?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\Cache;
/**
* Cache regions configuration
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class RegionsConfiguration
{
/**
* @var array
*/
private $lifetimes = array();
/**
* @var array
*/
private $lockLifetimes = array();
/**
* @var integer
*/
private $defaultLifetime;
/**
* @var integer
*/
private $defaultLockLifetime;
/**
* @param integer $defaultLifetime
* @param integer $defaultLockLifetime
*/
public function __construct($defaultLifetime = 3600, $defaultLockLifetime = 60)
{
$this->defaultLifetime = (integer) $defaultLifetime;
$this->defaultLockLifetime = (integer) $defaultLockLifetime;
}
/**
* @return integer
*/
public function getDefaultLifetime()
{
return $this->defaultLifetime;
}
/**
* @param integer $defaultLifetime
*/
public function setDefaultLifetime($defaultLifetime)
{
$this->defaultLifetime = (integer) $defaultLifetime;
}
/**
* @return integer
*/
public function getDefaultLockLifetime()
{
return $this->defaultLockLifetime;
}
/**
* @param integer $defaultLockLifetime
*/
public function setDefaultLockLifetime($defaultLockLifetime)
{
$this->defaultLockLifetime = (integer) $defaultLockLifetime;
}
/**
* @param string $regionName
*
* @return integer
*/
public function getLifetime($regionName)
{
return isset($this->lifetimes[$regionName])
? $this->lifetimes[$regionName]
: $this->defaultLifetime;
}
/**
* @param string $name
* @param integer $lifetime
*/
public function setLifetime($name, $lifetime)
{
$this->lifetimes[$name] = (integer) $lifetime;
}
/**
* @param string $regionName
*
* @return integer
*/
public function getLockLifetime($regionName)
{
return isset($this->lockLifetimes[$regionName])
? $this->lockLifetimes[$regionName]
: $this->defaultLockLifetime;
}
/**
* @param string $name
* @param integer $lifetime
*/
public function setLockLifetime($name, $lifetime)
{
$this->lockLifetimes[$name] = (integer) $lifetime;
}
}

View File

@ -0,0 +1,57 @@
<?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\Cache;
/**
* Timestamp cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class TimestampCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var float
*/
public $time;
/**
* @param float $time
*/
public function __construct($time = null)
{
$this->time = $time ? (float)$time : microtime(true);
}
/**
* Creates a new TimestampCacheEntry
*
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*/
public static function __set_state(array $values)
{
return new self($values['time']);
}
}

View File

@ -0,0 +1,38 @@
<?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\Cache;
/**
* A key that identifies a timestamped space.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class TimestampCacheKey extends CacheKey
{
/**
* @param string $space Result cache id
*/
public function __construct($space)
{
$this->hash = (string) $space;
}
}

View File

@ -0,0 +1,43 @@
<?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\Cache;
use Doctrine\ORM\Cache\QueryCacheEntry;
use Doctrine\ORM\Cache\QueryCacheKey;
/**
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class TimestampQueryCacheValidator implements QueryCacheValidator
{
/**
* {@inheritdoc}
*/
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry)
{
if ($key->lifetime == 0) {
return true;
}
return ($entry->time + $key->lifetime) > time();
}
}

View File

@ -0,0 +1,39 @@
<?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\Cache;
/**
* Defines the contract for a cache region which will specifically be used to store entity "update timestamps".
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface TimestampRegion extends Region
{
/**
* Update an specific key into the cache region.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to update the timestamp.
*
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
*/
public function update(CacheKey $key);
}

View File

@ -24,7 +24,8 @@ use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\Cache as CacheDriver;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
@ -252,7 +253,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @return void
*/
public function setQueryCacheImpl(Cache $cacheImpl)
public function setQueryCacheImpl(CacheDriver $cacheImpl)
{
$this->_attributes['queryCacheImpl'] = $cacheImpl;
}
@ -276,7 +277,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @return void
*/
public function setHydrationCacheImpl(Cache $cacheImpl)
public function setHydrationCacheImpl(CacheDriver $cacheImpl)
{
$this->_attributes['hydrationCacheImpl'] = $cacheImpl;
}
@ -300,7 +301,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @return void
*/
public function setMetadataCacheImpl(Cache $cacheImpl)
public function setMetadataCacheImpl(CacheDriver $cacheImpl)
{
$this->_attributes['metadataCacheImpl'] = $cacheImpl;
}
@ -805,4 +806,56 @@ class Configuration extends \Doctrine\DBAL\Configuration
? $this->_attributes['repositoryFactory']
: new DefaultRepositoryFactory();
}
/**
* @since 2.5
*
* @return boolean
*/
public function isSecondLevelCacheEnabled()
{
return isset($this->_attributes['isSecondLevelCacheEnabled'])
? $this->_attributes['isSecondLevelCacheEnabled']
: false;
}
/**
* @since 2.5
*
* @param boolean $flag
*
* @return void
*/
public function setSecondLevelCacheEnabled($flag = true)
{
$this->_attributes['isSecondLevelCacheEnabled'] = (boolean) $flag;
}
/**
* @since 2.5
*
* @param \Doctrine\ORM\Cache\CacheConfiguration $cacheConfig
*
* @return void
*/
public function setSecondLevelCacheConfiguration(CacheConfiguration $cacheConfig)
{
$this->_attributes['secondLevelCacheConfiguration'] = $cacheConfig;
}
/**
* @since 2.5
*
* @return \Doctrine\ORM\Cache\CacheConfiguration|null
*/
public function getSecondLevelCacheConfiguration()
{
if ( ! isset($this->_attributes['secondLevelCacheConfiguration']) && $this->isSecondLevelCacheEnabled()) {
$this->_attributes['secondLevelCacheConfiguration'] = new CacheConfiguration();
}
return isset($this->_attributes['secondLevelCacheConfiguration'])
? $this->_attributes['secondLevelCacheConfiguration']
: null;
}
}

View File

@ -268,4 +268,12 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
{
return $this->wrapped->hasFilters();
}
/**
* {@inheritdoc}
*/
public function getCache()
{
return $this->wrapped->getCache();
}
}

View File

@ -131,6 +131,11 @@ use Doctrine\Common\Util\ClassUtils;
*/
private $filterCollection;
/**
* @var \Doctrine\ORM\Cache The second level cache regions API.
*/
private $cache;
/**
* Creates a new EntityManager that operates on the given database connection
* and uses the given Configuration and EventManager implementations.
@ -159,12 +164,15 @@ use Doctrine\Common\Util\ClassUtils;
$config->getProxyNamespace(),
$config->getAutoGenerateProxyClasses()
);
if ($config->isSecondLevelCacheEnabled()) {
$cacheClass = $config->getSecondLevelCacheConfiguration()->getCacheClassName();
$this->cache = new $cacheClass($this);
}
}
/**
* Gets the database connection object used by the EntityManager.
*
* @return \Doctrine\DBAL\Connection
* {@inheritDoc}
*/
public function getConnection()
{
@ -182,18 +190,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Gets an ExpressionBuilder used for object-oriented construction of query expressions.
*
* Example:
*
* <code>
* $qb = $em->createQueryBuilder();
* $expr = $em->getExpressionBuilder();
* $qb->select('u')->from('User', 'u')
* ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2)));
* </code>
*
* @return \Doctrine\ORM\Query\Expr
* {@inheritDoc}
*/
public function getExpressionBuilder()
{
@ -205,9 +202,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Starts a transaction on the underlying database connection.
*
* @return void
* {@inheritDoc}
*/
public function beginTransaction()
{
@ -215,18 +210,15 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Executes a function in a transaction.
*
* The function gets passed this EntityManager instance as an (optional) parameter.
*
* {@link flush} is invoked prior to transaction commit.
*
* If an exception occurs during execution of the function or flushing or transaction commit,
* the transaction is rolled back, the EntityManager closed and the exception re-thrown.
*
* @param callable $func The function to execute transactionally.
*
* @return mixed The non-empty value returned from the closure or true instead.
* {@inheritDoc}
*/
public function getCache()
{
return $this->cache;
}
/**
* {@inheritDoc}
*/
public function transactional($func)
{
@ -252,9 +244,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Commits a transaction on the underlying database connection.
*
* @return void
* {@inheritDoc}
*/
public function commit()
{
@ -262,9 +252,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Performs a rollback on the underlying database connection.
*
* @return void
* {@inheritDoc}
*/
public function rollback()
{
@ -293,11 +281,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Creates a new Query object.
*
* @param string $dql The DQL string.
*
* @return \Doctrine\ORM\Query
* {@inheritDoc}
*/
public function createQuery($dql = '')
{
@ -311,11 +295,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Creates a Query from a named query.
*
* @param string $name
*
* @return \Doctrine\ORM\Query
* {@inheritDoc}
*/
public function createNamedQuery($name)
{
@ -323,12 +303,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Creates a native SQL query.
*
* @param string $sql
* @param ResultSetMapping $rsm The ResultSetMapping to use.
*
* @return NativeQuery
* {@inheritDoc}
*/
public function createNativeQuery($sql, ResultSetMapping $rsm)
{
@ -341,11 +316,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Creates a NativeQuery from a named native query.
*
* @param string $name
*
* @return \Doctrine\ORM\NativeQuery
* {@inheritDoc}
*/
public function createNamedNativeQuery($name)
{
@ -355,9 +326,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Create a QueryBuilder instance
*
* @return QueryBuilder
* {@inheritDoc}
*/
public function createQueryBuilder()
{
@ -454,7 +423,7 @@ use Doctrine\Common\Util\ClassUtils;
switch ($lockMode) {
case LockMode::NONE:
return $persister->load($sortedId);
return $persister->loadById($sortedId);
case LockMode::OPTIMISTIC:
if ( ! $class->isVersioned) {
@ -477,15 +446,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Gets a reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* @param string $entityName The name of the entity type.
* @param mixed $id The entity identifier.
*
* @return object The entity reference.
*
* @throws ORMException
* {@inheritDoc}
*/
public function getReference($entityName, $id)
{
@ -526,24 +487,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Gets a partial reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* The returned reference may be a partial object if the entity is not yet loaded/managed.
* If it is a partial object it will not initialize the rest of the entity state on access.
* Thus you can only ever safely access the identifier of an entity obtained through
* this method.
*
* The use-cases for partial references involve maintaining bidirectional associations
* without loading one side of the association or to update an entity without loading it.
* Note, however, that in the latter case the original (persistent) entity data will
* never be visible to the application (especially not event listeners) as it will
* never be loaded in the first place.
*
* @param string $entityName The name of the entity type.
* @param mixed $identifier The entity identifier.
*
* @return object The (partial) entity reference.
* {@inheritDoc}
*/
public function getPartialReference($entityName, $identifier)
{
@ -582,11 +526,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Closes the EntityManager. All entities that are currently managed
* by this EntityManager become detached. The EntityManager may no longer
* be used after it is closed.
*
* @return void
* {@inheritDoc}
*/
public function close()
{
@ -710,14 +650,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Creates a copy of the given entity. Can create a shallow or a deep copy.
*
* @param object $entity The entity to copy.
* @param boolean $deep FALSE for a shallow copy, TRUE for a deep copy.
*
* @return object The new entity.
*
* @throws \BadMethodCallException
* {@inheritDoc}
*
* @todo Implementation need. This is necessary since $e2 = clone $e1; throws an E_FATAL when access anything on $e:
* Fatal error: Maximum function nesting level of '100' reached, aborting!
@ -728,16 +661,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Acquire a lock on the given entity.
*
* @param object $entity
* @param int $lockMode
* @param int|null $lockVersion
*
* @return void
*
* @throws OptimisticLockException
* @throws PessimisticLockException
* {@inheritDoc}
*/
public function lock($entity, $lockMode, $lockVersion = null)
{
@ -771,9 +695,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Gets the EventManager used by the EntityManager.
*
* @return \Doctrine\Common\EventManager
* {@inheritDoc}
*/
public function getEventManager()
{
@ -781,9 +703,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Gets the Configuration used by the EntityManager.
*
* @return \Doctrine\ORM\Configuration
* {@inheritDoc}
*/
public function getConfiguration()
{
@ -805,9 +725,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Check if the Entity manager is open or closed.
*
* @return bool
* {@inheritDoc}
*/
public function isOpen()
{
@ -815,9 +733,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Gets the UnitOfWork used by the EntityManager to coordinate operations.
*
* @return \Doctrine\ORM\UnitOfWork
* {@inheritDoc}
*/
public function getUnitOfWork()
{
@ -825,16 +741,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Gets a hydrator for the given hydration mode.
*
* This method caches the hydrator instances which is used for all queries that don't
* selectively iterate over the result.
*
* @deprecated
*
* @param int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
* {@inheritDoc}
*/
public function getHydrator($hydrationMode)
{
@ -842,13 +749,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Create a new instance for the given hydration mode.
*
* @param int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*
* @throws ORMException
* {@inheritDoc}
*/
public function newHydrator($hydrationMode)
{
@ -878,9 +779,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Gets the proxy factory used by the EntityManager to create entity proxies.
*
* @return ProxyFactory
* {@inheritDoc}
*/
public function getProxyFactory()
{
@ -888,13 +787,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Helper method to initialize a lazy loading proxy or persistent collection.
*
* This method is a no-op for other objects
*
* @param object $obj
*
* @return void
* {@inheritDoc}
*/
public function initializeObject($obj)
{
@ -940,9 +833,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Gets the enabled filters.
*
* @return FilterCollection The active filter collection.
* {@inheritDoc}
*/
public function getFilters()
{
@ -954,9 +845,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Checks whether the state of the filter collection is clean.
*
* @return boolean True, if the filter collection is clean.
* {@inheritDoc}
*/
public function isFiltersStateClean()
{
@ -964,9 +853,7 @@ use Doctrine\Common\Util\ClassUtils;
}
/**
* Checks whether the Entity Manager has filters.
*
* @return boolean True, if the EM has a filter collection.
* {@inheritDoc}
*/
public function hasFilters()
{

View File

@ -20,7 +20,6 @@
namespace Doctrine\ORM;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Query\ResultSetMapping;
/**
@ -31,30 +30,265 @@ use Doctrine\ORM\Query\ResultSetMapping;
*/
interface EntityManagerInterface extends ObjectManager
{
/**
* Returns the cache API for managing the second level cache regions or NULL if the cache is not enabled.
*
* @return \Doctrine\ORM\Cache|null
*/
public function getCache();
/**
* Gets the database connection object used by the EntityManager.
*
* @return \Doctrine\DBAL\Connection
*/
public function getConnection();
/**
* Gets an ExpressionBuilder used for object-oriented construction of query expressions.
*
* Example:
*
* <code>
* $qb = $em->createQueryBuilder();
* $expr = $em->getExpressionBuilder();
* $qb->select('u')->from('User', 'u')
* ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2)));
* </code>
*
* @return \Doctrine\ORM\Query\Expr
*/
public function getExpressionBuilder();
/**
* Starts a transaction on the underlying database connection.
*
* @return void
*/
public function beginTransaction();
/**
* Executes a function in a transaction.
*
* The function gets passed this EntityManager instance as an (optional) parameter.
*
* {@link flush} is invoked prior to transaction commit.
*
* If an exception occurs during execution of the function or flushing or transaction commit,
* the transaction is rolled back, the EntityManager closed and the exception re-thrown.
*
* @param callable $func The function to execute transactionally.
*
* @return mixed The non-empty value returned from the closure or true instead.
*/
public function transactional($func);
/**
* Commits a transaction on the underlying database connection.
*
* @return void
*/
public function commit();
/**
* Performs a rollback on the underlying database connection.
*
* @return void
*/
public function rollback();
/**
* Creates a new Query object.
*
* @param string $dql The DQL string.
*
* @return Query
*/
public function createQuery($dql = '');
/**
* Creates a Query from a named query.
*
* @param string $name
*
* @return Query
*/
public function createNamedQuery($name);
/**
* Creates a native SQL query.
*
* @param string $sql
* @param ResultSetMapping $rsm The ResultSetMapping to use.
*
* @return NativeQuery
*/
public function createNativeQuery($sql, ResultSetMapping $rsm);
/**
* Creates a NativeQuery from a named native query.
*
* @param string $name
*
* @return NativeQuery
*/
public function createNamedNativeQuery($name);
/**
* Create a QueryBuilder instance
*
* @return QueryBuilder
*/
public function createQueryBuilder();
/**
* Gets a reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* @param string $entityName The name of the entity type.
* @param mixed $id The entity identifier.
*
* @return object The entity reference.
*
* @throws ORMException
*/
public function getReference($entityName, $id);
/**
* Gets a partial reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* The returned reference may be a partial object if the entity is not yet loaded/managed.
* If it is a partial object it will not initialize the rest of the entity state on access.
* Thus you can only ever safely access the identifier of an entity obtained through
* this method.
*
* The use-cases for partial references involve maintaining bidirectional associations
* without loading one side of the association or to update an entity without loading it.
* Note, however, that in the latter case the original (persistent) entity data will
* never be visible to the application (especially not event listeners) as it will
* never be loaded in the first place.
*
* @param string $entityName The name of the entity type.
* @param mixed $identifier The entity identifier.
*
* @return object The (partial) entity reference.
*/
public function getPartialReference($entityName, $identifier);
/**
* Closes the EntityManager. All entities that are currently managed
* by this EntityManager become detached. The EntityManager may no longer
* be used after it is closed.
*
* @return void
*/
public function close();
/**
* Creates a copy of the given entity. Can create a shallow or a deep copy.
*
* @param object $entity The entity to copy.
* @param boolean $deep FALSE for a shallow copy, TRUE for a deep copy.
*
* @return object The new entity.
*
* @throws \BadMethodCallException
*/
public function copy($entity, $deep = false);
/**
* Acquire a lock on the given entity.
*
* @param object $entity
* @param int $lockMode
* @param int|null $lockVersion
*
* @return void
*
* @throws OptimisticLockException
* @throws PessimisticLockException
*/
public function lock($entity, $lockMode, $lockVersion = null);
/**
* Gets the EventManager used by the EntityManager.
*
* @return \Doctrine\Common\EventManager
*/
public function getEventManager();
/**
* Gets the Configuration used by the EntityManager.
*
* @return Configuration
*/
public function getConfiguration();
/**
* Check if the Entity manager is open or closed.
*
* @return bool
*/
public function isOpen();
/**
* Gets the UnitOfWork used by the EntityManager to coordinate operations.
*
* @return UnitOfWork
*/
public function getUnitOfWork();
/**
* Gets a hydrator for the given hydration mode.
*
* This method caches the hydrator instances which is used for all queries that don't
* selectively iterate over the result.
*
* @deprecated
*
* @param int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
public function getHydrator($hydrationMode);
/**
* Create a new instance for the given hydration mode.
*
* @param int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*
* @throws ORMException
*/
public function newHydrator($hydrationMode);
/**
* Gets the proxy factory used by the EntityManager to create entity proxies.
*
* @return \Doctrine\ORM\Proxy\ProxyFactory
*/
public function getProxyFactory();
/**
* Gets the enabled filters.
*
* @return \Doctrine\ORM\Query\FilterCollection The active filter collection.
*/
public function getFilters();
/**
* Checks whether the state of the filter collection is clean.
*
* @return boolean True, if the filter collection is clean.
*/
public function isFiltersStateClean();
/**
* Checks whether the Entity Manager has filters.
*
* @return boolean True, if the EM has a filter collection.
*/
public function hasFilters();
}

View File

@ -477,7 +477,7 @@ class ObjectHydrator extends AbstractHydrator
$targetClass = $this->ce[$relation['targetEntity']];
if ($relation['isOwningSide']) {
//TODO: Just check hints['fetched'] here?
// TODO: Just check hints['fetched'] here?
// If there is an inverse mapping on the target class its bidirectional
if ($relation['inversedBy']) {
$inverseAssoc = $targetClass->associationMappings[$relation['inversedBy']];

View File

@ -153,6 +153,20 @@ class FieldBuilder
return $this;
}
/**
* Sets an option.
*
* @param string $name
* @param mixed $value
*
* @return FieldBuilder
*/
public function option($name, $value)
{
$this->mapping['options'][$name] = $value;
return $this;
}
/**
* @param string $strategy
*

View File

@ -0,0 +1,44 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping;
/**
* Caching to an entity or a collection.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*
* @Annotation
* @Target({"CLASS","PROPERTY"})
*/
final class Cache implements Annotation
{
/**
* @Enum({"READ_ONLY", "NONSTRICT_READ_WRITE", "READ_WRITE"})
*
* @var string The concurrency strategy.
*/
public $usage = 'READ_ONLY';
/**
* @var string Cache region name.
*/
public $region;
}

View File

@ -154,6 +154,10 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$class->setPrimaryTable($parent->table);
}
if ($parent && $parent->cache) {
$class->cache = $parent->cache;
}
if ($parent && $parent->containsForeignIdentifier) {
$class->containsForeignIdentifier = true;
}
@ -468,17 +472,15 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
// Create & assign an appropriate ID generator instance
switch ($class->generatorType) {
case ClassMetadata::GENERATOR_TYPE_IDENTITY:
// For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to
// <table>_<column>_seq in PostgreSQL for SERIAL columns.
// Not pretty but necessary and the simplest solution that currently works.
$sequenceName = null;
$fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null;
if ($this->targetPlatform instanceof Platforms\PostgreSqlPlatform) {
$columnName = $class->getSingleIdentifierColumnName();
$quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
$sequenceName = $class->getTableName() . '_' . $columnName . '_seq';
$definition = array(
// Platforms that do not have native IDENTITY support need a sequence to emulate this behaviour.
if ($this->targetPlatform->usesSequenceEmulatedIdentityColumns()) {
$columnName = $class->getSingleIdentifierColumnName();
$quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
$sequenceName = $this->targetPlatform->getIdentitySequenceName($class->getTableName(), $columnName);
$definition = array(
'sequenceName' => $this->targetPlatform->fixSchemaElementName($sequenceName)
);
@ -486,7 +488,11 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$definition['quoted'] = true;
}
$sequenceName = $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->targetPlatform);
$sequenceName = $this
->em
->getConfiguration()
->getQuoteStrategy()
->getSequenceName($definition, $class, $this->targetPlatform);
}
$generator = ($fieldName && $class->fieldMappings[$fieldName]['type'] === 'bigint')

View File

@ -26,7 +26,6 @@ use Doctrine\DBAL\Types\Type;
use ReflectionClass;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\ClassLoader;
use Doctrine\Common\EventArgs;
/**
* A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
@ -189,6 +188,21 @@ class ClassMetadataInfo implements ClassMetadata
*/
const TO_MANY = 12;
/**
* ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks,
*/
const CACHE_USAGE_READ_ONLY = 1;
/**
* Nonstrict Read Write Cache doesnt employ any locks but can do inserts, update and deletes.
*/
const CACHE_USAGE_NONSTRICT_READ_WRITE = 2;
/**
* Read Write Attempts to lock the entity before update/delete.
*/
const CACHE_USAGE_READ_WRITE = 3;
/**
* READ-ONLY: The name of the entity class.
*
@ -591,6 +605,11 @@ class ClassMetadataInfo implements ClassMetadata
*/
public $versionField;
/**
* @var array
*/
public $cache = null;
/**
* The ReflectionClass instance of the mapped class.
*
@ -870,6 +889,10 @@ class ClassMetadataInfo implements ClassMetadata
$serialized[] = "customGeneratorDefinition";
}
if ($this->cache) {
$serialized[] = 'cache';
}
return $serialized;
}
@ -1010,6 +1033,45 @@ class ClassMetadataInfo implements ClassMetadata
return $this->reflClass;
}
/**
* @param array $cache
*
* @return void
*/
public function enableCache(array $cache)
{
if ( ! isset($cache['usage'])) {
$cache['usage'] = self::CACHE_USAGE_READ_ONLY;
}
if ( ! isset($cache['region'])) {
$cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName));
}
$this->cache = $cache;
}
/**
* @param string $fieldName
* @param array $cache
*
* @return void
*/
public function enableAssociationCache($fieldName, array $cache)
{
if ( ! isset($cache['usage'])) {
$cache['usage'] = isset($this->cache['usage'])
? $this->cache['usage']
: self::CACHE_USAGE_READ_ONLY;
}
if ( ! isset($cache['region'])) {
$cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
}
$this->associationMappings[$fieldName]['cache'] = $cache;
}
/**
* Sets the change tracking policy used by this class.
*
@ -1062,9 +1124,14 @@ class ClassMetadataInfo implements ClassMetadata
*/
public function isIdentifier($fieldName)
{
if ( ! $this->identifier) {
return false;
}
if ( ! $this->isIdentifierComposite) {
return $fieldName === $this->identifier[0];
}
return in_array($fieldName, $this->identifier);
}
@ -2543,7 +2610,11 @@ class ClassMetadataInfo implements ClassMetadata
*/
public function addEntityListener($eventName, $class, $method)
{
$class = $this->fullyQualifiedClassName($class);
$class = $this->fullyQualifiedClassName($class);
$listener = array(
'class' => $class,
'method' => $method
);
if ( ! class_exists($class)) {
throw MappingException::entityListenerClassNotFound($class, $this->name);
@ -2553,10 +2624,11 @@ class ClassMetadataInfo implements ClassMetadata
throw MappingException::entityListenerMethodNotFound($class, $method, $this->name);
}
$this->entityListeners[$eventName][] = array(
'class' => $class,
'method' => $method
);
if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName])) {
throw MappingException::duplicateEntityListener($class, $method, $this->name);
}
$this->entityListeners[$eventName][] = $listener;
}
/**

View File

@ -130,6 +130,17 @@ class AnnotationDriver extends AbstractAnnotationDriver
$metadata->setPrimaryTable($primaryTable);
}
// Evaluate @Cache annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\Cache'])) {
$cacheAnnot = $classAnnotations['Doctrine\ORM\Mapping\Cache'];
$cacheMap = array(
'region' => $cacheAnnot->region,
'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
);
$metadata->enableCache($cacheMap);
}
// Evaluate NamedNativeQueries annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries'])) {
$namedNativeQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries'];
@ -373,6 +384,14 @@ class AnnotationDriver extends AbstractAnnotationDriver
$mapping['columnPrefix'] = $embeddedAnnot->columnPrefix;
$metadata->mapEmbedded($mapping);
}
// Evaluate @Cache annotation
if (($cacheAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Cache')) !== null) {
$metadata->enableAssociationCache($mapping['fieldName'], array(
'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
'region' => $cacheAnnot->region,
));
}
}
// Evaluate AssociationOverrides annotation

View File

@ -35,7 +35,6 @@ require_once __DIR__.'/../OneToOne.php';
require_once __DIR__.'/../OneToMany.php';
require_once __DIR__.'/../ManyToOne.php';
require_once __DIR__.'/../ManyToMany.php';
require_once __DIR__.'/../ElementCollection.php';
require_once __DIR__.'/../Table.php';
require_once __DIR__.'/../UniqueConstraint.php';
require_once __DIR__.'/../Index.php';
@ -67,3 +66,7 @@ require_once __DIR__.'/../AssociationOverrides.php';
require_once __DIR__.'/../AttributeOverride.php';
require_once __DIR__.'/../AttributeOverrides.php';
require_once __DIR__.'/../EntityListeners.php';
<<<<<<< HEAD
=======
require_once __DIR__.'/../Cache.php';
>>>>>>> 69cad4079dcfbee273e7451ee6361a82c2432afb

View File

@ -83,6 +83,11 @@ class XmlDriver extends FileDriver
$metadata->setPrimaryTable($table);
// Evaluate second level cache
if (isset($xmlRoot->cache)) {
$metadata->enableCache($this->cacheToArray($xmlRoot->cache));
}
// Evaluate named queries
if (isset($xmlRoot->{'named-queries'})) {
foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) {
@ -291,6 +296,10 @@ class XmlDriver extends FileDriver
$mapping['columnDefinition'] = (string)$idElement['column-definition'];
}
if (isset($idElement->options)) {
$mapping['options'] = $this->_parseOptions($idElement->options->children());
}
$metadata->mapField($mapping);
if (isset($idElement->generator)) {
@ -362,6 +371,11 @@ class XmlDriver extends FileDriver
}
$metadata->mapOneToOne($mapping);
// Evaluate second level cache
if (isset($oneToOneElement->cache)) {
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToOneElement->cache));
}
}
}
@ -401,6 +415,11 @@ class XmlDriver extends FileDriver
}
$metadata->mapOneToMany($mapping);
// Evaluate second level cache
if (isset($oneToManyElement->cache)) {
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToManyElement->cache));
}
}
}
@ -441,6 +460,11 @@ class XmlDriver extends FileDriver
}
$metadata->mapManyToOne($mapping);
// Evaluate second level cache
if (isset($manyToOneElement->cache)) {
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToOneElement->cache));
}
}
}
@ -506,6 +530,11 @@ class XmlDriver extends FileDriver
}
$metadata->mapManyToMany($mapping);
// Evaluate second level cache
if (isset($manyToManyElement->cache)) {
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToManyElement->cache));
}
}
}
@ -714,6 +743,32 @@ class XmlDriver extends FileDriver
return $mapping;
}
/**
* Parse / Normalize the cache configuration
*
* @param SimpleXMLElement $cacheMapping
*
* @return array
*/
private function cacheToArray(SimpleXMLElement $cacheMapping)
{
$region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null;
$usage = isset($cacheMapping['usage']) ? strtoupper($cacheMapping['usage']) : null;
if ($usage && ! defined('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage)) {
throw new \InvalidArgumentException(sprintf('Invalid cache usage "%s"', $usage));
}
if ($usage) {
$usage = constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage);
}
return array(
'usage' => $usage,
'region' => $region,
);
}
/**
* Gathers a list of cascade options found in the given cascade element.
*

View File

@ -74,9 +74,16 @@ class YamlDriver extends FileDriver
// Evaluate root level properties
$table = array();
if (isset($element['table'])) {
$table['name'] = $element['table'];
}
// Evaluate second level cache
if (isset($element['cache'])) {
$metadata->enableCache($this->cacheToArray($element['cache']));
}
$metadata->setPrimaryTable($table);
// Evaluate named queries
@ -373,6 +380,11 @@ class YamlDriver extends FileDriver
}
$metadata->mapOneToOne($mapping);
// Evaluate second level cache
if (isset($oneToOneElement['cache'])) {
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToOneElement['cache']));
}
}
}
@ -406,6 +418,11 @@ class YamlDriver extends FileDriver
}
$metadata->mapOneToMany($mapping);
// Evaluate second level cache
if (isset($oneToManyElement['cache'])) {
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToManyElement['cache']));
}
}
}
@ -450,6 +467,11 @@ class YamlDriver extends FileDriver
}
$metadata->mapManyToOne($mapping);
// Evaluate second level cache
if (isset($manyToOneElement['cache'])) {
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToOneElement['cache']));
}
}
}
@ -478,17 +500,21 @@ class YamlDriver extends FileDriver
$joinTable['schema'] = $joinTableElement['schema'];
}
foreach ($joinTableElement['joinColumns'] as $joinColumnName => $joinColumnElement) {
if ( ! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $joinColumnName;
if (isset($joinTableElement['joinColumns'])) {
foreach ($joinTableElement['joinColumns'] as $joinColumnName => $joinColumnElement) {
if ( ! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $joinColumnName;
}
}
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement);
}
foreach ($joinTableElement['inverseJoinColumns'] as $joinColumnName => $joinColumnElement) {
if ( ! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $joinColumnName;
if (isset($joinTableElement['inverseJoinColumns'])) {
foreach ($joinTableElement['inverseJoinColumns'] as $joinColumnName => $joinColumnElement) {
if ( ! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $joinColumnName;
}
}
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement);
@ -518,6 +544,11 @@ class YamlDriver extends FileDriver
}
$metadata->mapManyToMany($mapping);
// Evaluate second level cache
if (isset($manyToManyElement['cache'])) {
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToManyElement['cache']));
}
}
}
@ -603,7 +634,7 @@ class YamlDriver extends FileDriver
}
foreach ($entityListener as $eventName => $callbackElement){
foreach ($callbackElement as $methodName){
foreach ($callbackElement as $methodName) {
$metadata->addEntityListener($eventName, $className, $methodName);
}
}
@ -716,6 +747,32 @@ class YamlDriver extends FileDriver
return $mapping;
}
/**
* Parse / Normalize the cache configuration
*
* @param array $cacheMapping
*
* @return array
*/
private function cacheToArray($cacheMapping)
{
$region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null;
$usage = isset($cacheMapping['usage']) ? strtoupper($cacheMapping['usage']) : null;
if ($usage && ! defined('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage)) {
throw new \InvalidArgumentException(sprintf('Invalid cache usage "%s"', $usage));
}
if ($usage) {
$usage = constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage);
}
return array(
'usage' => $usage,
'region' => $region,
);
}
/**
* {@inheritDoc}
*/

View File

@ -707,6 +707,18 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self(sprintf('Entity Listener "%s" declared on "%s" has no method "%s".', $listenerName, $className, $methodName));
}
/**
* @param string $listenerName
* @param string $methodName
* @param string $className
*
* @return \Doctrine\ORM\Mapping\MappingException
*/
public static function duplicateEntityListener($listenerName, $methodName, $className)
{
return new self(sprintf('Entity Listener "%s#%s()" in "%s" was already declared, but it must be declared only once.', $listenerName, $methodName, $className));
}
/**
* @param string $className
* @param string $annotation

View File

@ -100,6 +100,20 @@ class ORMException extends Exception
return new self("Unrecognized field: $field");
}
/**
*
* @param string $class
* @param string $association
* @param string $given
* @param string $expected
*
* @return \Doctrine\ORM\ORMInvalidArgumentException
*/
public static function unexpectedAssociationValue($class, $association, $given, $expected)
{
return new self(sprintf('Found entity of type %s on association %s#%s, but expecting %s', $given, $class, $association, $expected));
}
/**
* @param string $className
* @param string $field
@ -248,6 +262,16 @@ class ORMException extends Exception
return new self("Invalid repository class '".$className."'. It must be a Doctrine\Common\Persistence\ObjectRepository.");
}
/**
* @param string $className
*
* @return ORMException
*/
public static function invalidSecondLevelCache($className)
{
return new self(sprintf('Invalid cache class "%s". It must be a Doctrine\ORM\Cache.', $className));
}
/**
* @param string $className
* @param string $fieldName

View File

@ -28,7 +28,7 @@ use Doctrine\ORM\PersistentCollection;
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
*/
abstract class AbstractCollectionPersister
abstract class AbstractCollectionPersister implements CollectionPersister
{
/**
* @var EntityManager
@ -74,11 +74,7 @@ abstract class AbstractCollectionPersister
}
/**
* Deletes the persistent state represented by the given collection.
*
* @param \Doctrine\ORM\PersistentCollection $coll
*
* @return void
* {@inheritdoc}
*/
public function delete(PersistentCollection $coll)
{
@ -88,9 +84,7 @@ abstract class AbstractCollectionPersister
return; // ignore inverse side
}
$sql = $this->getDeleteSQL($coll);
$this->conn->executeUpdate($sql, $this->getDeleteSQLParameters($coll));
$this->conn->executeUpdate($this->getDeleteSQL($coll), $this->getDeleteSQLParameters($coll));
}
/**
@ -113,12 +107,7 @@ abstract class AbstractCollectionPersister
abstract protected function getDeleteSQLParameters(PersistentCollection $coll);
/**
* Updates the given collection, synchronizing its state with the database
* by inserting, updating and deleting individual elements.
*
* @param \Doctrine\ORM\PersistentCollection $coll
*
* @return void
* {@inheritdoc}
*/
public function update(PersistentCollection $coll)
{
@ -133,11 +122,7 @@ abstract class AbstractCollectionPersister
}
/**
* Deletes rows.
*
* @param \Doctrine\ORM\PersistentCollection $coll
*
* @return void
* {@inheritdoc}
*/
public function deleteRows(PersistentCollection $coll)
{
@ -150,11 +135,7 @@ abstract class AbstractCollectionPersister
}
/**
* Inserts rows.
*
* @param \Doctrine\ORM\PersistentCollection $coll
*
* @return void
* {@inheritdoc}
*/
public function insertRows(PersistentCollection $coll)
{
@ -167,13 +148,7 @@ abstract class AbstractCollectionPersister
}
/**
* Counts the size of this persistent collection.
*
* @param \Doctrine\ORM\PersistentCollection $coll
*
* @return integer
*
* @throws \BadMethodCallException
* {@inheritdoc}
*/
public function count(PersistentCollection $coll)
{
@ -181,15 +156,7 @@ abstract class AbstractCollectionPersister
}
/**
* Slices elements.
*
* @param \Doctrine\ORM\PersistentCollection $coll
* @param integer $offset
* @param integer $length
*
* @return array
*
* @throws \BadMethodCallException
* {@inheritdoc}
*/
public function slice(PersistentCollection $coll, $offset, $length = null)
{
@ -197,14 +164,7 @@ abstract class AbstractCollectionPersister
}
/**
* Checks for existence of an element.
*
* @param \Doctrine\ORM\PersistentCollection $coll
* @param object $element
*
* @return boolean
*
* @throws \BadMethodCallException
* {@inheritdoc}
*/
public function contains(PersistentCollection $coll, $element)
{
@ -212,14 +172,7 @@ abstract class AbstractCollectionPersister
}
/**
* Checks for existence of a key.
*
* @param \Doctrine\ORM\PersistentCollection $coll
* @param mixed $key
*
* @return boolean
*
* @throws \BadMethodCallException
* {@inheritdoc}
*/
public function containsKey(PersistentCollection $coll, $key)
{
@ -227,14 +180,7 @@ abstract class AbstractCollectionPersister
}
/**
* Removes an element.
*
* @param \Doctrine\ORM\PersistentCollection $coll
* @param object $element
*
* @return mixed
*
* @throws \BadMethodCallException
* {@inheritdoc}
*/
public function removeElement(PersistentCollection $coll, $element)
{
@ -242,14 +188,7 @@ abstract class AbstractCollectionPersister
}
/**
* Removes an element by key.
*
* @param \Doctrine\ORM\PersistentCollection $coll
* @param mixed $key
*
* @return void
*
* @throws \BadMethodCallException
* {@inheritdoc}
*/
public function removeKey(PersistentCollection $coll, $key)
{
@ -257,14 +196,7 @@ abstract class AbstractCollectionPersister
}
/**
* Gets an element by key.
*
* @param \Doctrine\ORM\PersistentCollection $coll
* @param mixed $index
*
* @return mixed
*
* @throws \BadMethodCallException
* {@inheritdoc}
*/
public function get(PersistentCollection $coll, $index)
{

View File

@ -78,7 +78,7 @@ use Doctrine\Common\Collections\Expr\Comparison;
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.0
*/
class BasicEntityPersister
class BasicEntityPersister implements EntityPersister
{
/**
* @var array
@ -223,7 +223,7 @@ class BasicEntityPersister
}
/**
* @return \Doctrine\ORM\Mapping\ClassMetadata
* {@inheritdoc}
*/
public function getClassMetadata()
{
@ -231,12 +231,15 @@ class BasicEntityPersister
}
/**
* Adds an entity to the queued insertions.
* The entity remains queued until {@link executeInserts} is invoked.
*
* @param object $entity The entity to queue for insertion.
*
* @return void
* {@inheritdoc}
*/
public function getResultSetMapping()
{
return $this->rsm;
}
/**
* {@inheritdoc}
*/
public function addInsert($entity)
{
@ -244,13 +247,15 @@ class BasicEntityPersister
}
/**
* Executes all queued entity insertions and returns any generated post-insert
* identifiers that were created as a result of the insertions.
*
* If no inserts are queued, invoking this method is a NOOP.
*
* @return array An array of any generated post-insert IDs. This will be an empty array
* if the entity class does not use the IDENTITY generation strategy.
* {@inheritdoc}
*/
public function getInserts()
{
return $this->queuedInserts;
}
/**
* {@inheritdoc}
*/
public function executeInserts()
{
@ -328,7 +333,7 @@ class BasicEntityPersister
$identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform);
$columnName = $this->quoteStrategy->getColumnName($versionField, $versionedClass, $this->platform);
//FIXME: Order with composite keys might not be correct
// FIXME: Order with composite keys might not be correct
$sql = 'SELECT ' . $columnName
. ' FROM ' . $tableName
. ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?';
@ -339,20 +344,7 @@ class BasicEntityPersister
}
/**
* Updates a managed entity. The entity is updated according to its current changeset
* in the running UnitOfWork. If there is no changeset, nothing is updated.
*
* The data to update is retrieved through {@link prepareUpdateData}.
* Subclasses that override this method are supposed to obtain the update data
* in the same way, through {@link prepareUpdateData}.
*
* Subclasses are also supposed to take care of versioning when overriding this method,
* if necessary. The {@link updateTable} method can be used to apply the data retrieved
* from {@prepareUpdateData} on the target tables, thereby optionally applying versioning.
*
* @param object $entity The entity to update.
*
* @return void
* {@inheritdoc}
*/
public function update($entity)
{
@ -549,16 +541,7 @@ class BasicEntityPersister
}
/**
* Deletes a managed entity.
*
* The entity to delete must be managed and have a persistent identifier.
* The deletion happens instantaneously.
*
* Subclasses may override this method to customize the semantics of entity deletion.
*
* @param object $entity The entity to delete.
*
* @return void
* {@inheritdoc}
*/
public function delete($entity)
{
@ -713,15 +696,7 @@ class BasicEntityPersister
}
/**
* Gets the name of the table that owns the column the given field is mapped to.
*
* The default implementation in BasicEntityPersister always returns the name
* of the table the entity type of this persister is mapped to, since an entity
* is always persisted to a single table with a BasicEntityPersister.
*
* @param string $fieldName The field name.
*
* @return string The table name.
* {@inheritdoc}
*/
public function getOwningTable($fieldName)
{
@ -729,19 +704,7 @@ class BasicEntityPersister
}
/**
* Loads an entity by a list of field criteria.
*
* @param array $criteria The criteria by which to load the entity.
* @param object|null $entity The entity to load the data into. If not specified, a new entity is created.
* @param array|null $assoc The association that connects the entity to load to another entity, if any.
* @param array $hints Hints for entity creation.
* @param int $lockMode
* @param int|null $limit Limit number of results.
* @param array|null $orderBy Criteria to order by.
*
* @return object|null The loaded and managed entity instance or NULL if the entity can not be found.
*
* @todo Check identity map? loadById method? Try to guess whether $criteria is the id?
* {@inheritdoc}
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null, array $orderBy = null)
{
@ -761,18 +724,15 @@ class BasicEntityPersister
}
/**
* Loads an entity of this persister's mapped class as part of a single-valued
* association from another entity.
*
* @param array $assoc The association to load.
* @param object $sourceEntity The entity that owns the association (not necessarily the "owning side").
* @param array $identifier The identifier of the entity to load. Must be provided if
* the association to load represents the owning side, otherwise
* the identifier is derived from the $sourceEntity.
*
* @return object The loaded and managed entity instance or NULL if the entity can not be found.
*
* @throws \Doctrine\ORM\Mapping\MappingException
* {@inheritdoc}
*/
public function loadById(array $identifier, $entity = null)
{
return $this->load($identifier, $entity);
}
/**
* {@inheritdoc}
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
{
@ -838,14 +798,7 @@ class BasicEntityPersister
}
/**
* Refreshes a managed entity.
*
* @param array $id The identifier of the entity as an associative array from
* column or field names to values.
* @param object $entity The entity to refresh.
* @param int $lockMode
*
* @return void
* {@inheritdoc}
*/
public function refresh(array $id, $entity, $lockMode = 0)
{
@ -858,11 +811,7 @@ class BasicEntityPersister
}
/**
* Loads Entities matching the given Criteria object.
*
* @param \Doctrine\Common\Collections\Criteria $criteria
*
* @return array
* {@inheritdoc}
*/
public function loadCriteria(Criteria $criteria)
{
@ -916,14 +865,7 @@ class BasicEntityPersister
}
/**
* Loads a list of entities by a list of field criteria.
*
* @param array $criteria
* @param array|null $orderBy
* @param int|null $limit
* @param int|null $offset
*
* @return array
* {@inheritdoc}
*/
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null)
{
@ -937,14 +879,7 @@ class BasicEntityPersister
}
/**
* Gets (sliced or full) elements of the given collection.
*
* @param array $assoc
* @param object $sourceEntity
* @param int|null $offset
* @param int|null $limit
*
* @return array
* {@inheritdoc}
*/
public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{
@ -1000,13 +935,7 @@ class BasicEntityPersister
}
/**
* Loads a collection of entities of a many-to-many association.
*
* @param array $assoc The association mapping of the association being loaded.
* @param object $sourceEntity The entity that owns the collection.
* @param PersistentCollection $coll The collection to fill.
*
* @return array
* {@inheritdoc}
*/
public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
{
@ -1083,18 +1012,9 @@ class BasicEntityPersister
}
/**
* Gets the SELECT SQL to select one or more entities by a set of field criteria.
*
* @param array|\Doctrine\Common\Collections\Criteria $criteria
* @param array|null $assoc
* @param int $lockMode
* @param int|null $limit
* @param int|null $offset
* @param array|null $orderBy
*
* @return string
* {@inheritdoc}
*/
protected function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
public function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
{
$lockSql = '';
$joinSql = '';
@ -1391,11 +1311,9 @@ class BasicEntityPersister
}
/**
* Gets the INSERT SQL used by the persister to persist a new entity.
*
* @return string
* {@inheritdoc}
*/
protected function getInsertSQL()
public function getInsertSQL()
{
if ($this->insertSql !== null) {
return $this->insertSql;
@ -1529,12 +1447,7 @@ class BasicEntityPersister
}
/**
* Locks all rows of this entity matching the given criteria with the specified pessimistic lock mode.
*
* @param array $criteria
* @param int $lockMode
*
* @return void
* {@inheritdoc}
*/
public function lock(array $criteria, $lockMode)
{
@ -1597,14 +1510,7 @@ class BasicEntityPersister
}
/**
* Gets the SQL WHERE condition for matching a field with a given value.
*
* @param string $field
* @param mixed $value
* @param array|null $assoc
* @param string|null $comparison
*
* @return string
* {@inheritdoc}
*/
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null)
{
@ -1707,14 +1613,7 @@ class BasicEntityPersister
}
/**
* Returns an array with (sliced or full list) of elements in the specified collection.
*
* @param array $assoc
* @param object $sourceEntity
* @param int|null $offset
* @param int|null $limit
*
* @return array
* {@inheritdoc}
*/
public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{
@ -1724,13 +1623,7 @@ class BasicEntityPersister
}
/**
* Loads a collection of entities in a one-to-many association.
*
* @param array $assoc
* @param object $sourceEntity
* @param PersistentCollection $coll The collection to load/fill.
*
* @return array
* {@inheritdoc}
*/
public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
{
@ -1782,13 +1675,9 @@ class BasicEntityPersister
}
/**
* Expands the parameters from the given criteria and use the correct binding types if found.
*
* @param array $criteria
*
* @return array
* {@inheritdoc}
*/
private function expandParameters($criteria)
public function expandParameters($criteria)
{
$params = array();
$types = array();
@ -1890,12 +1779,7 @@ class BasicEntityPersister
}
/**
* Checks whether the given managed entity exists in the database.
*
* @param object $entity
* @param array $extraConditions
*
* @return boolean TRUE if the entity exists in the database, FALSE otherwise.
* {@inheritdoc}
*/
public function exists($entity, array $extraConditions = array())
{
@ -1944,11 +1828,7 @@ class BasicEntityPersister
}
/**
* Gets an SQL column alias for a column name.
*
* @param string $columnName
*
* @return string
* {@inheritdoc}
*/
public function getSQLColumnAlias($columnName)
{
@ -1976,4 +1856,4 @@ class BasicEntityPersister
$sql = implode(' AND ', $filterClauses);
return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL"
}
}
}

View File

@ -0,0 +1,139 @@
<?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\Persisters;
use Doctrine\ORM\PersistentCollection;
/**
* Collection persister interface
* Define the behavior that should be implemented by all collection persisters.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
interface CollectionPersister
{
/**
* Deletes the persistent state represented by the given collection.
*
* @param \Doctrine\ORM\PersistentCollection $collection
*
* @return void
*/
public function delete(PersistentCollection $collection);
/**
* Updates the given collection, synchronizing its state with the database
* by inserting, updating and deleting individual elements.
*
* @param \Doctrine\ORM\PersistentCollection $collection
*
* @return void
*/
public function update(PersistentCollection $collection);
/**
* Deletes rows.
*
* @param \Doctrine\ORM\PersistentCollection $collection
*
* @return void
*/
public function deleteRows(PersistentCollection $collection);
/**
* Inserts rows.
*
* @param \Doctrine\ORM\PersistentCollection $collection
*
* @return void
*/
public function insertRows(PersistentCollection $collection);
/**
* Counts the size of this persistent collection.
*
* @param \Doctrine\ORM\PersistentCollection $collection
*
* @return integer
*/
public function count(PersistentCollection $collection);
/**
* Slices elements.
*
* @param \Doctrine\ORM\PersistentCollection $collection
* @param integer $offset
* @param integer $length
*
* @return array
*/
public function slice(PersistentCollection $collection, $offset, $length = null);
/**
* Checks for existence of an element.
*
* @param \Doctrine\ORM\PersistentCollection $collection
* @param object $element
*
* @return boolean
*/
public function contains(PersistentCollection $collection, $element);
/**
* Checks for existence of a key.
*
* @param \Doctrine\ORM\PersistentCollection $collection
* @param mixed $key
*
* @return boolean
*/
public function containsKey(PersistentCollection $collection, $key);
/**
* Removes an element.
*
* @param \Doctrine\ORM\PersistentCollection $collection
* @param object $element
*
* @return mixed
*/
public function removeElement(PersistentCollection $collection, $element);
/**
* Removes an element by key.
*
* @param \Doctrine\ORM\PersistentCollection $collection
* @param mixed $key
*
* @return void
*/
public function removeKey(PersistentCollection $collection, $key);
/**
* Gets an element by key.
*
* @param \Doctrine\ORM\PersistentCollection $collection
* @param mixed $index
*
* @return mixed
*/
public function get(PersistentCollection $collection, $index);
}

View File

@ -0,0 +1,299 @@
<?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\Persisters;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Common\Collections\Criteria;
/**
* Entity persister interface
* Define the behavior that should be implemented by all entity persisters.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.5
*/
interface EntityPersister
{
/**
* @return \Doctrine\ORM\Mapping\ClassMetadata
*/
public function getClassMetadata();
/**
* Gets the ResultSetMapping used for hydration.
*
* @return \Doctrine\ORM\Query\ResultSetMapping
*/
public function getResultSetMapping();
/**
* Get all queued inserts.
*
* @return array
*/
public function getInserts();
/**
* @TODO - It should not be here.
* But its necessary since JoinedSubclassPersister#executeInserts invoke the root persister.
*
* Gets the INSERT SQL used by the persister to persist a new entity.
*
* @return string
*/
public function getInsertSQL();
/**
* Gets the SELECT SQL to select one or more entities by a set of field criteria.
*
* @param array|\Doctrine\Common\Collections\Criteria $criteria
* @param array|null $assoc
* @param int $lockMode
* @param int|null $limit
* @param int|null $offset
* @param array|null $orderBy
*
* @return string
*/
public function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null);
/**
* Expands the parameters from the given criteria and use the correct binding types if found.
*
* @param $criteria
*
* @return array
*/
public function expandParameters($criteria);
/**
* Gets the SQL WHERE condition for matching a field with a given value.
*
* @param string $field
* @param mixed $value
* @param array|null $assoc
* @param string|null $comparison
*
* @return string
*/
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null);
/**
* Adds an entity to the queued insertions.
* The entity remains queued until {@link executeInserts} is invoked.
*
* @param object $entity The entity to queue for insertion.
*
* @return void
*/
public function addInsert($entity);
/**
* Executes all queued entity insertions and returns any generated post-insert
* identifiers that were created as a result of the insertions.
*
* If no inserts are queued, invoking this method is a NOOP.
*
* @return array An array of any generated post-insert IDs. This will be an empty array
* if the entity class does not use the IDENTITY generation strategy.
*/
public function executeInserts();
/**
* Updates a managed entity. The entity is updated according to its current changeset
* in the running UnitOfWork. If there is no changeset, nothing is updated.
*
* @param object $entity The entity to update.
*
* @return void
*/
public function update($entity);
/**
* Deletes a managed entity.
*
* The entity to delete must be managed and have a persistent identifier.
* The deletion happens instantaneously.
*
* Subclasses may override this method to customize the semantics of entity deletion.
*
* @param object $entity The entity to delete.
*
* @return void
*/
public function delete($entity);
/**
* Gets the name of the table that owns the column the given field is mapped to.
*
* The default implementation in BasicEntityPersister always returns the name
* of the table the entity type of this persister is mapped to, since an entity
* is always persisted to a single table with a BasicEntityPersister.
*
* @param string $fieldName The field name.
*
* @return string The table name.
*/
public function getOwningTable($fieldName);
/**
* Loads an entity by a list of field criteria.
*
* @param array $criteria The criteria by which to load the entity.
* @param object|null $entity The entity to load the data into. If not specified, a new entity is created.
* @param array|null $assoc The association that connects the entity to load to another entity, if any.
* @param array $hints Hints for entity creation.
* @param int $lockMode
* @param int|null $limit Limit number of results.
* @param array|null $orderBy Criteria to order by.
*
* @return object|null The loaded and managed entity instance or NULL if the entity can not be found.
*
* @todo Check identity map? loadById method? Try to guess whether $criteria is the id?
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null, array $orderBy = null);
/**
* Loads an entity by identifier.
*
* @param array $identifier The entity identifier.
* @param object|null $entity The entity to load the data into. If not specified, a new entity is created.
*
* @return object The loaded and managed entity instance or NULL if the entity can not be found.
*
* @todo Check parameters
*/
public function loadById(array $identifier, $entity = null);
/**
* Loads an entity of this persister's mapped class as part of a single-valued
* association from another entity.
*
* @param array $assoc The association to load.
* @param object $sourceEntity The entity that owns the association (not necessarily the "owning side").
* @param array $identifier The identifier of the entity to load. Must be provided if
* the association to load represents the owning side, otherwise
* the identifier is derived from the $sourceEntity.
*
* @return object The loaded and managed entity instance or NULL if the entity can not be found.
*
* @throws \Doctrine\ORM\Mapping\MappingException
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array());
/**
* Refreshes a managed entity.
*
* @param array $id The identifier of the entity as an associative array from
* column or field names to values.
* @param object $entity The entity to refresh.
* @param int $lockMode
*
* @return void
*/
public function refresh(array $id, $entity, $lockMode = 0);
/**
* Loads Entities matching the given Criteria object.
*
* @param \Doctrine\Common\Collections\Criteria $criteria
*
* @return array
*/
public function loadCriteria(Criteria $criteria);
/**
* Loads a list of entities by a list of field criteria.
*
* @param array $criteria
* @param array|null $orderBy
* @param int|null $limit
* @param int|null $offset
*
* @return array
*/
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null);
/**
* Gets (sliced or full) elements of the given collection.
*
* @param array $assoc
* @param object $sourceEntity
* @param int|null $offset
* @param int|null $limit
*
* @return array
*/
public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null);
/**
* Loads a collection of entities of a many-to-many association.
*
* @param array $assoc The association mapping of the association being loaded.
* @param object $sourceEntity The entity that owns the collection.
* @param PersistentCollection $collection The collection to fill.
*
* @return array
*/
public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection);
/**
* Loads a collection of entities in a one-to-many association.
*
* @param array $assoc
* @param object $sourceEntity
* @param PersistentCollection $collection The collection to load/fill.
*
* @return array
*/
public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection);
/**
* Locks all rows of this entity matching the given criteria with the specified pessimistic lock mode.
*
* @param array $criteria
* @param int $lockMode
*
* @return void
*/
public function lock(array $criteria, $lockMode);
/**
* Returns an array with (sliced or full list) of elements in the specified collection.
*
* @param array $assoc
* @param object $sourceEntity
* @param int|null $offset
* @param int|null $limit
*
* @return array
*/
public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null);
/**
* Checks whether the given managed entity exists in the database.
*
* @param object $entity
* @param array $extraConditions
*
* @return boolean TRUE if the entity exists in the database, FALSE otherwise.
*/
public function exists($entity, array $extraConditions = array());
}

View File

@ -181,6 +181,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$id = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
}
if ($this->class->isVersioned) {
$this->assignDefaultVersionValue($entity, $id);
}
// Execute inserts on subtables.
// The order doesn't matter because all child tables link to the root table via FK.
foreach ($subTableStmts as $tableName => $stmt) {
@ -212,10 +216,6 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$stmt->closeCursor();
}
if ($this->class->isVersioned) {
$this->assignDefaultVersionValue($entity, $id);
}
$this->queuedInserts = array();
return $postInsertIds;
@ -296,7 +296,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
/**
* {@inheritdoc}
*/
protected function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
public function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
{
$joinSql = '';
$identifierColumn = $this->class->getIdentifierColumnNames();

View File

@ -257,11 +257,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
}
/**
* @param \Doctrine\ORM\PersistentCollection $coll
* @param int $offset
* @param int|null $length
*
* @return array
* {@inheritdoc}
*/
public function slice(PersistentCollection $coll, $offset, $length = null)
{
@ -271,10 +267,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
}
/**
* @param \Doctrine\ORM\PersistentCollection $coll
* @param object $element
*
* @return boolean
* {@inheritdoc}
*/
public function contains(PersistentCollection $coll, $element)
{
@ -300,10 +293,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
}
/**
* @param \Doctrine\ORM\PersistentCollection $coll
* @param object $element
*
* @return boolean
* {@inheritdoc}
*/
public function removeElement(PersistentCollection $coll, $element)
{

View File

@ -34,8 +34,6 @@ class OneToManyPersister extends AbstractCollectionPersister
{
/**
* {@inheritdoc}
*
* @override
*/
public function get(PersistentCollection $coll, $index)
{
@ -166,11 +164,7 @@ class OneToManyPersister extends AbstractCollectionPersister
}
/**
* @param \Doctrine\ORM\PersistentCollection $coll
* @param int $offset
* @param int|null $length
*
* @return \Doctrine\Common\Collections\ArrayCollection
* {@inheritdoc}
*/
public function slice(PersistentCollection $coll, $offset, $length = null)
{
@ -181,11 +175,8 @@ class OneToManyPersister extends AbstractCollectionPersister
return $persister->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length);
}
/**
* @param \Doctrine\ORM\PersistentCollection $coll
* @param object $element
*
* @return boolean
/**
* {@inheritdoc}
*/
public function contains(PersistentCollection $coll, $element)
{
@ -215,10 +206,7 @@ class OneToManyPersister extends AbstractCollectionPersister
}
/**
* @param \Doctrine\ORM\PersistentCollection $coll
* @param object $element
*
* @return boolean
* {@inheritdoc}
*/
public function removeElement(PersistentCollection $coll, $element)
{

View File

@ -26,7 +26,7 @@ use Doctrine\Common\Util\ClassUtils;
use Doctrine\Common\Proxy\Proxy as BaseProxy;
use Doctrine\Common\Proxy\ProxyGenerator;
use Doctrine\ORM\ORMInvalidArgumentException;
use Doctrine\ORM\Persisters\BasicEntityPersister;
use Doctrine\ORM\Persisters\EntityPersister;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityNotFoundException;
@ -62,9 +62,9 @@ class ProxyFactory extends AbstractProxyFactory
* @param \Doctrine\ORM\EntityManager $em The EntityManager the new factory works for.
* @param string $proxyDir The directory to use for the proxy classes. It must exist.
* @param string $proxyNs The namespace to use for the proxy classes.
* @param boolean $autoGenerate Whether to automatically generate proxy classes.
* @param boolean|int $autoGenerate Whether to automatically generate proxy classes.
*/
public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false)
public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = AbstractProxyFactory::AUTOGENERATE_NEVER)
{
$proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs);
@ -107,13 +107,13 @@ class ProxyFactory extends AbstractProxyFactory
* Creates a closure capable of initializing a proxy
*
* @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata
* @param \Doctrine\ORM\Persisters\BasicEntityPersister $entityPersister
* @param \Doctrine\ORM\Persisters\EntityPersister $entityPersister
*
* @return \Closure
*
* @throws \Doctrine\ORM\EntityNotFoundException
*/
private function createInitializer(ClassMetadata $classMetadata, BasicEntityPersister $entityPersister)
private function createInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister)
{
if ($classMetadata->getReflectionClass()->hasMethod('__wakeup')) {
return function (BaseProxy $proxy) use ($entityPersister, $classMetadata) {
@ -130,7 +130,7 @@ class ProxyFactory extends AbstractProxyFactory
$properties = $proxy->__getLazyProperties();
foreach ($properties as $propertyName => $property) {
if (!isset($proxy->$propertyName)) {
if ( ! isset($proxy->$propertyName)) {
$proxy->$propertyName = $properties[$propertyName];
}
}
@ -138,7 +138,7 @@ class ProxyFactory extends AbstractProxyFactory
$proxy->__setInitialized(true);
$proxy->__wakeup();
if (null === $entityPersister->load($classMetadata->getIdentifierValues($proxy), $proxy)) {
if (null === $entityPersister->loadById($classMetadata->getIdentifierValues($proxy), $proxy)) {
$proxy->__setInitializer($initializer);
$proxy->__setCloner($cloner);
$proxy->__setInitialized(false);
@ -169,7 +169,7 @@ class ProxyFactory extends AbstractProxyFactory
$proxy->__setInitialized(true);
if (null === $entityPersister->load($classMetadata->getIdentifierValues($proxy), $proxy)) {
if (null === $entityPersister->loadById($classMetadata->getIdentifierValues($proxy), $proxy)) {
$proxy->__setInitializer($initializer);
$proxy->__setCloner($cloner);
$proxy->__setInitialized(false);
@ -183,13 +183,13 @@ class ProxyFactory extends AbstractProxyFactory
* Creates a closure capable of finalizing state a cloned proxy
*
* @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata
* @param \Doctrine\ORM\Persisters\BasicEntityPersister $entityPersister
* @param \Doctrine\ORM\Persisters\EntityPersister $entityPersister
*
* @return \Closure
*
* @throws \Doctrine\ORM\EntityNotFoundException
*/
private function createCloner(ClassMetadata $classMetadata, BasicEntityPersister $entityPersister)
private function createCloner(ClassMetadata $classMetadata, EntityPersister $entityPersister)
{
return function (BaseProxy $proxy) use ($entityPersister, $classMetadata) {
if ($proxy->__isInitialized()) {
@ -198,20 +198,21 @@ class ProxyFactory extends AbstractProxyFactory
$proxy->__setInitialized(true);
$proxy->__setInitializer(null);
$class = $entityPersister->getClassMetadata();
$original = $entityPersister->load($classMetadata->getIdentifierValues($proxy));
$class = $entityPersister->getClassMetadata();
$original = $entityPersister->loadById($classMetadata->getIdentifierValues($proxy));
if (null === $original) {
throw new EntityNotFoundException();
}
foreach ($class->getReflectionClass()->getProperties() as $reflectionProperty) {
$propertyName = $reflectionProperty->getName();
if ($class->hasField($propertyName) || $class->hasAssociation($propertyName)) {
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($proxy, $reflectionProperty->getValue($original));
foreach ($class->getReflectionClass()->getProperties() as $property) {
if ( ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
continue;
}
$property->setAccessible(true);
$property->setValue($proxy, $property->getValue($original));
}
};
}

View File

@ -19,15 +19,13 @@
namespace Doctrine\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\ParserResult;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ParameterTypeInferer;
use Doctrine\Common\Collections\ArrayCollection;
/**
* A Query object represents a DQL query.
@ -61,6 +59,16 @@ final class Query extends AbstractQuery
*/
const HINT_REFRESH = 'doctrine.refresh';
/**
* @var string
*/
const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
/**
* @var string
*/
const HINT_CACHE_EVICT = 'doctrine.cache.evict';
/**
* Internal hint: is set to the proxy entity that is currently triggered for loading
*
@ -178,16 +186,6 @@ final class Query extends AbstractQuery
*/
private $_useQueryCache = true;
/**
* Initializes a new Query instance.
*
* @param \Doctrine\ORM\EntityManager $entityManager
*/
/*public function __construct(EntityManager $entityManager)
{
parent::__construct($entityManager);
}*/
/**
* Gets the SQL query/queries that correspond to this DQL query.
*
@ -214,6 +212,19 @@ final class Query extends AbstractQuery
return $parser->getAST();
}
/**
* {@inheritdoc}
*/
protected function getResultSetMapping()
{
// parse query or load from cache
if ($this->_resultSetMapping === null) {
$this->_resultSetMapping = $this->_parse()->getResultSetMapping();
}
return $this->_resultSetMapping;
}
/**
* Parses the DQL query, if necessary, and stores the parser result.
*
@ -281,11 +292,34 @@ final class Query extends AbstractQuery
throw QueryException::invalidParameterNumber();
}
// evict all cache for the entity region
if ($this->hasCache && isset($this->_hints[self::HINT_CACHE_EVICT]) && $this->_hints[self::HINT_CACHE_EVICT]) {
$this->evictEntityCacheRegion();
}
list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
}
/**
* Evict entity cache region
*/
private function evictEntityCacheRegion()
{
$AST = $this->getAST();
if ($AST instanceof \Doctrine\ORM\Query\AST\SelectStatement) {
throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.');
}
$className = ($AST instanceof \Doctrine\ORM\Query\AST\DeleteStatement)
? $AST->deleteClause->abstractSchemaName
: $AST->updateClause->abstractSchemaName;
$this->_em->getCache()->evictEntityRegion($className);
}
/**
* Processes query parameter mappings.
*
@ -303,13 +337,14 @@ final class Query extends AbstractQuery
foreach ($this->parameters as $parameter) {
$key = $parameter->getName();
$value = $parameter->getValue();
$rsm = $this->getResultSetMapping();
if ( ! isset($paramMappings[$key])) {
throw QueryException::unknownParameter($key);
}
if (isset($this->_resultSetMapping->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) {
$value = $value->getMetadataValue($this->_resultSetMapping->metadataParameterMapping[$key]);
if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) {
$value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
}
$value = $this->processParameterValue($value);
@ -655,6 +690,14 @@ final class Query extends AbstractQuery
);
}
/**
* {@inheritdoc}
*/
protected function getHash()
{
return sha1(parent::getHash(). '-'. $this->_firstResult . '-' . $this->_maxResults);
}
/**
* Cleanup Query resource when clone is called.
*

View File

@ -82,7 +82,7 @@ class IdentityFunction extends FunctionNode
}
}
//The table with the relation may be a subclass, so get the table name from the association definition
// The table with the relation may be a subclass, so get the table name from the association definition
$tableName = $sqlWalker->getEntityManager()->getClassMetadata($assoc['sourceEntity'])->getTableName();
$tableAlias = $sqlWalker->getSQLTableAlias($tableName, $dqlAlias);

View File

@ -1884,7 +1884,7 @@ class Parser
case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
return $this->SimpleArithmeticExpression();
//this check must be done before checking for a filed path expression
// this check must be done before checking for a filed path expression
case ($this->isFunction()):
$this->lexer->peek(); // "("
@ -1902,7 +1902,7 @@ class Parser
}
break;
//it is no function, so it must be a field path
// it is no function, so it must be a field path
case ($lookahead === Lexer::T_IDENTIFIER):
$this->lexer->peek(); // lookahead => '.'
$this->lexer->peek(); // lookahead => token after '.'
@ -2437,7 +2437,7 @@ class Parser
switch ($peek['value']) {
case '(':
//Peeks beyond the matched closing parenthesis.
// Peeks beyond the matched closing parenthesis.
$token = $this->peekBeyondClosingParenthesis(false);
if ($token['type'] === Lexer::T_NOT) {

View File

@ -47,6 +47,11 @@ class QueryExpressionVisitor extends ExpressionVisitor
Comparison::LTE => Expr\Comparison::LTE
);
/**
* @var string
*/
private $rootAlias;
/**
* @var Expr
*/
@ -58,10 +63,13 @@ class QueryExpressionVisitor extends ExpressionVisitor
private $parameters = array();
/**
* Constructor with internal initialization.
* Constructor
*
* @param string $rootAlias
*/
public function __construct()
public function __construct($rootAlias)
{
$this->rootAlias = $rootAlias;
$this->expr = new Expr();
}
@ -127,44 +135,52 @@ class QueryExpressionVisitor extends ExpressionVisitor
public function walkComparison(Comparison $comparison)
{
$parameterName = str_replace('.', '_', $comparison->getField());
foreach($this->parameters as $parameter) {
if($parameter->getName() === $parameterName) {
$parameterName .= '_' . count($this->parameters);
break;
}
}
$parameter = new Parameter($parameterName, $this->walkValue($comparison->getValue()));
$placeholder = ':' . $parameterName;
switch ($comparison->getOperator()) {
case Comparison::IN:
$this->parameters[] = $parameter;
return $this->expr->in($comparison->getField(), $placeholder);
return $this->expr->in($this->rootAlias . '.' . $comparison->getField(), $placeholder);
case Comparison::NIN:
$this->parameters[] = $parameter;
return $this->expr->notIn($comparison->getField(), $placeholder);
return $this->expr->notIn($this->rootAlias . '.' . $comparison->getField(), $placeholder);
case Comparison::EQ:
case Comparison::IS:
if ($this->walkValue($comparison->getValue()) === null) {
return $this->expr->isNull($comparison->getField());
return $this->expr->isNull($this->rootAlias . '.' . $comparison->getField());
}
$this->parameters[] = $parameter;
return $this->expr->eq($comparison->getField(), $placeholder);
return $this->expr->eq($this->rootAlias . '.' . $comparison->getField(), $placeholder);
case Comparison::NEQ:
if ($this->walkValue($comparison->getValue()) === null) {
return $this->expr->isNotNull($comparison->getField());
return $this->expr->isNotNull($this->rootAlias . '.' . $comparison->getField());
}
$this->parameters[] = $parameter;
return $this->expr->neq($comparison->getField(), $placeholder);
return $this->expr->neq($this->rootAlias . '.' . $comparison->getField(), $placeholder);
case Comparison::CONTAINS:
$parameter->setValue('%' . $parameter->getValue() . '%', $parameter->getType());
$this->parameters[] = $parameter;
return $this->expr->like($comparison->getField(), $placeholder);
return $this->expr->like($this->rootAlias . '.' . $comparison->getField(), $placeholder);
default:
$operator = self::convertComparisonOperator($comparison->getOperator());
if ($operator) {
$this->parameters[] = $parameter;
return new Expr\Comparison(
$comparison->getField(),
$this->rootAlias . '.' . $comparison->getField(),
$operator,
$placeholder
);

View File

@ -43,6 +43,14 @@ class ResultSetMapping
*/
public $isMixed = false;
/**
* Whether the result is a select statement.
*
* @ignore
* @var boolean
*/
public $isSelect = true;
/**
* Maps alias names to class names.
*

View File

@ -401,12 +401,13 @@ class SqlWalker implements TreeWalker
foreach ($this->selectedClasses as $selectedClass) {
$dqlAlias = $selectedClass['dqlAlias'];
$qComp = $this->queryComponents[$dqlAlias];
$persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
if ( ! isset($qComp['relation']['orderBy'])) {
continue;
}
$persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) {
$columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform);
$tableName = ($qComp['metadata']->isInheritanceTypeJoined())
@ -572,6 +573,7 @@ class SqlWalker implements TreeWalker
public function walkUpdateStatement(AST\UpdateStatement $AST)
{
$this->useSqlTableAliases = false;
$this->rsm->isSelect = false;
return $this->walkUpdateClause($AST->updateClause)
. $this->walkWhereClause($AST->whereClause);
@ -583,6 +585,7 @@ class SqlWalker implements TreeWalker
public function walkDeleteStatement(AST\DeleteStatement $AST)
{
$this->useSqlTableAliases = false;
$this->rsm->isSelect = false;
return $this->walkDeleteClause($AST->deleteClause)
. $this->walkWhereClause($AST->whereClause);

View File

@ -187,7 +187,7 @@ class QueryBuilder
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->from('User', 'u');
* echo $qb->getDql(); // SELECT u FROM User u
* </code>
*
@ -283,12 +283,18 @@ class QueryBuilder
* </code>
*
* @deprecated Please use $qb->getRootAliases() instead.
* @throws RuntimeException
*
* @return string
*/
public function getRootAlias()
{
$aliases = $this->getRootAliases();
if ( ! isset($aliases[0])) {
throw new \RuntimeException('No alias was set before invoking getRootAlias().');
}
return $aliases[0];
}
@ -655,7 +661,7 @@ class QueryBuilder
* <code>
* $qb = $em->createQueryBuilder()
* ->delete('User', 'u')
* ->where('u.id = :user_id');
* ->where('u.id = :user_id')
* ->setParameter('user_id', 1);
* </code>
*
@ -709,7 +715,7 @@ class QueryBuilder
* <code>
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->from('User', 'u');
* </code>
*
* @param string $from The class name.
@ -967,8 +973,8 @@ class QueryBuilder
* $qb = $em->createQueryBuilder()
* ->select('u')
* ->from('User', 'u')
* ->groupBy('u.lastLogin');
* ->addGroupBy('u.createdAt')
* ->groupBy('u.lastLogin')
* ->addGroupBy('u.createdAt');
* </code>
*
* @param string $groupBy The grouping expression.
@ -1087,7 +1093,8 @@ class QueryBuilder
*/
public function addCriteria(Criteria $criteria)
{
$visitor = new QueryExpressionVisitor();
$rootAlias = $this->getRootAlias();
$visitor = new QueryExpressionVisitor($rootAlias);
if ($whereExpression = $criteria->getWhereExpression()) {
$this->andWhere($visitor->dispatch($whereExpression));
@ -1098,7 +1105,7 @@ class QueryBuilder
if ($criteria->getOrderings()) {
foreach ($criteria->getOrderings() as $sort => $order) {
$this->addOrderBy($sort, $order);
$this->addOrderBy($rootAlias . '.' . $sort, $order);
}
}

View File

@ -0,0 +1,79 @@
<?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\Tools;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
/**
* Mechanism to programmatically attach entity listeners.
*
* @author Fabio B. SIlva <fabio.bat.silva@gmail.com>
*
* @since 2.5
*/
class AttachEntityListenersListener
{
/**
* @var array[]
*/
private $entityListeners = array();
/**
* Adds a entity listener for a specific entity.
*
* @param string $entityClass The entity to attach the listener.
* @param string $listenerClass The listener class.
* @param string $eventName The entity lifecycle event.
* @param string $listenerCallback|null The listener callback method or NULL to use $eventName.
*
* @return void
*/
public function addEntityListener($entityClass, $listenerClass, $eventName, $listenerCallback = null)
{
$this->entityListeners[ltrim($entityClass, '\\')][] = array(
'event' => $eventName,
'class' => $listenerClass,
'method' => $listenerCallback ?: $eventName
);
}
/**
* Processes event and attach the entity listener.
*
* @param \Doctrine\ORM\Event\LoadClassMetadataEventArgs $event
*
* @return void
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $event)
{
/** @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
$metadata = $event->getClassMetadata();
if ( ! isset($this->entityListeners[$metadata->name])) {
return;
}
foreach ($this->entityListeners[$metadata->name] as $listener) {
$metadata->addEntityListener($listener['event'], $listener['class'], $listener['method']);
}
unset($this->entityListeners[$metadata->name]);
}
}

View File

@ -0,0 +1,134 @@
<?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\Tools\Console\Command\ClearCache;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\Cache\Region\DefaultRegion;
use Doctrine\ORM\Cache;
/**
* Command to clear a collection cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CollectionRegionCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('orm:clear-cache:region:collection')
->setDescription('Clear a second-level cache collection region.')
->addArgument('owner-class', InputArgument::OPTIONAL, 'The owner entity name.')
->addArgument('association', InputArgument::OPTIONAL, 'The association collection name.')
->addArgument('owner-id', InputArgument::OPTIONAL, 'The owner identifier.')
->addOption('all', null, InputOption::VALUE_NONE, 'If defined, all entity regions will be deleted/invalidated.')
->addOption('flush', null, InputOption::VALUE_NONE,'If defined, all cache entries will be flushed.');
$this->setHelp(<<<EOT
The <info>%command.name%</info> command is meant to clear a second-level cache collection regions for an associated Entity Manager.
It is possible to delete/invalidate all collection region, a specific collection region or flushes the cache provider.
The execution type differ on how you execute the command.
If you want to invalidate all entries for an collection region this command would do the work:
<info>%command.name% 'Entities\MyEntity' 'collectionName'</info>
To invalidate a specific entry you should use :
<info>%command.name% 'Entities\MyEntity' 'collectionName' 1</info>
If you want to invalidate all entries for the all collection regions:
<info>%command.name% --all</info>
Alternatively, if you want to flush the configured cache provider for an collection region use this command:
<info>%command.name% 'Entities\MyEntity' 'collectionName' --flush</info>
Finally, be aware that if <info>--flush</info> option is passed,
not all cache providers are able to flush entries, because of a limitation of its execution nature.
EOT
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getHelper('em')->getEntityManager();
$ownerClass = $input->getArgument('owner-class');
$assoc = $input->getArgument('association');
$ownerId = $input->getArgument('owner-id');
$cache = $em->getCache();
if ( ! $cache instanceof Cache) {
throw new \InvalidArgumentException('No second-level cache is configured on the given EntityManager.');
}
if ( (! $ownerClass || ! $assoc) && ! $input->getOption('all')) {
throw new \InvalidArgumentException('Missing arguments "--owner-class" "--association"');
}
if ($input->getOption('flush')) {
$collectionRegion = $cache->getCollectionCacheRegion($ownerClass, $assoc);
if ( ! $collectionRegion instanceof DefaultRegion) {
throw new \InvalidArgumentException(sprintf(
'The option "--flush" expects a "Doctrine\ORM\Cache\Region\DefaultRegion", but got "%s".',
is_object($collectionRegion) ? get_class($collectionRegion) : gettype($collectionRegion)
));
}
$collectionRegion->getCache()->flushAll();
$output->writeln(sprintf('Flushing cache provider configured for <info>"%s#%s"</info>', $ownerClass, $assoc));
return;
}
if ($input->getOption('all')) {
$output->writeln('Clearing <info>all</info> second-level cache collection regions');
$cache->evictEntityRegions();
return;
}
if ($ownerId) {
$output->writeln(sprintf('Clearing second-level cache entry for collection <info>"%s#%s"</info> owner entity identified by <info>"%s"</info>', $ownerClass, $assoc, $ownerId));
$cache->evictCollection($ownerClass, $assoc, $ownerId);
return;
}
$output->writeln(sprintf('Clearing second-level cache for collection <info>"%s#%s"</info>', $ownerClass, $assoc));
$cache->evictCollectionRegion($ownerClass, $assoc);
}
}

View File

@ -0,0 +1,132 @@
<?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\Tools\Console\Command\ClearCache;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\Cache\Region\DefaultRegion;
use Doctrine\ORM\Cache;
/**
* Command to clear a entity cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class EntityRegionCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('orm:clear-cache:region:entity')
->setDescription('Clear a second-level cache entity region.')
->addArgument('entity-class', InputArgument::OPTIONAL, 'The entity name.')
->addArgument('entity-id', InputArgument::OPTIONAL, 'The entity identifier.')
->addOption('all', null, InputOption::VALUE_NONE, 'If defined, all entity regions will be deleted/invalidated.')
->addOption('flush', null, InputOption::VALUE_NONE,'If defined, all cache entries will be flushed.');
$this->setHelp(<<<EOT
The <info>%command.name%</info> command is meant to clear a second-level cache entity region for an associated Entity Manager.
It is possible to delete/invalidate all entity region, a specific entity region or flushes the cache provider.
The execution type differ on how you execute the command.
If you want to invalidate all entries for an entity region this command would do the work:
<info>%command.name% 'Entities\MyEntity'</info>
To invalidate a specific entry you should use :
<info>%command.name% 'Entities\MyEntity' 1</info>
If you want to invalidate all entries for the all entity regions:
<info>%command.name% --all</info>
Alternatively, if you want to flush the configured cache provider for an entity region use this command:
<info>%command.name% 'Entities\MyEntity' --flush</info>
Finally, be aware that if <info>--flush</info> option is passed,
not all cache providers are able to flush entries, because of a limitation of its execution nature.
EOT
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getHelper('em')->getEntityManager();
$entityClass = $input->getArgument('entity-class');
$entityId = $input->getArgument('entity-id');
$cache = $em->getCache();
if ( ! $cache instanceof Cache) {
throw new \InvalidArgumentException('No second-level cache is configured on the given EntityManager.');
}
if ( ! $entityClass && ! $input->getOption('all')) {
throw new \InvalidArgumentException('Invalid argument "--entity-class"');
}
if ($input->getOption('flush')) {
$entityRegion = $cache->getEntityCacheRegion($entityClass);
if ( ! $entityRegion instanceof DefaultRegion) {
throw new \InvalidArgumentException(sprintf(
'The option "--flush" expects a "Doctrine\ORM\Cache\Region\DefaultRegion", but got "%s".',
is_object($entityRegion) ? get_class($entityRegion) : gettype($entityRegion)
));
}
$entityRegion->getCache()->flushAll();
$output->writeln(sprintf('Flushing cache provider configured for entity named <info>"%s"</info>', $entityClass));
return;
}
if ($input->getOption('all')) {
$output->writeln('Clearing <info>all</info> second-level cache entity regions');
$cache->evictEntityRegions();
return;
}
if ($entityId) {
$output->writeln(sprintf('Clearing second-level cache entry for entity <info>"%s"</info> identified by <info>"%s"</info>', $entityClass, $entityId));
$cache->evictEntity($entityClass, $entityId);
return;
}
$output->writeln(sprintf('Clearing second-level cache for entity <info>"%s"</info>', $entityClass));
$cache->evictEntityRegion($entityClass);
}
}

View File

@ -24,6 +24,7 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\Common\Cache\ApcCache;
use Doctrine\Common\Cache\XcacheCache;
/**
* Command to clear the metadata cache of the various cache drivers.
@ -88,6 +89,11 @@ EOT
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
}
if ($cacheDriver instanceof XcacheCache) {
throw new \LogicException("Cannot clear XCache Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
}
$output->writeln('Clearing ALL Metadata cache entries');
$result = $cacheDriver->deleteAll();

View File

@ -24,6 +24,7 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\Common\Cache\ApcCache;
use Doctrine\Common\Cache\XcacheCache;
/**
* Command to clear the query cache of the various cache drivers.
@ -87,7 +88,10 @@ EOT
if ($cacheDriver instanceof ApcCache) {
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
}
if ($cacheDriver instanceof XcacheCache) {
throw new \LogicException("Cannot clear XCache Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
}
$output->write('Clearing ALL Query cache entries' . PHP_EOL);
$result = $cacheDriver->deleteAll();

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