commit
b081e5681d
11
.travis.yml
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
|
||||
|
@ -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
|
||||
---------
|
||||
|
@ -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
|
||||
|
725
docs/en/reference/second-level-cache.rst
Normal file
725
docs/en/reference/second-level-cache.rst
Normal file
@ -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.
|
@ -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
|
||||
|
||||
|
@ -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"/>
|
||||
|
@ -22,9 +22,11 @@ namespace Doctrine\ORM;
|
||||
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;
|
||||
|
||||
@ -120,6 +122,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 +165,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 +346,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 +385,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 +397,14 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @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);
|
||||
@ -306,6 +444,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 +895,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 +910,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 +968,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 +1086,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
187
lib/Doctrine/ORM/Cache.php
Normal 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);
|
||||
}
|
159
lib/Doctrine/ORM/Cache/CacheConfiguration.php
Normal file
159
lib/Doctrine/ORM/Cache/CacheConfiguration.php
Normal 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;
|
||||
}
|
||||
}
|
36
lib/Doctrine/ORM/Cache/CacheEntry.php
Normal file
36
lib/Doctrine/ORM/Cache/CacheEntry.php
Normal file
@ -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
|
||||
{
|
||||
|
||||
}
|
72
lib/Doctrine/ORM/Cache/CacheException.php
Normal file
72
lib/Doctrine/ORM/Cache/CacheException.php
Normal 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));
|
||||
}
|
||||
}
|
104
lib/Doctrine/ORM/Cache/CacheFactory.php
Normal file
104
lib/Doctrine/ORM/Cache/CacheFactory.php
Normal 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();
|
||||
}
|
38
lib/Doctrine/ORM/Cache/CacheKey.php
Normal file
38
lib/Doctrine/ORM/Cache/CacheKey.php
Normal 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;
|
||||
}
|
57
lib/Doctrine/ORM/Cache/CollectionCacheEntry.php
Normal file
57
lib/Doctrine/ORM/Cache/CollectionCacheEntry.php
Normal 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']);
|
||||
}
|
||||
}
|
66
lib/Doctrine/ORM/Cache/CollectionCacheKey.php
Normal file
66
lib/Doctrine/ORM/Cache/CollectionCacheKey.php
Normal 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;
|
||||
}
|
||||
}
|
54
lib/Doctrine/ORM/Cache/CollectionHydrator.php
Normal file
54
lib/Doctrine/ORM/Cache/CollectionHydrator.php
Normal 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);
|
||||
}
|
59
lib/Doctrine/ORM/Cache/ConcurrentRegion.php
Normal file
59
lib/Doctrine/ORM/Cache/ConcurrentRegion.php
Normal 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);
|
||||
}
|
342
lib/Doctrine/ORM/Cache/DefaultCache.php
Normal file
342
lib/Doctrine/ORM/Cache/DefaultCache.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
235
lib/Doctrine/ORM/Cache/DefaultCacheFactory.php
Normal file
235
lib/Doctrine/ORM/Cache/DefaultCacheFactory.php
Normal 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;
|
||||
}
|
||||
}
|
103
lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php
Normal file
103
lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php
Normal 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;
|
||||
}
|
||||
}
|
150
lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php
Normal file
150
lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?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\Common\Proxy\Proxy;
|
||||
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) === 0 ||
|
||||
($data[$name] instanceof Proxy && ! $data[$name]->__isInitialized__)) {
|
||||
|
||||
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
|
||||
? $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);
|
||||
}
|
||||
}
|
338
lib/Doctrine/ORM/Cache/DefaultQueryCache.php
Normal file
338
lib/Doctrine/ORM/Cache/DefaultQueryCache.php
Normal 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;
|
||||
}
|
||||
}
|
66
lib/Doctrine/ORM/Cache/EntityCacheEntry.php
Normal file
66
lib/Doctrine/ORM/Cache/EntityCacheEntry.php
Normal 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']);
|
||||
}
|
||||
}
|
57
lib/Doctrine/ORM/Cache/EntityCacheKey.php
Normal file
57
lib/Doctrine/ORM/Cache/EntityCacheKey.php
Normal 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));
|
||||
}
|
||||
}
|
51
lib/Doctrine/ORM/Cache/EntityHydrator.php
Normal file
51
lib/Doctrine/ORM/Cache/EntityHydrator.php
Normal 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);
|
||||
}
|
58
lib/Doctrine/ORM/Cache/Lock.php
Normal file
58
lib/Doctrine/ORM/Cache/Lock.php
Normal 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()));
|
||||
}
|
||||
}
|
32
lib/Doctrine/ORM/Cache/LockException.php
Normal file
32
lib/Doctrine/ORM/Cache/LockException.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Lock exception for cache.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class LockException extends CacheException
|
||||
{
|
||||
|
||||
}
|
106
lib/Doctrine/ORM/Cache/Logging/CacheLogger.php
Normal file
106
lib/Doctrine/ORM/Cache/Logging/CacheLogger.php
Normal 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);
|
||||
}
|
156
lib/Doctrine/ORM/Cache/Logging/CacheLoggerChain.php
Normal file
156
lib/Doctrine/ORM/Cache/Logging/CacheLoggerChain.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
251
lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php
Normal file
251
lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php
Normal 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);
|
||||
}
|
||||
}
|
275
lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php
Normal file
275
lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php
Normal 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);
|
||||
}
|
||||
}
|
534
lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php
Normal file
534
lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php
Normal file
@ -0,0 +1,534 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ($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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
45
lib/Doctrine/ORM/Cache/Persister/CachedEntityPersister.php
Normal file
45
lib/Doctrine/ORM/Cache/Persister/CachedEntityPersister.php
Normal 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);
|
||||
}
|
46
lib/Doctrine/ORM/Cache/Persister/CachedPersister.php
Normal file
46
lib/Doctrine/ORM/Cache/Persister/CachedPersister.php
Normal 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();
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
62
lib/Doctrine/ORM/Cache/QueryCache.php
Normal file
62
lib/Doctrine/ORM/Cache/QueryCache.php
Normal 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();
|
||||
}
|
62
lib/Doctrine/ORM/Cache/QueryCacheEntry.php
Normal file
62
lib/Doctrine/ORM/Cache/QueryCacheEntry.php
Normal 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']);
|
||||
}
|
||||
}
|
58
lib/Doctrine/ORM/Cache/QueryCacheKey.php
Normal file
58
lib/Doctrine/ORM/Cache/QueryCacheKey.php
Normal 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;
|
||||
}
|
||||
}
|
42
lib/Doctrine/ORM/Cache/QueryCacheValidator.php
Normal file
42
lib/Doctrine/ORM/Cache/QueryCacheValidator.php
Normal 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);
|
||||
}
|
86
lib/Doctrine/ORM/Cache/Region.php
Normal file
86
lib/Doctrine/ORM/Cache/Region.php
Normal 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();
|
||||
}
|
121
lib/Doctrine/ORM/Cache/Region/DefaultRegion.php
Normal file
121
lib/Doctrine/ORM/Cache/Region/DefaultRegion.php
Normal 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();
|
||||
}
|
||||
}
|
245
lib/Doctrine/ORM/Cache/Region/FileLockRegion.php
Normal file
245
lib/Doctrine/ORM/Cache/Region/FileLockRegion.php
Normal 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;
|
||||
}
|
||||
}
|
42
lib/Doctrine/ORM/Cache/Region/UpdateTimestampCache.php
Normal file
42
lib/Doctrine/ORM/Cache/Region/UpdateTimestampCache.php
Normal 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);
|
||||
}
|
||||
}
|
134
lib/Doctrine/ORM/Cache/RegionsConfiguration.php
Normal file
134
lib/Doctrine/ORM/Cache/RegionsConfiguration.php
Normal 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;
|
||||
}
|
||||
}
|
57
lib/Doctrine/ORM/Cache/TimestampCacheEntry.php
Normal file
57
lib/Doctrine/ORM/Cache/TimestampCacheEntry.php
Normal 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']);
|
||||
}
|
||||
}
|
38
lib/Doctrine/ORM/Cache/TimestampCacheKey.php
Normal file
38
lib/Doctrine/ORM/Cache/TimestampCacheKey.php
Normal 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;
|
||||
}
|
||||
}
|
43
lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php
Normal file
43
lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php
Normal 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();
|
||||
}
|
||||
}
|
39
lib/Doctrine/ORM/Cache/TimestampRegion.php
Normal file
39
lib/Doctrine/ORM/Cache/TimestampRegion.php
Normal 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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -268,4 +268,12 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
|
||||
{
|
||||
return $this->wrapped->hasFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCache()
|
||||
{
|
||||
return $this->wrapped->getCache();
|
||||
}
|
||||
}
|
||||
|
@ -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,6 +164,11 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
$config->getProxyNamespace(),
|
||||
$config->getAutoGenerateProxyClasses()
|
||||
);
|
||||
|
||||
if ($config->isSecondLevelCacheEnabled()) {
|
||||
$cacheClass = $config->getSecondLevelCacheConfiguration()->getCacheClassName();
|
||||
$this->cache = new $cacheClass($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,6 +209,14 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
$this->conn->beginTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getCache()
|
||||
{
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@ -405,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) {
|
||||
|
@ -30,6 +30,13 @@ 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.
|
||||
*
|
||||
|
44
lib/Doctrine/ORM/Mapping/Cache.php
Normal file
44
lib/Doctrine/ORM/Mapping/Cache.php
Normal 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;
|
||||
}
|
@ -144,6 +144,10 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
$class->setPrimaryTable($parent->table);
|
||||
}
|
||||
|
||||
if ($parent && $parent->cache) {
|
||||
$class->cache = $parent->cache;
|
||||
}
|
||||
|
||||
if ($parent && $parent->containsForeignIdentifier) {
|
||||
$class->containsForeignIdentifier = true;
|
||||
}
|
||||
|
@ -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 doesn’t 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.
|
||||
*
|
||||
@ -577,6 +591,11 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*/
|
||||
public $versionField;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $cache = null;
|
||||
|
||||
/**
|
||||
* The ReflectionClass instance of the mapped class.
|
||||
*
|
||||
@ -855,6 +874,10 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$serialized[] = "customGeneratorDefinition";
|
||||
}
|
||||
|
||||
if ($this->cache) {
|
||||
$serialized[] = 'cache';
|
||||
}
|
||||
|
||||
return $serialized;
|
||||
}
|
||||
|
||||
@ -979,6 +1002,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.
|
||||
*
|
||||
|
@ -128,6 +128,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'];
|
||||
@ -365,6 +376,14 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
|
||||
$metadata->mapManyToMany($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
|
||||
|
@ -64,4 +64,5 @@ require_once __DIR__.'/../AssociationOverride.php';
|
||||
require_once __DIR__.'/../AssociationOverrides.php';
|
||||
require_once __DIR__.'/../AttributeOverride.php';
|
||||
require_once __DIR__.'/../AttributeOverrides.php';
|
||||
require_once __DIR__.'/../EntityListeners.php';
|
||||
require_once __DIR__.'/../EntityListeners.php';
|
||||
require_once __DIR__.'/../Cache.php';
|
||||
|
@ -81,6 +81,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) {
|
||||
@ -349,6 +354,11 @@ class XmlDriver extends FileDriver
|
||||
}
|
||||
|
||||
$metadata->mapOneToOne($mapping);
|
||||
|
||||
// Evaluate second level cache
|
||||
if (isset($oneToOneElement->cache)) {
|
||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToOneElement->cache));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,6 +398,11 @@ class XmlDriver extends FileDriver
|
||||
}
|
||||
|
||||
$metadata->mapOneToMany($mapping);
|
||||
|
||||
// Evaluate second level cache
|
||||
if (isset($oneToManyElement->cache)) {
|
||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToManyElement->cache));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -428,6 +443,11 @@ class XmlDriver extends FileDriver
|
||||
}
|
||||
|
||||
$metadata->mapManyToOne($mapping);
|
||||
|
||||
// Evaluate second level cache
|
||||
if (isset($manyToOneElement->cache)) {
|
||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToOneElement->cache));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,6 +513,11 @@ class XmlDriver extends FileDriver
|
||||
}
|
||||
|
||||
$metadata->mapManyToMany($mapping);
|
||||
|
||||
// Evaluate second level cache
|
||||
if (isset($manyToManyElement->cache)) {
|
||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToManyElement->cache));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -701,6 +726,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.
|
||||
*
|
||||
|
@ -72,9 +72,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
|
||||
@ -361,6 +368,11 @@ class YamlDriver extends FileDriver
|
||||
}
|
||||
|
||||
$metadata->mapOneToOne($mapping);
|
||||
|
||||
// Evaluate second level cache
|
||||
if (isset($oneToOneElement['cache'])) {
|
||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToOneElement['cache']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,6 +406,11 @@ class YamlDriver extends FileDriver
|
||||
}
|
||||
|
||||
$metadata->mapOneToMany($mapping);
|
||||
|
||||
// Evaluate second level cache
|
||||
if (isset($oneToManyElement['cache'])) {
|
||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToManyElement['cache']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,6 +455,11 @@ class YamlDriver extends FileDriver
|
||||
}
|
||||
|
||||
$metadata->mapManyToOne($mapping);
|
||||
|
||||
// Evaluate second level cache
|
||||
if (isset($manyToOneElement['cache'])) {
|
||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToOneElement['cache']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -506,6 +528,11 @@ class YamlDriver extends FileDriver
|
||||
}
|
||||
|
||||
$metadata->mapManyToMany($mapping);
|
||||
|
||||
// Evaluate second level cache
|
||||
if (isset($manyToManyElement['cache'])) {
|
||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToManyElement['cache']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -704,6 +731,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}
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
{
|
||||
@ -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)
|
||||
{
|
||||
|
139
lib/Doctrine/ORM/Persisters/CollectionPersister.php
Normal file
139
lib/Doctrine/ORM/Persisters/CollectionPersister.php
Normal 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);
|
||||
}
|
299
lib/Doctrine/ORM/Persisters/EntityPersister.php
Normal file
299
lib/Doctrine/ORM/Persisters/EntityPersister.php
Normal 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());
|
||||
}
|
@ -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();
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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\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 query cache region.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class QueryRegionCommand extends Command
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('orm:clear-cache:region:query')
|
||||
->setDescription('Clear a second-level cache query region.')
|
||||
->addArgument('region-name', InputArgument::OPTIONAL, 'The query region to clear.')
|
||||
->addOption('all', null, InputOption::VALUE_NONE, 'If defined, all query 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 query region for an associated Entity Manager.
|
||||
It is possible to delete/invalidate all query region, a specific query region or flushes the cache provider.
|
||||
|
||||
The execution type differ on how you execute the command.
|
||||
If you want to invalidate all entries for the default query region this command would do the work:
|
||||
|
||||
<info>%command.name%</info>
|
||||
|
||||
To invalidate entries for a specific query region you should use :
|
||||
|
||||
<info>%command.name% my_region_name</info>
|
||||
|
||||
If you want to invalidate all entries for the all query region:
|
||||
|
||||
<info>%command.name% --all</info>
|
||||
|
||||
Alternatively, if you want to flush the configured cache provider use this command:
|
||||
|
||||
<info>%command.name% my_region_name --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();
|
||||
$name = $input->getArgument('region-name');
|
||||
$cache = $em->getCache();
|
||||
|
||||
if ($name === null) {
|
||||
$name = Cache::DEFAULT_QUERY_REGION_NAME;
|
||||
}
|
||||
|
||||
if ( ! $cache instanceof Cache) {
|
||||
throw new \InvalidArgumentException('No second-level cache is configured on the given EntityManager.');
|
||||
}
|
||||
|
||||
if ($input->getOption('flush')) {
|
||||
$queryCache = $cache->getQueryCache($name);
|
||||
$queryRegion = $queryCache->getRegion();
|
||||
|
||||
if ( ! $queryRegion instanceof DefaultRegion) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'The option "--flush" expects a "Doctrine\ORM\Cache\Region\DefaultRegion", but got "%s".',
|
||||
is_object($queryRegion) ? get_class($queryRegion) : gettype($queryRegion)
|
||||
));
|
||||
}
|
||||
|
||||
$queryRegion->getCache()->flushAll();
|
||||
|
||||
$output->writeln(sprintf('Flushing cache provider configured for second-level cache query region named <info>"%s"</info>', $name));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input->getOption('all')) {
|
||||
$output->writeln('Clearing <info>all</info> second-level cache query regions');
|
||||
|
||||
$cache->evictQueryRegions();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('Clearing second-level cache query region named <info>"%s"</info>', $name));
|
||||
$cache->evictQueryRegion($name);
|
||||
}
|
||||
}
|
@ -38,6 +38,13 @@ use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
use Doctrine\ORM\Event\PostFlushEventArgs;
|
||||
use Doctrine\ORM\Event\ListenersInvoker;
|
||||
|
||||
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
||||
use Doctrine\ORM\Persisters\BasicEntityPersister;
|
||||
use Doctrine\ORM\Persisters\SingleTablePersister;
|
||||
use Doctrine\ORM\Persisters\JoinedSubclassPersister;
|
||||
use Doctrine\ORM\Persisters\OneToManyPersister;
|
||||
use Doctrine\ORM\Persisters\ManyToManyPersister;
|
||||
|
||||
/**
|
||||
* The UnitOfWork is responsible for tracking changes to objects during an
|
||||
* "object-level" transaction and for writing out changes to the database
|
||||
@ -254,6 +261,11 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
private $eagerLoadingEntities = array();
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $hasCache = false;
|
||||
|
||||
/**
|
||||
* Initializes a new UnitOfWork instance, bound to the given EntityManager.
|
||||
*
|
||||
@ -264,6 +276,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$this->em = $em;
|
||||
$this->evm = $em->getEventManager();
|
||||
$this->listenersInvoker = new ListenersInvoker($em);
|
||||
$this->hasCache = $em->getConfiguration()->isSecondLevelCacheEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -351,6 +364,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
foreach ($this->collectionDeletions as $collectionToDelete) {
|
||||
$this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
|
||||
}
|
||||
|
||||
// Collection updates (deleteRows, updateRows, insertRows)
|
||||
foreach ($this->collectionUpdates as $collectionToUpdate) {
|
||||
$this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate);
|
||||
@ -368,9 +382,13 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$this->em->close();
|
||||
$conn->rollback();
|
||||
|
||||
$this->afterTransactionRolledBack();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->afterTransactionComplete();
|
||||
|
||||
// Take new snapshots from visited collections
|
||||
foreach ($this->visitedCollections as $coll) {
|
||||
$coll->takeSnapshot();
|
||||
@ -675,17 +693,22 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
// Look for changes in associations of the entity
|
||||
foreach ($class->associationMappings as $field => $assoc) {
|
||||
if (($val = $class->reflFields[$field]->getValue($entity)) !== null) {
|
||||
$this->computeAssociationChanges($assoc, $val);
|
||||
if (!isset($this->entityChangeSets[$oid]) &&
|
||||
$assoc['isOwningSide'] &&
|
||||
$assoc['type'] == ClassMetadata::MANY_TO_MANY &&
|
||||
$val instanceof PersistentCollection &&
|
||||
$val->isDirty()) {
|
||||
$this->entityChangeSets[$oid] = array();
|
||||
$this->originalEntityData[$oid] = $actualData;
|
||||
$this->entityUpdates[$oid] = $entity;
|
||||
}
|
||||
|
||||
if (($val = $class->reflFields[$field]->getValue($entity)) === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->computeAssociationChanges($assoc, $val);
|
||||
|
||||
if ( ! isset($this->entityChangeSets[$oid]) &&
|
||||
$assoc['isOwningSide'] &&
|
||||
$assoc['type'] == ClassMetadata::MANY_TO_MANY &&
|
||||
$val instanceof PersistentCollection &&
|
||||
$val->isDirty()) {
|
||||
|
||||
$this->entityChangeSets[$oid] = array();
|
||||
$this->originalEntityData[$oid] = $actualData;
|
||||
$this->entityUpdates[$oid] = $entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -746,8 +769,8 @@ class UnitOfWork implements PropertyChangedListener
|
||||
/**
|
||||
* Computes the changes of an association.
|
||||
*
|
||||
* @param array $assoc
|
||||
* @param mixed $value The value of the association.
|
||||
* @param array $assoc The association mapping.
|
||||
* @param mixed $value The value of the association.
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
@ -780,15 +803,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$state = $this->getEntityState($entry, self::STATE_NEW);
|
||||
|
||||
if ( ! ($entry instanceof $assoc['targetEntity'])) {
|
||||
throw new ORMException(
|
||||
sprintf(
|
||||
'Found entity of type %s on association %s#%s, but expecting %s',
|
||||
get_class($entry),
|
||||
$assoc['sourceEntity'],
|
||||
$assoc['fieldName'],
|
||||
$targetClass->name
|
||||
)
|
||||
);
|
||||
throw ORMException::unexpectedAssociationValue($assoc['sourceEntity'], $assoc['fieldName'], get_class($entry), $assoc['targetEntity']);
|
||||
}
|
||||
|
||||
switch ($state) {
|
||||
@ -936,6 +951,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
|
||||
|
||||
foreach ($this->entityInsertions as $oid => $entity) {
|
||||
|
||||
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
|
||||
continue;
|
||||
}
|
||||
@ -987,6 +1003,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
|
||||
|
||||
foreach ($this->entityUpdates as $oid => $entity) {
|
||||
|
||||
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
|
||||
continue;
|
||||
}
|
||||
@ -2522,7 +2539,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
|
||||
|
||||
// If only a specific entity is set to refresh, check that it's the one
|
||||
if(isset($hints[Query::HINT_REFRESH_ENTITY])) {
|
||||
if (isset($hints[Query::HINT_REFRESH_ENTITY])) {
|
||||
$overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity;
|
||||
}
|
||||
|
||||
@ -2591,6 +2608,14 @@ class UnitOfWork implements PropertyChangedListener
|
||||
continue 2;
|
||||
}
|
||||
|
||||
// use the entity association
|
||||
if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
|
||||
$class->reflFields[$field]->setValue($entity, $data[$field]);
|
||||
$this->originalEntityData[$oid][$field] = $data[$field];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$associatedId = array();
|
||||
|
||||
// TODO: Is this even computed right in all cases of composite keys?
|
||||
@ -2697,6 +2722,22 @@ class UnitOfWork implements PropertyChangedListener
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignore if its a cached collection
|
||||
if (isset($hints[Query::HINT_CACHE_ENABLED]) && $class->getFieldValue($entity, $field) instanceof PersistentCollection) {
|
||||
break;
|
||||
}
|
||||
|
||||
// use the given collection
|
||||
if (isset($data[$field]) && $data[$field] instanceof PersistentCollection) {
|
||||
|
||||
$data[$field]->setOwner($entity, $assoc);
|
||||
|
||||
$class->reflFields[$field]->setValue($entity, $data[$field]);
|
||||
$this->originalEntityData[$oid][$field] = $data[$field];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Inject collection
|
||||
$pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection);
|
||||
$pColl->setOwner($entity, $assoc);
|
||||
@ -2942,7 +2983,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*
|
||||
* @param string $entityName The name of the Entity.
|
||||
*
|
||||
* @return \Doctrine\ORM\Persisters\BasicEntityPersister
|
||||
* @return \Doctrine\ORM\Persisters\EntityPersister
|
||||
*/
|
||||
public function getEntityPersister($entityName)
|
||||
{
|
||||
@ -2954,21 +2995,28 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
switch (true) {
|
||||
case ($class->isInheritanceTypeNone()):
|
||||
$persister = new Persisters\BasicEntityPersister($this->em, $class);
|
||||
$persister = new BasicEntityPersister($this->em, $class);
|
||||
break;
|
||||
|
||||
case ($class->isInheritanceTypeSingleTable()):
|
||||
$persister = new Persisters\SingleTablePersister($this->em, $class);
|
||||
$persister = new SingleTablePersister($this->em, $class);
|
||||
break;
|
||||
|
||||
case ($class->isInheritanceTypeJoined()):
|
||||
$persister = new Persisters\JoinedSubclassPersister($this->em, $class);
|
||||
$persister = new JoinedSubclassPersister($this->em, $class);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \RuntimeException('No persister found for entity.');
|
||||
}
|
||||
|
||||
if ($this->hasCache && $class->cache !== null) {
|
||||
$persister = $this->em->getConfiguration()
|
||||
->getSecondLevelCacheConfiguration()
|
||||
->getCacheFactory()
|
||||
->buildCachedEntityPersister($this->em, $persister, $class);
|
||||
}
|
||||
|
||||
$this->persisters[$entityName] = $persister;
|
||||
|
||||
return $this->persisters[$entityName];
|
||||
@ -2979,29 +3027,32 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*
|
||||
* @param array $association
|
||||
*
|
||||
* @return \Doctrine\ORM\Persisters\AbstractCollectionPersister
|
||||
* @return \Doctrine\ORM\Persisters\CollectionPersister
|
||||
*/
|
||||
public function getCollectionPersister(array $association)
|
||||
{
|
||||
$type = $association['type'];
|
||||
$role = isset($association['cache'])
|
||||
? $association['sourceEntity'] . '::' . $association['fieldName']
|
||||
: $association['type'];
|
||||
|
||||
if (isset($this->collectionPersisters[$type])) {
|
||||
return $this->collectionPersisters[$type];
|
||||
if (isset($this->collectionPersisters[$role])) {
|
||||
return $this->collectionPersisters[$role];
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case ClassMetadata::ONE_TO_MANY:
|
||||
$persister = new Persisters\OneToManyPersister($this->em);
|
||||
break;
|
||||
$persister = ClassMetadata::ONE_TO_MANY === $association['type']
|
||||
? new OneToManyPersister($this->em)
|
||||
: new ManyToManyPersister($this->em);
|
||||
|
||||
case ClassMetadata::MANY_TO_MANY:
|
||||
$persister = new Persisters\ManyToManyPersister($this->em);
|
||||
break;
|
||||
if ($this->hasCache && isset($association['cache'])) {
|
||||
$persister = $this->em->getConfiguration()
|
||||
->getSecondLevelCacheConfiguration()
|
||||
->getCacheFactory()
|
||||
->buildCachedCollectionPersister($this->em, $persister, $association);
|
||||
}
|
||||
|
||||
$this->collectionPersisters[$type] = $persister;
|
||||
$this->collectionPersisters[$role] = $persister;
|
||||
|
||||
return $this->collectionPersisters[$type];
|
||||
return $this->collectionPersisters[$role];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3194,6 +3245,50 @@ class UnitOfWork implements PropertyChangedListener
|
||||
return isset($this->readOnlyObjects[spl_object_hash($object)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform whatever processing is encapsulated here after completion of the transaction.
|
||||
*/
|
||||
private function afterTransactionComplete()
|
||||
{
|
||||
if ( ! $this->hasCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->persisters as $persister) {
|
||||
if ($persister instanceof CachedPersister) {
|
||||
$persister->afterTransactionComplete();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->collectionPersisters as $persister) {
|
||||
if ($persister instanceof CachedPersister) {
|
||||
$persister->afterTransactionComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform whatever processing is encapsulated here after completion of the rolled-back.
|
||||
*/
|
||||
private function afterTransactionRolledBack()
|
||||
{
|
||||
if ( ! $this->hasCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->persisters as $persister) {
|
||||
if ($persister instanceof CachedPersister) {
|
||||
$persister->afterTransactionRolledBack();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->collectionPersisters as $persister) {
|
||||
if ($persister instanceof CachedPersister) {
|
||||
$persister->afterTransactionRolledBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function dispatchOnFlushEvent()
|
||||
{
|
||||
if ($this->evm->hasListeners(Events::onFlush)) {
|
||||
|
35
tests/Doctrine/Tests/EventListener/CacheMetadataListener.php
Normal file
35
tests/Doctrine/Tests/EventListener/CacheMetadataListener.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\EventListener;
|
||||
|
||||
use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
class CacheMetadataListener
|
||||
{
|
||||
/**
|
||||
* @param \Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs $event
|
||||
*/
|
||||
public function loadClassMetadata(LoadClassMetadataEventArgs $event)
|
||||
{
|
||||
$metadata = $event->getClassMetadata();
|
||||
$cache = array(
|
||||
'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE
|
||||
);
|
||||
|
||||
/** @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
|
||||
if (strstr($metadata->name, 'Doctrine\Tests\Models\Cache')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($metadata->isVersioned) {
|
||||
return;
|
||||
}
|
||||
|
||||
$metadata->enableCache($cache);
|
||||
|
||||
foreach ($metadata->associationMappings as $mapping) {
|
||||
$metadata->enableAssociationCache($mapping['fieldName'], $cache);
|
||||
}
|
||||
}
|
||||
}
|
13
tests/Doctrine/Tests/Mocks/CacheEntryMock.php
Normal file
13
tests/Doctrine/Tests/Mocks/CacheEntryMock.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Mocks;
|
||||
|
||||
use Doctrine\ORM\Cache\CacheEntry;
|
||||
|
||||
/**
|
||||
* Cache entry mock
|
||||
*/
|
||||
class CacheEntryMock extends \ArrayObject implements CacheEntry
|
||||
{
|
||||
|
||||
}
|
21
tests/Doctrine/Tests/Mocks/CacheKeyMock.php
Normal file
21
tests/Doctrine/Tests/Mocks/CacheKeyMock.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Mocks;
|
||||
|
||||
use Doctrine\ORM\Cache\CacheKey;
|
||||
|
||||
/**
|
||||
* Cache key mock
|
||||
*
|
||||
* Used to store/retrieve entries from a cache region
|
||||
*/
|
||||
class CacheKeyMock extends CacheKey
|
||||
{
|
||||
/**
|
||||
* @param string $hash The string hash that represend this cache key
|
||||
*/
|
||||
function __construct($hash)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
}
|
||||
}
|
115
tests/Doctrine/Tests/Mocks/CacheRegionMock.php
Normal file
115
tests/Doctrine/Tests/Mocks/CacheRegionMock.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Mocks;
|
||||
|
||||
use Doctrine\ORM\Cache\CacheEntry;
|
||||
use Doctrine\ORM\Cache\CacheKey;
|
||||
use Doctrine\ORM\Cache\Lock;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
|
||||
/**
|
||||
* Cache region mock
|
||||
*/
|
||||
class CacheRegionMock implements Region
|
||||
{
|
||||
public $calls = array();
|
||||
public $returns = array();
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Queue a return value for a specific method invocation
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function addReturn($method, $value)
|
||||
{
|
||||
$this->returns[$method][] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dequeue a value for a specific method invocation
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function getReturn($method, $default)
|
||||
{
|
||||
if (isset($this->returns[$method]) && ! empty($this->returns[$method])) {
|
||||
return array_shift($this->returns[$method]);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array();
|
||||
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function contains(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
return $this->getReturn(__FUNCTION__, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evict(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
return $this->getReturn(__FUNCTION__, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictAll()
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array();
|
||||
|
||||
return $this->getReturn(__FUNCTION__, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
return $this->getReturn(__FUNCTION__, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key, 'entry' => $entry);
|
||||
|
||||
return $this->getReturn(__FUNCTION__, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->calls = array();
|
||||
$this->returns = array();
|
||||
}
|
||||
}
|
202
tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php
Normal file
202
tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Mocks;
|
||||
|
||||
|
||||
use Doctrine\ORM\Cache\ConcurrentRegion;
|
||||
use Doctrine\ORM\Cache\LockException;
|
||||
use Doctrine\ORM\Cache\CacheEntry;
|
||||
use Doctrine\ORM\Cache\CacheKey;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Cache\Lock;
|
||||
|
||||
/**
|
||||
* Concurrent region mock
|
||||
*
|
||||
* Used to mock a ConcurrentRegion
|
||||
*/
|
||||
class ConcurrentRegionMock implements ConcurrentRegion
|
||||
{
|
||||
public $calls = array();
|
||||
public $exceptions = array();
|
||||
public $locks = array();
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
private $region;
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\Region $region
|
||||
*/
|
||||
public function __construct(Region $region)
|
||||
{
|
||||
$this->region = $region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dequeue an exception for a specific method invocation
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function throwException($method)
|
||||
{
|
||||
if (isset($this->exceptions[$method]) && ! empty($this->exceptions[$method])) {
|
||||
$exception = array_shift($this->exceptions[$method]);
|
||||
|
||||
if ($exception != null) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue an exception for the next method invocation
|
||||
*
|
||||
* @param string $method
|
||||
* @param \Exception $e
|
||||
*/
|
||||
public function addException($method, \Exception $e)
|
||||
{
|
||||
$this->exceptions[$method][] = $e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks a specific cache entry
|
||||
*
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key
|
||||
* @param \Doctrine\ORM\Cache\Lock $lock
|
||||
*/
|
||||
public function setLock(CacheKey $key, Lock $lock)
|
||||
{
|
||||
$this->locks[$key->hash] = $lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function contains(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
if (isset($this->locks[$key->hash])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
return $this->region->contains($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evict(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
return $this->region->evict($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictAll()
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array();
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
return $this->region->evictAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
if (isset($this->locks[$key->hash])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->region->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array();
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
return $this->region->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key, 'entry' => $entry);
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
if (isset($this->locks[$key->hash])) {
|
||||
|
||||
if ($lock !== null && $this->locks[$key->hash]->value === $lock->value) {
|
||||
return $this->region->put($key, $entry);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->region->put($key, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lock(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
if (isset($this->locks[$key->hash])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->locks[$key->hash] = Lock::createLockRead();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unlock(CacheKey $key, Lock $lock)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key, 'lock' => $lock);
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
if ( ! isset($this->locks[$key->hash])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->locks[$key->hash]->value !== $lock->value) {
|
||||
throw LockException::unexpectedLockValue($lock);
|
||||
}
|
||||
|
||||
unset($this->locks[$key->hash]);
|
||||
}
|
||||
}
|
19
tests/Doctrine/Tests/Mocks/TimestampRegionMock.php
Normal file
19
tests/Doctrine/Tests/Mocks/TimestampRegionMock.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Mocks;
|
||||
|
||||
use Doctrine\ORM\Cache\TimestampRegion;
|
||||
use Doctrine\ORM\Cache\CacheKey;
|
||||
|
||||
/**
|
||||
* Timestamp region mock
|
||||
*
|
||||
* Used to mock a TimestampRegion
|
||||
*/
|
||||
class TimestampRegionMock extends CacheRegionMock implements TimestampRegion
|
||||
{
|
||||
public function update(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
}
|
||||
}
|
95
tests/Doctrine/Tests/Models/Cache/Attraction.php
Normal file
95
tests/Doctrine/Tests/Models/Cache/Attraction.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @Entity
|
||||
* @Table("cache_attraction")
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorMap({
|
||||
* 1 = "Restaurant",
|
||||
* 2 = "Beach",
|
||||
* 3 = "Bar"
|
||||
* })
|
||||
*/
|
||||
abstract class Attraction
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @ManyToOne(targetEntity="City", inversedBy="attractions")
|
||||
* @JoinColumn(name="city_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $city;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @OneToMany(targetEntity="AttractionInfo", mappedBy="attraction")
|
||||
*/
|
||||
protected $infos;
|
||||
|
||||
public function __construct($name, City $city)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->city = $city;
|
||||
$this->infos = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getCity()
|
||||
{
|
||||
return $this->city;
|
||||
}
|
||||
|
||||
public function setCity(City $city)
|
||||
{
|
||||
$this->city = $city;
|
||||
}
|
||||
|
||||
public function getInfos()
|
||||
{
|
||||
return $this->infos;
|
||||
}
|
||||
|
||||
public function addInfo(AttractionInfo $info)
|
||||
{
|
||||
if ( ! $this->infos->contains($info)) {
|
||||
$this->infos->add($info);
|
||||
}
|
||||
}
|
||||
}
|
33
tests/Doctrine/Tests/Models/Cache/AttractionContactInfo.php
Normal file
33
tests/Doctrine/Tests/Models/Cache/AttractionContactInfo.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table("cache_attraction_contact_info")
|
||||
*/
|
||||
class AttractionContactInfo extends AttractionInfo
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $fone;
|
||||
|
||||
public function __construct($fone, Attraction $attraction)
|
||||
{
|
||||
$this->setAttraction($attraction);
|
||||
$this->setFone($fone);
|
||||
}
|
||||
|
||||
public function getFone()
|
||||
{
|
||||
return $this->fone;
|
||||
}
|
||||
|
||||
public function setFone($fone)
|
||||
{
|
||||
$this->fone = $fone;
|
||||
}
|
||||
}
|
54
tests/Doctrine/Tests/Models/Cache/AttractionInfo.php
Normal file
54
tests/Doctrine/Tests/Models/Cache/AttractionInfo.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @Entity
|
||||
* @Table("cache_attraction_info")
|
||||
* @InheritanceType("JOINED")
|
||||
* @DiscriminatorMap({
|
||||
* 1 = "AttractionContactInfo",
|
||||
* 2 = "AttractionLocationInfo",
|
||||
* })
|
||||
*/
|
||||
abstract class AttractionInfo
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @ManyToOne(targetEntity="Attraction", inversedBy="infos")
|
||||
* @JoinColumn(name="attraction_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $attraction;
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getAttraction()
|
||||
{
|
||||
return $this->attraction;
|
||||
}
|
||||
|
||||
public function setAttraction(Attraction $attraction)
|
||||
{
|
||||
$this->attraction = $attraction;
|
||||
|
||||
$attraction->addInfo($this);
|
||||
}
|
||||
}
|
33
tests/Doctrine/Tests/Models/Cache/AttractionLocationInfo.php
Normal file
33
tests/Doctrine/Tests/Models/Cache/AttractionLocationInfo.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table("cache_attraction_location_info")
|
||||
*/
|
||||
class AttractionLocationInfo extends AttractionInfo
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $address;
|
||||
|
||||
public function __construct($address, Attraction $attraction)
|
||||
{
|
||||
$this->setAttraction($attraction);
|
||||
$this->setAddress($address);
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
public function setAddress($address)
|
||||
{
|
||||
$this->address = $address;
|
||||
}
|
||||
}
|
11
tests/Doctrine/Tests/Models/Cache/Bar.php
Normal file
11
tests/Doctrine/Tests/Models/Cache/Bar.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Bar extends Attraction
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
}
|
11
tests/Doctrine/Tests/Models/Cache/Beach.php
Normal file
11
tests/Doctrine/Tests/Models/Cache/Beach.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Beach extends Attraction
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
}
|
109
tests/Doctrine/Tests/Models/Cache/City.php
Normal file
109
tests/Doctrine/Tests/Models/Cache/City.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @Entity
|
||||
* @Table("cache_city")
|
||||
*/
|
||||
class City
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @ManyToOne(targetEntity="State", inversedBy="cities")
|
||||
* @JoinColumn(name="state_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Travel", mappedBy="visitedCities")
|
||||
*/
|
||||
public $travels;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @OrderBy({"name" = "ASC"})
|
||||
* @OneToMany(targetEntity="Attraction", mappedBy="city")
|
||||
*/
|
||||
public $attractions;
|
||||
|
||||
public function __construct($name, State $state = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->state = $state;
|
||||
$this->travels = new ArrayCollection();
|
||||
$this->attractions = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getState()
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
public function setState(State $state)
|
||||
{
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
public function addTravel(Travel $travel)
|
||||
{
|
||||
$this->travels[] = $travel;
|
||||
}
|
||||
|
||||
public function getTravels()
|
||||
{
|
||||
return $this->travels;
|
||||
}
|
||||
|
||||
public function addAttraction(Attraction $attraction)
|
||||
{
|
||||
$this->attractions[] = $attraction;
|
||||
}
|
||||
|
||||
public function getAttractions()
|
||||
{
|
||||
return $this->attractions;
|
||||
}
|
||||
|
||||
public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
|
||||
{
|
||||
include __DIR__ . '/../../ORM/Mapping/php/Doctrine.Tests.Models.Cache.City.php';
|
||||
}
|
||||
}
|
50
tests/Doctrine/Tests/Models/Cache/Country.php
Normal file
50
tests/Doctrine/Tests/Models/Cache/Country.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @Entity
|
||||
* @Table("cache_country")
|
||||
*/
|
||||
class Country
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
65
tests/Doctrine/Tests/Models/Cache/Flight.php
Normal file
65
tests/Doctrine/Tests/Models/Cache/Flight.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table("cache_flight")
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
*/
|
||||
class Flight
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @Cache
|
||||
* @ManyToOne(targetEntity="City")
|
||||
* @JoinColumn(name="leaving_from_city_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $leavingFrom;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @Cache
|
||||
* @ManyToOne(targetEntity="City")
|
||||
* @JoinColumn(name="going_to_city_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $goingTo;
|
||||
|
||||
/**
|
||||
* @Column(type="date")
|
||||
*/
|
||||
protected $departure;
|
||||
|
||||
/**
|
||||
* @param \Doctrine\Tests\Models\Cache\City $leavingFrom
|
||||
* @param \Doctrine\Tests\Models\Cache\City $goingTo
|
||||
*/
|
||||
public function __construct(City $leavingFrom, City $goingTo)
|
||||
{
|
||||
$this->goingTo = $goingTo;
|
||||
$this->leavingFrom = $leavingFrom;
|
||||
$this->departure = new \DateTime();
|
||||
}
|
||||
|
||||
public function getLeavingFrom()
|
||||
{
|
||||
return $this->leavingFrom;
|
||||
}
|
||||
|
||||
public function getGoingTo()
|
||||
{
|
||||
return $this->goingTo;
|
||||
}
|
||||
|
||||
public function getDeparture()
|
||||
{
|
||||
return $this->departure;
|
||||
}
|
||||
|
||||
public function setDeparture($departure)
|
||||
{
|
||||
$this->departure = $departure;
|
||||
}
|
||||
}
|
11
tests/Doctrine/Tests/Models/Cache/Restaurant.php
Normal file
11
tests/Doctrine/Tests/Models/Cache/Restaurant.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Restaurant extends Attraction
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
}
|
92
tests/Doctrine/Tests/Models/Cache/State.php
Normal file
92
tests/Doctrine/Tests/Models/Cache/State.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table("cache_state")
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
*/
|
||||
class State
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @ManyToOne(targetEntity="Country")
|
||||
* @JoinColumn(name="country_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $country;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @OneToMany(targetEntity="City", mappedBy="state")
|
||||
*/
|
||||
protected $cities;
|
||||
|
||||
public function __construct($name, Country $country = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->country = $country;
|
||||
$this->cities = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getCountry()
|
||||
{
|
||||
return $this->country;
|
||||
}
|
||||
|
||||
public function setCountry(Country $country)
|
||||
{
|
||||
$this->country = $country;
|
||||
}
|
||||
|
||||
public function getCities()
|
||||
{
|
||||
return $this->cities;
|
||||
}
|
||||
|
||||
public function setCities(ArrayCollection $cities)
|
||||
{
|
||||
$this->cities = $cities;
|
||||
}
|
||||
|
||||
public function addCity(City $city)
|
||||
{
|
||||
$this->cities[] = $city;
|
||||
}
|
||||
}
|
112
tests/Doctrine/Tests/Models/Cache/Travel.php
Normal file
112
tests/Doctrine/Tests/Models/Cache/Travel.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @Entity
|
||||
* @Table("cache_travel")
|
||||
*/
|
||||
class Travel
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(type="date")
|
||||
*/
|
||||
protected $createdAt;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @ManyToOne(targetEntity="Traveler", inversedBy="travels")
|
||||
* @JoinColumn(name="traveler_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $traveler;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
*
|
||||
* @ManyToMany(targetEntity="City", inversedBy="travels", cascade={"persist", "remove"})
|
||||
* @JoinTable(name="cache_visited_cities",
|
||||
* joinColumns={
|
||||
* @JoinColumn(name="travel_id", referencedColumnName="id")
|
||||
* },
|
||||
* inverseJoinColumns={
|
||||
* @JoinColumn(name="city_id", referencedColumnName="id")
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public $visitedCities;
|
||||
|
||||
public function __construct(Traveler $traveler)
|
||||
{
|
||||
$this->traveler = $traveler;
|
||||
$this->createdAt = new \DateTime('now');
|
||||
$this->visitedCities = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\Tests\Models\Cache\Traveler
|
||||
*/
|
||||
public function getTraveler()
|
||||
{
|
||||
return $this->traveler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\Tests\Models\Cache\Traveler $traveler
|
||||
*/
|
||||
public function setTraveler(Traveler $traveler)
|
||||
{
|
||||
$this->traveler = $traveler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\Common\Collections\ArrayCollection
|
||||
*/
|
||||
public function getVisitedCities()
|
||||
{
|
||||
return $this->visitedCities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\Tests\Models\Cache\City $city
|
||||
*/
|
||||
public function addVisitedCity(City $city)
|
||||
{
|
||||
$this->visitedCities->add($city);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\Tests\Models\Cache\City $city
|
||||
*/
|
||||
public function removeVisitedCity(City $city)
|
||||
{
|
||||
$this->visitedCities->removeElement($city);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getCreatedAt()
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
}
|
91
tests/Doctrine/Tests/Models/Cache/Traveler.php
Normal file
91
tests/Doctrine/Tests/Models/Cache/Traveler.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\Cache;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @Entity
|
||||
* @Table("cache_traveler")
|
||||
*/
|
||||
class Traveler
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @Cache()
|
||||
* @OneToMany(targetEntity="Travel", mappedBy="traveler", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
*
|
||||
* @var \Doctrine\Common\Collections\Collection
|
||||
*/
|
||||
public $travels;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->travels = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getTravels()
|
||||
{
|
||||
return $this->travels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\Tests\Models\Cache\Travel $item
|
||||
*/
|
||||
public function addTravel(Travel $item)
|
||||
{
|
||||
if ( ! $this->travels->contains($item)) {
|
||||
$this->travels->add($item);
|
||||
}
|
||||
|
||||
if ($item->getTraveler() !== $this) {
|
||||
$item->setTraveler($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\Tests\Models\Cache\Travel $item
|
||||
*/
|
||||
public function removeTravel(Travel $item)
|
||||
{
|
||||
$this->travels->removeElement($item);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user