Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #808 from FabioBatSilva/slc

Second level cache
  • Loading branch information...
commit b081e5681d9dc75ea2ab4a609e1aff5f4b7f2b87 2 parents 86ae6f1 + 4e0e1b8
@guilhermeblanco guilhermeblanco authored
Showing with 16,654 additions and 393 deletions.
  1. +7 −4 .travis.yml
  2. +1 −0  docs/en/index.rst
  3. +12 −0 docs/en/reference/annotations-reference.rst
  4. +725 −0 docs/en/reference/second-level-cache.rst
  5. +8 −4 docs/en/toc.rst
  6. +18 −0 doctrine-mapping.xsd
  7. +241 −16 lib/Doctrine/ORM/AbstractQuery.php
  8. +187 −0 lib/Doctrine/ORM/Cache.php
  9. +159 −0 lib/Doctrine/ORM/Cache/CacheConfiguration.php
  10. +36 −0 lib/Doctrine/ORM/Cache/CacheEntry.php
  11. +72 −0 lib/Doctrine/ORM/Cache/CacheException.php
  12. +104 −0 lib/Doctrine/ORM/Cache/CacheFactory.php
  13. +38 −0 lib/Doctrine/ORM/Cache/CacheKey.php
  14. +57 −0 lib/Doctrine/ORM/Cache/CollectionCacheEntry.php
  15. +66 −0 lib/Doctrine/ORM/Cache/CollectionCacheKey.php
  16. +54 −0 lib/Doctrine/ORM/Cache/CollectionHydrator.php
  17. +59 −0 lib/Doctrine/ORM/Cache/ConcurrentRegion.php
  18. +342 −0 lib/Doctrine/ORM/Cache/DefaultCache.php
  19. +235 −0 lib/Doctrine/ORM/Cache/DefaultCacheFactory.php
  20. +103 −0 lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php
  21. +150 −0 lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php
  22. +338 −0 lib/Doctrine/ORM/Cache/DefaultQueryCache.php
  23. +66 −0 lib/Doctrine/ORM/Cache/EntityCacheEntry.php
  24. +57 −0 lib/Doctrine/ORM/Cache/EntityCacheKey.php
  25. +51 −0 lib/Doctrine/ORM/Cache/EntityHydrator.php
  26. +58 −0 lib/Doctrine/ORM/Cache/Lock.php
  27. +32 −0 lib/Doctrine/ORM/Cache/LockException.php
  28. +106 −0 lib/Doctrine/ORM/Cache/Logging/CacheLogger.php
  29. +156 −0 lib/Doctrine/ORM/Cache/Logging/CacheLoggerChain.php
  30. +251 −0 lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php
  31. +275 −0 lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php
  32. +534 −0 lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php
  33. +64 −0 lib/Doctrine/ORM/Cache/Persister/CachedCollectionPersister.php
  34. +45 −0 lib/Doctrine/ORM/Cache/Persister/CachedEntityPersister.php
  35. +46 −0 lib/Doctrine/ORM/Cache/Persister/CachedPersister.php
  36. +104 −0 lib/Doctrine/ORM/Cache/Persister/NonStrictReadWriteCachedCollectionPersister.php
  37. +124 −0 lib/Doctrine/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersister.php
  38. +44 −0 lib/Doctrine/ORM/Cache/Persister/ReadOnlyCachedCollectionPersister.php
  39. +41 −0 lib/Doctrine/ORM/Cache/Persister/ReadOnlyCachedEntityPersister.php
  40. +135 −0 lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedCollectionPersister.php
  41. +138 −0 lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php
  42. +62 −0 lib/Doctrine/ORM/Cache/QueryCache.php
  43. +62 −0 lib/Doctrine/ORM/Cache/QueryCacheEntry.php
  44. +58 −0 lib/Doctrine/ORM/Cache/QueryCacheKey.php
  45. +42 −0 lib/Doctrine/ORM/Cache/QueryCacheValidator.php
  46. +86 −0 lib/Doctrine/ORM/Cache/Region.php
  47. +121 −0 lib/Doctrine/ORM/Cache/Region/DefaultRegion.php
  48. +245 −0 lib/Doctrine/ORM/Cache/Region/FileLockRegion.php
  49. +42 −0 lib/Doctrine/ORM/Cache/Region/UpdateTimestampCache.php
  50. +134 −0 lib/Doctrine/ORM/Cache/RegionsConfiguration.php
  51. +57 −0 lib/Doctrine/ORM/Cache/TimestampCacheEntry.php
  52. +38 −0 lib/Doctrine/ORM/Cache/TimestampCacheKey.php
  53. +43 −0 lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php
  54. +39 −0 lib/Doctrine/ORM/Cache/TimestampRegion.php
  55. +57 −4 lib/Doctrine/ORM/Configuration.php
  56. +8 −0 lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php
  57. +19 −1 lib/Doctrine/ORM/EntityManager.php
  58. +7 −0 lib/Doctrine/ORM/EntityManagerInterface.php
  59. +44 −0 lib/Doctrine/ORM/Mapping/Cache.php
  60. +4 −0 lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
  61. +63 −1 lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
  62. +19 −0 lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php
  63. +2 −1  lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php
  64. +51 −0 lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
  65. +53 −0 lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php
  66. +24 −0 lib/Doctrine/ORM/ORMException.php
  67. +13 −81 lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php
  68. +50 −170 lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
  69. +139 −0 lib/Doctrine/ORM/Persisters/CollectionPersister.php
  70. +299 −0 lib/Doctrine/ORM/Persisters/EntityPersister.php
  71. +1 −1  lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php
  72. +3 −13 lib/Doctrine/ORM/Persisters/ManyToManyPersister.php
  73. +4 −16 lib/Doctrine/ORM/Persisters/OneToManyPersister.php
  74. +17 −16 lib/Doctrine/ORM/Proxy/ProxyFactory.php
  75. +58 −15 lib/Doctrine/ORM/Query.php
  76. +8 −0 lib/Doctrine/ORM/Query/ResultSetMapping.php
  77. +4 −1 lib/Doctrine/ORM/Query/SqlWalker.php
  78. +134 −0 lib/Doctrine/ORM/Tools/Console/Command/ClearCache/CollectionRegionCommand.php
  79. +132 −0 lib/Doctrine/ORM/Tools/Console/Command/ClearCache/EntityRegionCommand.php
  80. +124 −0 lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryRegionCommand.php
  81. +135 −40 lib/Doctrine/ORM/UnitOfWork.php
  82. +35 −0 tests/Doctrine/Tests/EventListener/CacheMetadataListener.php
  83. +13 −0 tests/Doctrine/Tests/Mocks/CacheEntryMock.php
  84. +21 −0 tests/Doctrine/Tests/Mocks/CacheKeyMock.php
  85. +115 −0 tests/Doctrine/Tests/Mocks/CacheRegionMock.php
  86. +202 −0 tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php
  87. +19 −0 tests/Doctrine/Tests/Mocks/TimestampRegionMock.php
  88. +95 −0 tests/Doctrine/Tests/Models/Cache/Attraction.php
  89. +33 −0 tests/Doctrine/Tests/Models/Cache/AttractionContactInfo.php
  90. +54 −0 tests/Doctrine/Tests/Models/Cache/AttractionInfo.php
  91. +33 −0 tests/Doctrine/Tests/Models/Cache/AttractionLocationInfo.php
  92. +11 −0 tests/Doctrine/Tests/Models/Cache/Bar.php
  93. +11 −0 tests/Doctrine/Tests/Models/Cache/Beach.php
  94. +109 −0 tests/Doctrine/Tests/Models/Cache/City.php
  95. +50 −0 tests/Doctrine/Tests/Models/Cache/Country.php
  96. +65 −0 tests/Doctrine/Tests/Models/Cache/Flight.php
  97. +11 −0 tests/Doctrine/Tests/Models/Cache/Restaurant.php
  98. +92 −0 tests/Doctrine/Tests/Models/Cache/State.php
  99. +112 −0 tests/Doctrine/Tests/Models/Cache/Travel.php
  100. +91 −0 tests/Doctrine/Tests/Models/Cache/Traveler.php
  101. +85 −0 tests/Doctrine/Tests/ORM/Cache/AbstractRegionTest.php
  102. +82 −0 tests/Doctrine/Tests/ORM/Cache/CacheConfigTest.php
  103. +68 −0 tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php
  104. +118 −0 tests/Doctrine/Tests/ORM/Cache/CacheLoggerChainTest.php
  105. +267 −0 tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php
  106. +263 −0 tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php
  107. +77 −0 tests/Doctrine/Tests/ORM/Cache/DefaultCollectionHydratorTest.php
  108. +125 −0 tests/Doctrine/Tests/ORM/Cache/DefaultEntityHydratorTest.php
  109. +549 −0 tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php
  110. +50 −0 tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php
  111. +250 −0 tests/Doctrine/Tests/ORM/Cache/FileLockRegionTest.php
  112. +301 −0 tests/Doctrine/Tests/ORM/Cache/Persister/AbstractCollectionPersisterTest.php
  113. +416 −0 tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php
  114. +23 −0 tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedCollectionPersisterTest.php
  115. +140 −0 tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersisterTest.php
  116. +22 −0 tests/Doctrine/Tests/ORM/Cache/Persister/ReadOnlyCachedCollectionPersisterTest.php
  117. +36 −0 tests/Doctrine/Tests/ORM/Cache/Persister/ReadOnlyCachedEntityPersisterTest.php
  118. +301 −0 tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php
  119. +234 −0 tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php
  120. +134 −0 tests/Doctrine/Tests/ORM/Cache/StatisticsCacheLoggerTest.php
  121. +12 −0 tests/Doctrine/Tests/ORM/ConfigurationTest.php
  122. +3 −0  tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php
  123. +6 −0 tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php
  124. +1 −1  tests/Doctrine/Tests/ORM/Functional/JoinedTableCompositeKeyTest.php
  125. +9 −0 tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php
  126. +2 −0  tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php
  127. +203 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php
  128. +175 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheCompositePrimaryKeyTest.php
  129. +146 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php
  130. +81 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheExtraLazyCollectionTest.php
  131. +191 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php
  132. +214 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php
  133. +121 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php
  134. +350 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php
  135. +898 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php
  136. +250 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheRepositoryTest.php
  137. +209 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php
  138. +351 −0 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php
  139. +1 −1  tests/Doctrine/Tests/ORM/Functional/SingleTableCompositeKeyTest.php
  140. +8 −0 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php
  141. +3 −0  tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php
  142. +1 −0  tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1595Test.php
  143. +1 −0  tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2012Test.php
  144. +1 −0  tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2090Test.php
  145. +1 −0  tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2350Test.php
  146. +1 −0  tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2494Test.php
  147. +3 −0  tests/Doctrine/Tests/ORM/Functional/Ticket/DDC742Test.php
  148. +29 −1 tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php
  149. +54 −0 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Cache.City.php
  150. +23 −0 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Cache.City.dcm.xml
  151. +36 −0 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Cache.City.dcm.yml
  152. +282 −0 tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php
  153. +94 −0 tests/Doctrine/Tests/ORM/Tools/Console/Command/ClearCacheCollectionRegionCommandTest.php
  154. +91 −0 tests/Doctrine/Tests/ORM/Tools/Console/Command/ClearCacheEntityRegionCommandTest.php
  155. +90 −0 tests/Doctrine/Tests/ORM/Tools/Console/Command/ClearCacheQueryRegionCommandTest.php
  156. +62 −1 tests/Doctrine/Tests/OrmFunctionalTestCase.php
  157. +60 −0 tests/Doctrine/Tests/OrmTestCase.php
  158. +7 −1 tests/travis/mysql.travis.xml
  159. +7 −2 tests/travis/pgsql.travis.xml
  160. +7 −2 tests/travis/sqlite.travis.xml
  161. +14 −0 tools/sandbox/bootstrap.php
  162. +3 −0  tools/sandbox/doctrine.php
View
11 .travis.yml
@@ -7,9 +7,12 @@ php:
- 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"
@@ -19,7 +22,7 @@ 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
View
1  docs/en/index.rst
@@ -79,6 +79,7 @@ Advanced Topics
* :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
12 docs/en/reference/annotations-reference.rst
@@ -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
View
725 docs/en/reference/second-level-cache.rst
@@ -0,0 +1,725 @@
+The Second Level Cache
+======================
+
+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 doesn’t 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
12 docs/en/toc.rst
@@ -53,10 +53,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
--------
@@ -81,4 +84,5 @@ Cookbook
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session
+ cookbook/resolve-target-entity-listener
View
18 doctrine-mapping.xsd
@@ -55,6 +55,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>
@@ -152,8 +160,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"/>
@@ -445,6 +459,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" />
@@ -462,6 +477,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"/>
@@ -477,6 +493,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"/>
@@ -495,6 +512,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
257 lib/Doctrine/ORM/AbstractQuery.php
@@ -22,9 +22,11 @@
use Doctrine\Common\Util\ClassUtils;
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;
@@ -121,14 +123,150 @@
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>.
*
* @param \Doctrine\ORM\EntityManager $em
*/
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 +346,7 @@ public function setParameters($parameters)
$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 +385,7 @@ function ($parameter) use ($key)
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 +397,14 @@ function ($parameter) use ($key)
*
* @return array
*
- * @throws ORMInvalidArgumentException
+ * @throws \Doctrine\ORM\ORMInvalidArgumentException
*/
public function processParameterValue($value)
{
+ if (is_scalar($value)) {
+ return $value;
+ }
+
if (is_array($value)) {
foreach ($value as $key => $paramValue) {
$paramValue = $this->processParameterValue($paramValue);
@@ -307,6 +445,16 @@ public function setResultSetMapping(Query\ResultSetMapping $rsm)
}
/**
+ * 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.
*
* @param Query\ResultSetMapping $rsm
@@ -747,11 +895,10 @@ public function iterate($parameters = null, $hydrationMode = null)
$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);
}
/**
@@ -764,6 +911,23 @@ public function iterate($parameters = null, $hydrationMode = null)
*/
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,9 +968,8 @@ public function execute($parameters = null, $hydrationMode = null)
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);
@@ -814,6 +977,43 @@ public function execute($parameters = null, $hydrationMode = null)
}
/**
+ * 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
* automatically generated for you.
@@ -886,4 +1086,29 @@ public function __clone()
$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));
+ }
}
View
187 lib/Doctrine/ORM/Cache.php
@@ -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
159 lib/Doctrine/ORM/Cache/CacheConfiguration.php
@@ -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
36 lib/Doctrine/ORM/Cache/CacheEntry.php
@@ -0,0 +1,36 @@
+<?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 entry interface
+ *
+ * <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>
+ */
+interface CacheEntry
+{
+
+}
View
72 lib/Doctrine/ORM/Cache/CacheException.php
@@ -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
104 lib/Doctrine/ORM/Cache/CacheFactory.php
@@ -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
38 lib/Doctrine/ORM/Cache/CacheKey.php
@@ -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
57 lib/Doctrine/ORM/Cache/CollectionCacheEntry.php
@@ -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
66 lib/Doctrine/ORM/Cache/CollectionCacheKey.php
@@ -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
54 lib/Doctrine/ORM/Cache/CollectionHydrator.php
@@ -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
59 lib/Doctrine/ORM/Cache/ConcurrentRegion.php
@@ -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.