Second level cache
This commit is contained in:
parent
86ae6f18ab
commit
3140593e9b
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
|
||||
---------
|
||||
|
804
docs/en/reference/second-level-cache.rst
Normal file
804
docs/en/reference/second-level-cache.rst
Normal file
@ -0,0 +1,804 @@
|
||||
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 many 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.
|
||||
|
||||
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::
|
||||
|
||||
Notice that when caching collection and queries only identifiers are stored.
|
||||
The entity values will be stored in its own region
|
||||
|
||||
|
||||
.. _reference-second-level-cache-regions:
|
||||
|
||||
Cache Regions
|
||||
-------------
|
||||
|
||||
``Doctrine\ORM\Cache\Region\DefaultRegion`` Its 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.
|
||||
|
||||
|
||||
``Doctrine\ORM\Cache\Region``
|
||||
|
||||
Defines a contract for accessing a particular cache region.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
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
|
||||
*/
|
||||
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 The cached entry or NULL
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function evict(CacheKey $key);
|
||||
|
||||
/**
|
||||
* Remove all contents of this particular cache region.
|
||||
*/
|
||||
public function evictAll();
|
||||
}
|
||||
|
||||
|
||||
``Doctrine\ORM\Cache\ConcurrentRegion``
|
||||
|
||||
Defines contract for concurrently managed data region.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
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.
|
||||
*/
|
||||
public function readLock(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 readLock
|
||||
*/
|
||||
public function readUnlock(CacheKey $key, Lock $lock);
|
||||
}
|
||||
|
||||
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\Configuration */
|
||||
/* var $cache \Doctrine\Common\Cache */
|
||||
|
||||
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($config, $cache);
|
||||
|
||||
//Enable second-level-cache
|
||||
$config->setSecondLevelCacheEnabled();
|
||||
|
||||
//Cache factory
|
||||
$config->setSecondLevelCacheFactory($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
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
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
|
||||
* @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
|
||||
* @param array $mapping The association mapping
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\Persister\CachedCollectionPersister
|
||||
*/
|
||||
public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, $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 hidrator
|
||||
*
|
||||
* @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 hidrator.
|
||||
*/
|
||||
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata);
|
||||
|
||||
/**
|
||||
* Build a collection hidrator
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
|
||||
* @param array $mapping The association mapping.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CollectionHydrator The built collection hidrator.
|
||||
*/
|
||||
public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping);
|
||||
|
||||
/**
|
||||
* Gets a cache region based on its name.
|
||||
*
|
||||
* @param array $cache The cache configuration.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\Region The cache region.
|
||||
*/
|
||||
public function getRegion(array $cache);
|
||||
}
|
||||
|
||||
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 /*
|
||||
|
||||
//Cache Region lifetime
|
||||
$config->setSecondLevelCacheRegionLifetime('my_entity_region', 3600);
|
||||
$config->setSecondLevelCacheDefaultRegionLifetime(7200);
|
||||
|
||||
|
||||
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->setSecondLevelCacheLogger($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.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Log an entity put into second level cache.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param 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 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 \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 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 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 \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 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 \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 QueryCacheKey $key The cache key of the query.
|
||||
*/
|
||||
public function queryCacheMiss($regionName, QueryCacheKey $key);
|
||||
|
||||
|
||||
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``
|
||||
* ``region`` 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
|
||||
|
||||
$country = $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
|
||||
|
||||
$country = $em->find('Country', 1); // Retrieve item from cache
|
||||
|
||||
|
||||
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();
|
||||
|
||||
// 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 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('State', 1) // Check if the cache exists
|
||||
$cache->evictEntity('State', 1); // Remove an entity from cache
|
||||
$cache->evictEntityRegion('State'); // Remove all entities from cache
|
||||
|
||||
$cache->containsCollection('State', 'cities', 1); // Check if the cache exists
|
||||
$cache->evictCollection('State', 'cities', 1); // Remove an entity collection from cache
|
||||
$cache->evictCollectionRegion('State', 'cities'); // Remove all collections from cache
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
Composite primary key
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
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.
|
||||
|
||||
.. 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
|
||||
$id = array('source' => 1, 'target' => 2);
|
||||
$reference = $this->_em->find("Reference", $id);
|
||||
|
||||
// NOT Supported
|
||||
$id = array('source' => new Article(1), 'target' => new Article(2));
|
||||
$reference = $this->_em->find("Reference", $id);
|
||||
|
||||
|
||||
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.
|
||||
for more details about how to implement a cache region please see :ref:`reference-second-level-cache-regions`
|
@ -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="unbounded"/>
|
||||
<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="unbounded"/>
|
||||
<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="unbounded"/>
|
||||
<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="unbounded"/>
|
||||
<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="unbounded"/>
|
||||
<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"/>
|
||||
|
@ -23,8 +23,10 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
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,37 @@ abstract class AbstractQuery
|
||||
*/
|
||||
protected $_hydrationCacheProfile;
|
||||
|
||||
/**
|
||||
* Whether to use second level cache, if available.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $cacheable;
|
||||
|
||||
/**
|
||||
* Second level cache region name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheRegion;
|
||||
|
||||
/**
|
||||
* Second level query cache mode.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $cacheMode;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
|
||||
*/
|
||||
protected $cacheLogger;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $lifetime = 0;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
|
||||
*
|
||||
@ -127,8 +160,102 @@ abstract class AbstractQuery
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
$this->_em = $em;
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->_em = $em;
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->cacheLogger = $em->getConfiguration()->getSecondLevelCacheLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 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 = $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 anabled, FALSE otherwise.
|
||||
*/
|
||||
protected function isCacheEnabled()
|
||||
{
|
||||
return $this->cacheable && $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 = $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 = $cacheMode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -306,6 +433,16 @@ abstract class AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ResultSetMapping used for hydration.
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\ResultSetMapping
|
||||
*/
|
||||
public function getResultSetMapping()
|
||||
{
|
||||
return $this->_resultSetMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to translate entity namespaces to full qualified names.
|
||||
*
|
||||
@ -747,11 +884,10 @@ abstract class AbstractQuery
|
||||
$this->setParameters($parameters);
|
||||
}
|
||||
|
||||
$rsm = $this->_resultSetMapping ?: $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 +899,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 +957,52 @@ abstract class AbstractQuery
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll(
|
||||
$stmt, $this->_resultSetMapping, $this->_hints
|
||||
);
|
||||
$rsm = $this->_resultSetMapping ?: $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->_resultSetMapping ?: $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);
|
||||
|
||||
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);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey);
|
||||
}
|
||||
|
||||
if ($this->cacheLogger && $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 +1076,26 @@ 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()
|
||||
{
|
||||
$hints = $this->getHints();
|
||||
$query = $this->getSQL();
|
||||
$params = array();
|
||||
|
||||
foreach ($this->parameters as $parameter) {
|
||||
$value = $parameter->getValue();
|
||||
|
||||
$params[$parameter->getName()] = is_scalar($value) ? $value : $this->processParameterValue($value);
|
||||
}
|
||||
|
||||
ksort($hints);
|
||||
|
||||
return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
|
||||
}
|
||||
}
|
||||
|
185
lib/Doctrine/ORM/Cache.php
Normal file
185
lib/Doctrine/ORM/Cache.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?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';
|
||||
|
||||
/**
|
||||
* 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 session 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\ORMEntityManagerInterface $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 $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 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);
|
||||
}
|
32
lib/Doctrine/ORM/Cache/CacheEntry.php
Normal file
32
lib/Doctrine/ORM/Cache/CacheEntry.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;
|
||||
|
||||
/**
|
||||
* Cache entry interface
|
||||
*
|
||||
* @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));
|
||||
}
|
||||
}
|
95
lib/Doctrine/ORM/Cache/CacheFactory.php
Normal file
95
lib/Doctrine/ORM/Cache/CacheFactory.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @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 hidrator
|
||||
*
|
||||
* @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 hidrator.
|
||||
*/
|
||||
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata);
|
||||
|
||||
/**
|
||||
* Build a collection hidrator
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
|
||||
* @param array $mapping The association mapping.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CollectionHydrator The built collection hidrator.
|
||||
*/
|
||||
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);
|
||||
}
|
36
lib/Doctrine/ORM/Cache/CacheKey.php
Normal file
36
lib/Doctrine/ORM/Cache/CacheKey.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;
|
||||
|
||||
/**
|
||||
* Defines entity / collection 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
|
||||
{
|
||||
/**
|
||||
* @var string Unique identifier
|
||||
*/
|
||||
public $hash;
|
||||
}
|
51
lib/Doctrine/ORM/Cache/CollectionCacheEntry.php
Normal file
51
lib/Doctrine/ORM/Cache/CollectionCacheEntry.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;
|
||||
|
||||
/**
|
||||
* Collection cache entry
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class CollectionCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $identifiers;
|
||||
|
||||
/**
|
||||
* @param array $identifiers
|
||||
*/
|
||||
public function __construct(array $identifiers)
|
||||
{
|
||||
$this->identifiers = $identifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $values
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
return new self($values['identifiers']);
|
||||
}
|
||||
}
|
60
lib/Doctrine/ORM/Cache/CollectionCacheKey.php
Normal file
60
lib/Doctrine/ORM/Cache/CollectionCacheKey.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $ownerIdentifier;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $entityClass;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
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->entityClass = $entityClass;
|
||||
$this->association = $association;
|
||||
$this->ownerIdentifier = $ownerIdentifier;
|
||||
$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;
|
||||
|
||||
/**
|
||||
* Hidrator 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);
|
||||
}
|
346
lib/Doctrine/ORM/Cache/DefaultCache.php
Normal file
346
lib/Doctrine/ORM/Cache/DefaultCache.php
Normal file
@ -0,0 +1,346 @@
|
||||
<?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\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
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 array<\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()->getSecondLevelCacheFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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);
|
||||
$key = $this->buildEntityCacheKey($metadata, $identifier);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $persister->getCacheRegion()->contains($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictEntity($className, $identifier)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
|
||||
$key = $this->buildEntityCacheKey($metadata, $identifier);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$persister->getCacheRegion()->evict($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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));
|
||||
$key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $persister->getCacheRegion()->contains($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictCollection($className, $association, $ownerIdentifier)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
|
||||
$key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$persister->getCacheRegion()->evict($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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);
|
||||
}
|
||||
|
||||
}
|
202
lib/Doctrine/ORM/Cache/DefaultCacheFactory.php
Normal file
202
lib/Doctrine/ORM/Cache/DefaultCacheFactory.php
Normal file
@ -0,0 +1,202 @@
|
||||
<?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\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Cache\Region\DefaultRegion;
|
||||
use Doctrine\ORM\Cache\Region\FileLockRegion;
|
||||
use Doctrine\Common\Cache\Cache as CacheDriver;
|
||||
|
||||
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\Cache
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Configuration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $regions;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $fileLockRegionDirectory;
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Configuration $configuration
|
||||
* @param \Doctrine\Common\Cache\Cache $cache
|
||||
*/
|
||||
public function __construct(Configuration $configuration, CacheDriver $cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileLockRegionDirectory
|
||||
*/
|
||||
public function setFileLockRegionDirectory($fileLockRegionDirectory)
|
||||
{
|
||||
$this->fileLockRegionDirectory = $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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, array(
|
||||
'lifetime' => $this->configuration->getSecondLevelCacheRegionLifetime($cache['region'])
|
||||
));
|
||||
|
||||
if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
|
||||
|
||||
if ( ! $this->fileLockRegionDirectory) {
|
||||
throw new \RuntimeException(
|
||||
'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->configuration->getSecondLevelCacheLockLifetime());
|
||||
}
|
||||
|
||||
return $this->regions[$cache['region']] = $region;
|
||||
}
|
||||
}
|
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 hidrator 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;
|
||||
}
|
||||
}
|
151
lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php
Normal file
151
lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?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 hidrator 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 !
|
||||
// hande 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);
|
||||
}
|
||||
}
|
294
lib/Doctrine/ORM/Cache/DefaultQueryCache.php
Normal file
294
lib/Doctrine/ORM/Cache/DefaultQueryCache.php
Normal file
@ -0,0 +1,294 @@
|
||||
<?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 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)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->region = $region;
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->validator = $em->getConfiguration()->getSecondLevelCacheQueryValidator();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm)
|
||||
{
|
||||
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); //@TODO find root entity
|
||||
$hasRelation = ( ! empty($rsm->relationMap));
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
$region = $persister->getCacheRegion();
|
||||
|
||||
// @TODO - move to cache hydration componente
|
||||
foreach ($entry->result as $index => $entry) {
|
||||
|
||||
if (($entityEntry = $region->get(new EntityCacheKey($entityName, $entry['identifier']))) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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(new EntityCacheKey($assoc['targetEntity'], $assoc['identifier']))) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints);
|
||||
|
||||
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(new EntityCacheKey($assoc['targetEntity'], $assocId))) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$element = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints);
|
||||
|
||||
$collection->hydrateSet($assocIndex, $element);
|
||||
}
|
||||
|
||||
$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, array $result)
|
||||
{
|
||||
if ($rsm->scalarMappings) {
|
||||
throw new CacheException("Second level cache does not suport scalar results.");
|
||||
}
|
||||
|
||||
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); //@TODO find root entity
|
||||
$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 componente
|
||||
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;
|
||||
}
|
||||
}
|
58
lib/Doctrine/ORM/Cache/EntityCacheEntry.php
Normal file
58
lib/Doctrine/ORM/Cache/EntityCacheEntry.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;
|
||||
|
||||
/**
|
||||
* Entity cache entry
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class EntityCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $values
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
return new self($values['class'], $values['data']);
|
||||
}
|
||||
}
|
53
lib/Doctrine/ORM/Cache/EntityCacheKey.php
Normal file
53
lib/Doctrine/ORM/Cache/EntityCacheKey.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $identifier;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Hidrator 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);
|
||||
}
|
227
lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php
Normal file
227
lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php
Normal file
@ -0,0 +1,227 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $hidrator;
|
||||
|
||||
/**
|
||||
* @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 $mapping The association mapping.
|
||||
*/
|
||||
public function __construct(CollectionPersister $persister, Region $region, EntityManagerInterface $em, array $association)
|
||||
{
|
||||
$configuration = $em->getConfiguration();
|
||||
$cacheFactory = $configuration->getSecondLevelCacheFactory();
|
||||
|
||||
$this->region = $region;
|
||||
$this->persister = $persister;
|
||||
$this->association = $association;
|
||||
$this->regionName = $region->getName();
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->metadataFactory = $em->getMetadataFactory();
|
||||
$this->cacheLogger = $configuration->getSecondLevelCacheLogger();
|
||||
$this->hidrator = $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->hidrator->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();
|
||||
$targetHidrator = $targetPersister->getEntityHydrator();
|
||||
$entry = $this->hidrator->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 = $targetHidrator->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);
|
||||
}
|
||||
}
|
515
lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php
Normal file
515
lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php
Normal file
@ -0,0 +1,515 @@
|
||||
<?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\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\EntityHydrator
|
||||
*/
|
||||
protected $hidrator;
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
$config = $em->getConfiguration();
|
||||
$factory = $config->getSecondLevelCacheFactory();
|
||||
|
||||
$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 = $config->getSecondLevelCacheLogger();
|
||||
$this->hidrator = $factory->buildEntityHydrator($em, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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->hidrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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->hidrator->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
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
list($params) = $this->expandParameters($criteria);
|
||||
|
||||
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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)
|
||||
{
|
||||
//@TODO - Should throw exception ?
|
||||
if ($entity !== null || $assoc !== null || ! empty($hints) || $lockMode !== 0) {
|
||||
return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
|
||||
}
|
||||
|
||||
//handle only EntityRepository#findOneBy
|
||||
$query = $this->persister->getSelectSQL($criteria, null, 0, $limit, 0, $orderBy);
|
||||
$hash = $this->getHash($query, $criteria);
|
||||
$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 && $result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
if ($this->cacheLogger && $cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
$query = $this->persister->getSelectSQL($criteria, null, 0, $limit, $offset, $orderBy);
|
||||
$hash = $this->getHash($query, $criteria);
|
||||
$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 && $result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
if ($this->cacheLogger && $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->hidrator->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->hidrator->buildCacheEntry($class, $cacheKey, $entity);
|
||||
$cached = $this->region->put($cacheKey, $cacheEntry);
|
||||
|
||||
if ($this->cacheLogger && $cached) {
|
||||
$this->cacheLogger->entityCachePut($this->regionName, $cacheKey);
|
||||
}
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$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,105 @@
|
||||
<?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 odered 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,116 @@
|
||||
<?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()
|
||||
{
|
||||
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->hidrator->buildCacheEntry($class, $key, $entity);
|
||||
$cached = $this->region->put($key, $entry);
|
||||
|
||||
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->hidrator->buildCacheEntry($class, $key, $entity);
|
||||
$cached = $this->region->put($key, $entry);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
$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,140 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\ConcurrentRegion
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @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 $mapping 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,133 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\ConcurrentRegion
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @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()
|
||||
{
|
||||
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($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
|
||||
);
|
||||
}
|
||||
}
|
60
lib/Doctrine/ORM/Cache/QueryCache.php
Normal file
60
lib/Doctrine/ORM/Cache/QueryCache.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?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 array $result
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
|
||||
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm);
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
public function getRegion();
|
||||
}
|
58
lib/Doctrine/ORM/Cache/QueryCacheEntry.php
Normal file
58
lib/Doctrine/ORM/Cache/QueryCacheEntry.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;
|
||||
|
||||
/**
|
||||
* Query cache entry
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class QueryCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $result;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
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']);
|
||||
}
|
||||
}
|
52
lib/Doctrine/ORM/Cache/QueryCacheKey.php
Normal file
52
lib/Doctrine/ORM/Cache/QueryCacheKey.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?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 particular query.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class QueryCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $lifetime;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $cacheMode;
|
||||
|
||||
/**
|
||||
* @param string $hash Result cache id
|
||||
* @param integer $lifetime Query lifetime
|
||||
* @param integer $cacheMode Query cache mode
|
||||
*/
|
||||
public function __construct($hash, $lifetime, $cacheMode = 3)
|
||||
{
|
||||
$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\QueryCacheEntry $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 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();
|
||||
}
|
126
lib/Doctrine/ORM/Cache/Region/DefaultRegion.php
Normal file
126
lib/Doctrine/ORM/Cache/Region/DefaultRegion.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?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
|
||||
{
|
||||
const ENTRY_KEY = '_entry_';
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Cache\CacheProvider
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $lifetime = 0;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param \Doctrine\Common\Cache\CacheProvider $cache
|
||||
* @param array $configuration
|
||||
*/
|
||||
public function __construct($name, CacheProvider $cache, array $configuration = array())
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->cache = $cache;
|
||||
|
||||
$this->cache->setNamespace($this->name);
|
||||
|
||||
if (isset($configuration['lifetime']) && $configuration['lifetime'] > 0) {
|
||||
$this->lifetime = (integer) $configuration['lifetime'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\Common\Cache\Cache
|
||||
*/
|
||||
public function getCache()
|
||||
{
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function contains(CacheKey $key)
|
||||
{
|
||||
return $this->cache->contains($this->name . self::ENTRY_KEY . $key->hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
return $this->cache->fetch($this->name . self::ENTRY_KEY . $key->hash) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
|
||||
{
|
||||
return $this->cache->save($this->name . self::ENTRY_KEY . $key->hash, $entry, $this->lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evict(CacheKey $key)
|
||||
{
|
||||
return $this->cache->delete($this->name . self::ENTRY_KEY . $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 isLoked(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->isLoked($key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->region->contains($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
if ($this->isLoked($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->region->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
|
||||
{
|
||||
if ($this->isLoked($key, $lock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->region->put($key, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function evict(CacheKey $key)
|
||||
{
|
||||
if ($this->isLoked($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->isLoked($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->isLoked($key, $lock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! @unlink($this->getLockFileName($key))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
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();
|
||||
}
|
||||
}
|
@ -35,6 +35,10 @@ use Doctrine\ORM\Mapping\NamingStrategy;
|
||||
use Doctrine\ORM\Mapping\QuoteStrategy;
|
||||
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
|
||||
use Doctrine\ORM\Repository\RepositoryFactory;
|
||||
use Doctrine\ORM\Cache\CacheFactory;
|
||||
use Doctrine\ORM\Cache\Logging\CacheLogger;
|
||||
use Doctrine\ORM\Cache\QueryCacheValidator;
|
||||
use Doctrine\ORM\Cache\TimestampQueryCacheValidator;
|
||||
|
||||
/**
|
||||
* Configuration container for all configuration options of Doctrine.
|
||||
@ -233,6 +237,141 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isSecondLevelCacheEnabled()
|
||||
{
|
||||
return isset($this->_attributes['isSecondLevelCacheEnabled'])
|
||||
? $this->_attributes['isSecondLevelCacheEnabled']
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $flag
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setSecondLevelCacheEnabled($flag = true)
|
||||
{
|
||||
$this->_attributes['isSecondLevelCacheEnabled'] = (boolean) $flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\CacheFactory|null
|
||||
*/
|
||||
public function getSecondLevelCacheFactory()
|
||||
{
|
||||
return isset($this->_attributes['secondLevelCacheFactory'])
|
||||
? $this->_attributes['secondLevelCacheFactory']
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\CacheFactory $factory
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setSecondLevelCacheFactory(CacheFactory $factory)
|
||||
{
|
||||
$this->_attributes['secondLevelCacheFactory'] = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getSecondLevelCacheRegionLifetime($name)
|
||||
{
|
||||
if (isset($this->_attributes['secondLevelCacheRegionLifetime'][$name])) {
|
||||
return $this->_attributes['secondLevelCacheRegionLifetime'][$name];
|
||||
}
|
||||
|
||||
return $this->getSecondLevelCacheDefaultRegionLifetime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param integer $lifetime
|
||||
*/
|
||||
public function setSecondLevelCacheRegionLifetime($name, $lifetime)
|
||||
{
|
||||
$this->_attributes['secondLevelCacheRegionLifetime'][$name] = (integer) $lifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getSecondLevelCacheDefaultRegionLifetime()
|
||||
{
|
||||
return isset($this->_attributes['secondLevelCacheDefaultRegionLifetime'])
|
||||
? $this->_attributes['secondLevelCacheDefaultRegionLifetime']
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $lifetime
|
||||
*/
|
||||
public function setSecondLevelCacheDefaultRegionLifetime($lifetime)
|
||||
{
|
||||
$this->_attributes['secondLevelCacheDefaultRegionLifetime'] = (integer) $lifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $lifetime
|
||||
*/
|
||||
public function setSecondLevelCacheLockLifetime($lifetime)
|
||||
{
|
||||
$this->_attributes['secondLevelCacheLockLifetime'] = (integer) $lifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getSecondLevelCacheLockLifetime()
|
||||
{
|
||||
return isset($this->_attributes['secondLevelCacheLockLifetime'])
|
||||
? $this->_attributes['secondLevelCacheLockLifetime']
|
||||
: 60;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\Logging\CacheLogger|null
|
||||
*/
|
||||
public function getSecondLevelCacheLogger()
|
||||
{
|
||||
return isset($this->_attributes['secondLevelCacheLogger'])
|
||||
? $this->_attributes['secondLevelCacheLogger']
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\Logging\CacheLogger $logger
|
||||
*/
|
||||
public function setSecondLevelCacheLogger(CacheLogger $logger)
|
||||
{
|
||||
$this->_attributes['secondLevelCacheLogger'] = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\QueryCacheValidator
|
||||
*/
|
||||
public function getSecondLevelCacheQueryValidator()
|
||||
{
|
||||
return isset($this->_attributes['secondLevelCacheQueryValidator'])
|
||||
? $this->_attributes['secondLevelCacheQueryValidator']
|
||||
: $this->_attributes['secondLevelCacheQueryValidator'] = new TimestampQueryCacheValidator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheValidator $validator
|
||||
*/
|
||||
public function setSecondLevelCacheQueryValidator(QueryCacheValidator $validator)
|
||||
{
|
||||
$this->_attributes['secondLevelCacheQueryValidator'] = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache driver implementation that is used for the query cache (SQL cache).
|
||||
*
|
||||
@ -696,6 +835,38 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
: 'Doctrine\ORM\EntityRepository';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.5
|
||||
*
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException If not is a \Doctrine\ORM\Cache
|
||||
*/
|
||||
public function setSecondLevelCacheClassName($className)
|
||||
{
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
|
||||
if ( ! $reflectionClass->implementsInterface('Doctrine\ORM\Cache')) {
|
||||
throw ORMException::invalidSecondLevelCache($className);
|
||||
}
|
||||
|
||||
$this->_attributes['secondLevelCacheClassName'] = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.5
|
||||
*
|
||||
* @return string A \Doctrine\ORM\Cache implementation
|
||||
*/
|
||||
public function getSecondLevelCacheClassName()
|
||||
{
|
||||
return isset($this->_attributes['secondLevelCacheClassName'])
|
||||
? $this->_attributes['secondLevelCacheClassName']
|
||||
: 'Doctrine\ORM\Cache\DefaultCache';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets naming strategy.
|
||||
*
|
||||
|
@ -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->getSecondLevelCacheClassName();
|
||||
$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 anabled.
|
||||
*
|
||||
* @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")
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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,44 @@ 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 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,16 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
$metadata->setPrimaryTable($primaryTable);
|
||||
}
|
||||
|
||||
// Evaluate @Cache annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\Cache'])) {
|
||||
$cacheAnnot = $classAnnotations['Doctrine\ORM\Mapping\Cache'];
|
||||
|
||||
$metadata->enableCache(array(
|
||||
'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
|
||||
'region' => $cacheAnnot->region,
|
||||
));
|
||||
}
|
||||
|
||||
// Evaluate NamedNativeQueries annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries'])) {
|
||||
$namedNativeQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries'];
|
||||
@ -365,6 +375,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,22 @@ class XmlDriver extends FileDriver
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SimpleXMLElement $cacheMapping
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function cacheToArray(SimpleXMLElement $cacheMapping)
|
||||
{
|
||||
$region = isset($cacheMapping['region']) ? (string)$cacheMapping['region'] : null;
|
||||
$usage = isset($cacheMapping['usage']) ? constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . strtoupper($cacheMapping['usage'])) : null;
|
||||
|
||||
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,22 @@ class YamlDriver extends FileDriver
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $cacheMapping
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function cacheToArray($cacheMapping)
|
||||
{
|
||||
$region = isset($cacheMapping['region']) ? (string)$cacheMapping['region'] : null;
|
||||
$usage = isset($cacheMapping['usage']) ? constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . strtoupper($cacheMapping['usage'])) : null;
|
||||
|
||||
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
|
||||
*/
|
||||
static public 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)
|
||||
{
|
||||
|
141
lib/Doctrine/ORM/Persisters/CollectionPersister.php
Normal file
141
lib/Doctrine/ORM/Persisters/CollectionPersister.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?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
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
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);
|
||||
}
|
300
lib/Doctrine/ORM/Persisters/EntityPersister.php
Normal file
300
lib/Doctrine/ORM/Persisters/EntityPersister.php
Normal file
@ -0,0 +1,300 @@
|
||||
<?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,11 @@ final class Query extends AbstractQuery
|
||||
*/
|
||||
const HINT_REFRESH = 'doctrine.refresh';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
|
||||
|
||||
/**
|
||||
* Internal hint: is set to the proxy entity that is currently triggered for loading
|
||||
*
|
||||
@ -178,16 +181,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 +207,19 @@ final class Query extends AbstractQuery
|
||||
return $parser->getAST();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public 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.
|
||||
*
|
||||
@ -303,13 +309,14 @@ final class Query extends AbstractQuery
|
||||
foreach ($this->parameters as $parameter) {
|
||||
$key = $parameter->getName();
|
||||
$value = $parameter->getValue();
|
||||
$rsm = $this->_resultSetMapping ?: $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 +662,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.
|
||||
*
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
}
|
||||
@ -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,27 @@ 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()
|
||||
->getSecondLevelCacheFactory()
|
||||
->buildCachedEntityPersister($this->em, $persister, $class);
|
||||
}
|
||||
|
||||
$this->persisters[$entityName] = $persister;
|
||||
|
||||
return $this->persisters[$entityName];
|
||||
@ -2979,29 +3026,31 @@ 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()
|
||||
->getSecondLevelCacheFactory()
|
||||
->buildCachedCollectionPersister($this->em, $persister, $association);
|
||||
}
|
||||
|
||||
$this->collectionPersisters[$type] = $persister;
|
||||
$this->collectionPersisters[$role] = $persister;
|
||||
|
||||
return $this->collectionPersisters[$type];
|
||||
return $this->collectionPersisters[$role];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3194,6 +3243,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);
|
||||
}
|
||||
}
|
||||
}
|
10
tests/Doctrine/Tests/Mocks/CacheEntryMock.php
Normal file
10
tests/Doctrine/Tests/Mocks/CacheEntryMock.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Mocks;
|
||||
|
||||
use Doctrine\ORM\Cache\CacheEntry;
|
||||
|
||||
class CacheEntryMock extends \ArrayObject implements CacheEntry
|
||||
{
|
||||
|
||||
}
|
14
tests/Doctrine/Tests/Mocks/CacheKeyMock.php
Normal file
14
tests/Doctrine/Tests/Mocks/CacheKeyMock.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Mocks;
|
||||
|
||||
use Doctrine\ORM\Cache\CacheKey;
|
||||
|
||||
class CacheKeyMock extends CacheKey
|
||||
{
|
||||
|
||||
function __construct($hash)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
}
|
||||
}
|
77
tests/Doctrine/Tests/Mocks/CacheRegionMock.php
Normal file
77
tests/Doctrine/Tests/Mocks/CacheRegionMock.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?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;
|
||||
|
||||
class CacheRegionMock implements Region
|
||||
{
|
||||
public $calls = array();
|
||||
public $returns = array();
|
||||
public $name;
|
||||
|
||||
public function addReturn($method, $value)
|
||||
{
|
||||
$this->returns[$method][] = $value;
|
||||
}
|
||||
|
||||
public function getReturn($method, $datault)
|
||||
{
|
||||
if (isset($this->returns[$method]) && ! empty($this->returns[$method])) {
|
||||
return array_shift($this->returns[$method]);
|
||||
}
|
||||
|
||||
return $datault;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array();
|
||||
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function contains(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
return $this->getReturn(__FUNCTION__, false);
|
||||
}
|
||||
|
||||
public function evict(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
return $this->getReturn(__FUNCTION__, true);
|
||||
}
|
||||
|
||||
public function evictAll()
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array();
|
||||
|
||||
return $this->getReturn(__FUNCTION__, true);
|
||||
}
|
||||
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
return $this->getReturn(__FUNCTION__, null);
|
||||
}
|
||||
|
||||
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key, 'entry' => $entry);
|
||||
|
||||
return $this->getReturn(__FUNCTION__, true);
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$this->calls = array();
|
||||
$this->returns = array();
|
||||
}
|
||||
}
|
150
tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php
Normal file
150
tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?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;
|
||||
|
||||
class ConcurrentRegionMock implements ConcurrentRegion
|
||||
{
|
||||
public $calls = array();
|
||||
public $exceptions = array();
|
||||
public $locks = array();
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
private $region;
|
||||
|
||||
public function __construct(Region $region)
|
||||
{
|
||||
$this->region = $region;
|
||||
}
|
||||
|
||||
private function throwException($method)
|
||||
{
|
||||
if (isset($this->exceptions[$method]) && ! empty($this->exceptions[$method])) {
|
||||
$exception = array_shift($this->exceptions[$method]);
|
||||
|
||||
if($exception != null) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addException($method, \Exception $e)
|
||||
{
|
||||
$this->exceptions[$method][] = $e;
|
||||
}
|
||||
|
||||
public function setLock(CacheKey $key, Lock $lock)
|
||||
{
|
||||
$this->locks[$key->hash] = $lock;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public function evict(CacheKey $key)
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array('key' => $key);
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
return $this->region->evict($key);
|
||||
}
|
||||
|
||||
public function evictAll()
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array();
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
return $this->region->evictAll();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$this->calls[__FUNCTION__][] = array();
|
||||
|
||||
$this->throwException(__FUNCTION__);
|
||||
|
||||
return $this->region->getName();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
}
|
85
tests/Doctrine/Tests/ORM/Cache/AbstractRegionTest.php
Normal file
85
tests/Doctrine/Tests/ORM/Cache/AbstractRegionTest.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache;
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use Doctrine\Tests\Mocks\CacheEntryMock;
|
||||
use Doctrine\Tests\Mocks\CacheKeyMock;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
abstract class AbstractRegionTest extends OrmFunctionalTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Cache\ArrayCache
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->cache = new ArrayCache();
|
||||
$this->region = $this->createRegion();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
protected abstract function createRegion();
|
||||
|
||||
static public function dataProviderCacheValues()
|
||||
{
|
||||
return array(
|
||||
array(new CacheKeyMock('key.1'), new CacheEntryMock(array('id'=>1, 'name' => 'bar'))),
|
||||
array(new CacheKeyMock('key.2'), new CacheEntryMock(array('id'=>2, 'name' => 'foo'))),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderCacheValues
|
||||
*/
|
||||
public function testPutGetContainsEvict($key, $value)
|
||||
{
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
|
||||
$this->region->put($key, $value);
|
||||
|
||||
$this->assertTrue($this->region->contains($key));
|
||||
|
||||
$actual = $this->region->get($key);
|
||||
|
||||
$this->assertEquals($value, $actual);
|
||||
|
||||
$this->region->evict($key);
|
||||
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
}
|
||||
|
||||
public function testEvictAll()
|
||||
{
|
||||
$key1 = new CacheKeyMock('key.1');
|
||||
$key2 = new CacheKeyMock('key.2');
|
||||
|
||||
$this->assertFalse($this->region->contains($key1));
|
||||
$this->assertFalse($this->region->contains($key2));
|
||||
|
||||
$this->region->put($key1, new CacheEntryMock(array('value' => 'foo')));
|
||||
$this->region->put($key2, new CacheEntryMock(array('value' => 'bar')));
|
||||
|
||||
$this->assertTrue($this->region->contains($key1));
|
||||
$this->assertTrue($this->region->contains($key2));
|
||||
|
||||
$this->region->evictAll();
|
||||
|
||||
$this->assertFalse($this->region->contains($key1));
|
||||
$this->assertFalse($this->region->contains($key2));
|
||||
}
|
||||
}
|
68
tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php
Normal file
68
tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
class CacheKeyTest extends \Doctrine\Tests\DoctrineTestCase
|
||||
{
|
||||
public function testEntityCacheKeyIdentifierCollision()
|
||||
{
|
||||
$key1 = new EntityCacheKey('Foo', array('id'=>1));
|
||||
$key2 = new EntityCacheKey('Bar', array('id'=>1));
|
||||
|
||||
$this->assertNotEquals($key1->hash, $key2->hash);
|
||||
}
|
||||
|
||||
public function testEntityCacheKeyIdentifierType()
|
||||
{
|
||||
$key1 = new EntityCacheKey('Foo', array('id'=>1));
|
||||
$key2 = new EntityCacheKey('Foo', array('id'=>'1'));
|
||||
|
||||
$this->assertEquals($key1->hash, $key2->hash);
|
||||
}
|
||||
|
||||
public function testEntityCacheKeyIdentifierOrder()
|
||||
{
|
||||
$key1 = new EntityCacheKey('Foo', array('foo_bar'=>1, 'bar_foo'=> 2));
|
||||
$key2 = new EntityCacheKey('Foo', array('bar_foo'=>2, 'foo_bar'=> 1));
|
||||
|
||||
$this->assertEquals($key1->hash, $key2->hash);
|
||||
}
|
||||
|
||||
public function testCollectionCacheKeyIdentifierType()
|
||||
{
|
||||
$key1 = new CollectionCacheKey('Foo', 'assoc', array('id'=>1));
|
||||
$key2 = new CollectionCacheKey('Foo', 'assoc', array('id'=>'1'));
|
||||
|
||||
$this->assertEquals($key1->hash, $key2->hash);
|
||||
}
|
||||
|
||||
public function testCollectionCacheKeyIdentifierOrder()
|
||||
{
|
||||
$key1 = new CollectionCacheKey('Foo', 'assoc', array('foo_bar'=>1, 'bar_foo'=> 2));
|
||||
$key2 = new CollectionCacheKey('Foo', 'assoc', array('bar_foo'=>2, 'foo_bar'=> 1));
|
||||
|
||||
$this->assertEquals($key1->hash, $key2->hash);
|
||||
}
|
||||
|
||||
public function testCollectionCacheKeyIdentifierCollision()
|
||||
{
|
||||
$key1 = new CollectionCacheKey('Foo', 'assoc', array('id'=>1));
|
||||
$key2 = new CollectionCacheKey('Bar', 'assoc', array('id'=>1));
|
||||
|
||||
$this->assertNotEquals($key1->hash, $key2->hash);
|
||||
}
|
||||
|
||||
public function testCollectionCacheKeyAssociationCollision()
|
||||
{
|
||||
$key1 = new CollectionCacheKey('Foo', 'assoc1', array('id'=>1));
|
||||
$key2 = new CollectionCacheKey('Foo', 'assoc2', array('id'=>1));
|
||||
|
||||
$this->assertNotEquals($key1->hash, $key2->hash);
|
||||
}
|
||||
}
|
262
tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php
Normal file
262
tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php
Normal file
@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache;
|
||||
|
||||
use \Doctrine\Tests\OrmTestCase;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Cache\DefaultCacheFactory;
|
||||
use Doctrine\ORM\Cache\Region\DefaultRegion;
|
||||
use Doctrine\Tests\Mocks\ConcurrentRegionMock;
|
||||
use Doctrine\ORM\Persisters\BasicEntityPersister;
|
||||
use Doctrine\ORM\Persisters\OneToManyPersister;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
class DefaultCacheFactoryTest extends OrmTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\CacheFactory
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->enableSecondLevelCache();
|
||||
parent::setUp();
|
||||
|
||||
$this->em = $this->_getTestEntityManager();
|
||||
|
||||
|
||||
$arguments = array($this->em->getConfiguration(), $this->getSharedSecondLevelCacheDriverImpl());
|
||||
$this->factory = $this->getMock('\Doctrine\ORM\Cache\DefaultCacheFactory', array(
|
||||
'getRegion'
|
||||
), $arguments);
|
||||
}
|
||||
|
||||
public function testInplementsCacheFactory()
|
||||
{
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\CacheFactory', $this->factory);
|
||||
}
|
||||
|
||||
public function testBuildCachedEntityPersisterReadOnly()
|
||||
{
|
||||
$em = $this->em;
|
||||
$entityName = 'Doctrine\Tests\Models\Cache\State';
|
||||
$metadata = clone $em->getClassMetadata($entityName);
|
||||
$persister = new BasicEntityPersister($em, $metadata);
|
||||
$region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl()));
|
||||
|
||||
$metadata->cache['usage'] = ClassMetadata::CACHE_USAGE_READ_ONLY;
|
||||
|
||||
$this->factory->expects($this->once())
|
||||
->method('getRegion')
|
||||
->with($this->equalTo($metadata->cache))
|
||||
->will($this->returnValue($region));
|
||||
|
||||
|
||||
$cachedPersister = $this->factory->buildCachedEntityPersister($em, $persister, $metadata);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\ReadOnlyCachedEntityPersister', $cachedPersister);
|
||||
}
|
||||
|
||||
public function testBuildCachedEntityPersisterReadWrite()
|
||||
{
|
||||
$em = $this->em;
|
||||
$entityName = 'Doctrine\Tests\Models\Cache\State';
|
||||
$metadata = clone $em->getClassMetadata($entityName);
|
||||
$persister = new BasicEntityPersister($em, $metadata);
|
||||
$region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl()));
|
||||
|
||||
$metadata->cache['usage'] = ClassMetadata::CACHE_USAGE_READ_WRITE;
|
||||
|
||||
$this->factory->expects($this->once())
|
||||
->method('getRegion')
|
||||
->with($this->equalTo($metadata->cache))
|
||||
->will($this->returnValue($region));
|
||||
|
||||
$cachedPersister = $this->factory->buildCachedEntityPersister($em, $persister, $metadata);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', $cachedPersister);
|
||||
}
|
||||
|
||||
public function testBuildCachedEntityPersisterNonStrictReadWrite()
|
||||
{
|
||||
$em = $this->em;
|
||||
$entityName = 'Doctrine\Tests\Models\Cache\State';
|
||||
$metadata = clone $em->getClassMetadata($entityName);
|
||||
$persister = new BasicEntityPersister($em, $metadata);
|
||||
$region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl()));
|
||||
|
||||
$metadata->cache['usage'] = ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE;
|
||||
|
||||
$this->factory->expects($this->once())
|
||||
->method('getRegion')
|
||||
->with($this->equalTo($metadata->cache))
|
||||
->will($this->returnValue($region));
|
||||
|
||||
$cachedPersister = $this->factory->buildCachedEntityPersister($em, $persister, $metadata);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedEntityPersister', $cachedPersister);
|
||||
}
|
||||
|
||||
public function testBuildCachedCollectionPersisterReadOnly()
|
||||
{
|
||||
$em = $this->em;
|
||||
$entityName = 'Doctrine\Tests\Models\Cache\State';
|
||||
$metadata = $em->getClassMetadata($entityName);
|
||||
$mapping = $metadata->associationMappings['cities'];
|
||||
$persister = new OneToManyPersister($em);
|
||||
$region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl()));
|
||||
|
||||
$mapping['cache']['usage'] = ClassMetadata::CACHE_USAGE_READ_ONLY;
|
||||
|
||||
$this->factory->expects($this->once())
|
||||
->method('getRegion')
|
||||
->with($this->equalTo($mapping['cache']))
|
||||
->will($this->returnValue($region));
|
||||
|
||||
|
||||
$cachedPersister = $this->factory->buildCachedCollectionPersister($em, $persister, $mapping);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedCollectionPersister', $cachedPersister);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\ReadOnlyCachedCollectionPersister', $cachedPersister);
|
||||
}
|
||||
|
||||
public function testBuildCachedCollectionPersisterReadWrite()
|
||||
{
|
||||
$em = $this->em;
|
||||
$entityName = 'Doctrine\Tests\Models\Cache\State';
|
||||
$metadata = $em->getClassMetadata($entityName);
|
||||
$mapping = $metadata->associationMappings['cities'];
|
||||
$persister = new OneToManyPersister($em);
|
||||
$region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl()));
|
||||
|
||||
$mapping['cache']['usage'] = ClassMetadata::CACHE_USAGE_READ_WRITE;
|
||||
|
||||
$this->factory->expects($this->once())
|
||||
->method('getRegion')
|
||||
->with($this->equalTo($mapping['cache']))
|
||||
->will($this->returnValue($region));
|
||||
|
||||
$cachedPersister = $this->factory->buildCachedCollectionPersister($em, $persister, $mapping);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedCollectionPersister', $cachedPersister);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister', $cachedPersister);
|
||||
}
|
||||
|
||||
public function testBuildCachedCollectionPersisterNonStrictReadWrite()
|
||||
{
|
||||
$em = $this->em;
|
||||
$entityName = 'Doctrine\Tests\Models\Cache\State';
|
||||
$metadata = $em->getClassMetadata($entityName);
|
||||
$mapping = $metadata->associationMappings['cities'];
|
||||
$persister = new OneToManyPersister($em);
|
||||
$region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl()));
|
||||
|
||||
$mapping['cache']['usage'] = ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE;
|
||||
|
||||
$this->factory->expects($this->once())
|
||||
->method('getRegion')
|
||||
->with($this->equalTo($mapping['cache']))
|
||||
->will($this->returnValue($region));
|
||||
|
||||
$cachedPersister = $this->factory->buildCachedCollectionPersister($em, $persister, $mapping);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedCollectionPersister', $cachedPersister);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedCollectionPersister', $cachedPersister);
|
||||
}
|
||||
|
||||
public function testInheritedEntityCacheRegion()
|
||||
{
|
||||
$em = $this->em;
|
||||
$metadata1 = clone $em->getClassMetadata('Doctrine\Tests\Models\Cache\AttractionContactInfo');
|
||||
$metadata2 = clone $em->getClassMetadata('Doctrine\Tests\Models\Cache\AttractionLocationInfo');
|
||||
$persister1 = new BasicEntityPersister($em, $metadata1);
|
||||
$persister2 = new BasicEntityPersister($em, $metadata2);
|
||||
$factory = new DefaultCacheFactory($this->em->getConfiguration(), $this->getSharedSecondLevelCacheDriverImpl());
|
||||
|
||||
$cachedPersister1 = $factory->buildCachedEntityPersister($em, $persister1, $metadata1);
|
||||
$cachedPersister2 = $factory->buildCachedEntityPersister($em, $persister2, $metadata2);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister1);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister2);
|
||||
|
||||
$this->assertNotSame($cachedPersister1, $cachedPersister2);
|
||||
$this->assertSame($cachedPersister1->getCacheRegion(), $cachedPersister2->getCacheRegion());
|
||||
}
|
||||
|
||||
public function testCreateNewCacheDriver()
|
||||
{
|
||||
$em = $this->em;
|
||||
$metadata1 = clone $em->getClassMetadata('Doctrine\Tests\Models\Cache\State');
|
||||
$metadata2 = clone $em->getClassMetadata('Doctrine\Tests\Models\Cache\City');
|
||||
$persister1 = new BasicEntityPersister($em, $metadata1);
|
||||
$persister2 = new BasicEntityPersister($em, $metadata2);
|
||||
$factory = new DefaultCacheFactory($this->em->getConfiguration(), $this->getSharedSecondLevelCacheDriverImpl());
|
||||
|
||||
$cachedPersister1 = $factory->buildCachedEntityPersister($em, $persister1, $metadata1);
|
||||
$cachedPersister2 = $factory->buildCachedEntityPersister($em, $persister2, $metadata2);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister1);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister2);
|
||||
|
||||
$this->assertNotSame($cachedPersister1, $cachedPersister2);
|
||||
$this->assertNotSame($cachedPersister1->getCacheRegion(), $cachedPersister2->getCacheRegion());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
* @expectedExceptionMessage Unrecognized access strategy type [-1]
|
||||
*/
|
||||
public function testBuildCachedEntityPersisterNonStrictException()
|
||||
{
|
||||
$em = $this->em;
|
||||
$entityName = 'Doctrine\Tests\Models\Cache\State';
|
||||
$metadata = clone $em->getClassMetadata($entityName);
|
||||
$persister = new BasicEntityPersister($em, $metadata);
|
||||
|
||||
$metadata->cache['usage'] = -1;
|
||||
|
||||
$this->factory->buildCachedEntityPersister($em, $persister, $metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
* @expectedExceptionMessage Unrecognized access strategy type [-1]
|
||||
*/
|
||||
public function testBuildCachedCollectionPersisterException()
|
||||
{
|
||||
$em = $this->em;
|
||||
$entityName = 'Doctrine\Tests\Models\Cache\State';
|
||||
$metadata = $em->getClassMetadata($entityName);
|
||||
$mapping = $metadata->associationMappings['cities'];
|
||||
$persister = new OneToManyPersister($em);
|
||||
|
||||
$mapping['cache']['usage'] = -1;
|
||||
|
||||
$this->factory->buildCachedCollectionPersister($em, $persister, $mapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException RuntimeException
|
||||
* @expectedExceptionMessage 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
|
||||
*/
|
||||
public function testInvalidFileLockRegionDirectoryException()
|
||||
{
|
||||
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($this->em->getConfiguration(), $this->getSharedSecondLevelCacheDriverImpl());
|
||||
|
||||
$factory->getRegion(array(
|
||||
'usage' => ClassMetadata::CACHE_USAGE_READ_WRITE,
|
||||
'region' => 'foo'
|
||||
));
|
||||
}
|
||||
}
|
263
tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php
Normal file
263
tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php
Normal file
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache;
|
||||
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use Doctrine\ORM\Cache\DefaultCache;
|
||||
use Doctrine\Tests\Models\Cache\State;
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\EntityCacheEntry;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\CollectionCacheEntry;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
class DefaultCacheTest extends OrmTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
const NON_CACHEABLE_ENTITY = 'Doctrine\Tests\Models\CMS\CmsUser';
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::enableSecondLevelCache();
|
||||
parent::setUp();
|
||||
|
||||
$this->em = $this->_getTestEntityManager();
|
||||
$this->cache = new DefaultCache($this->em);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param array $identifier
|
||||
* @param array $data
|
||||
*/
|
||||
private function putEntityCacheEntry($className, array $identifier, array $data)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$cacheKey = new EntityCacheKey($metadata->name, $identifier);
|
||||
$cacheEntry = new EntityCacheEntry($metadata->name, $data);
|
||||
$persister = $this->em->getUnitOfWork()->getEntityPersister($metadata->rootEntityName);
|
||||
|
||||
$persister->getCacheRegion()->put($cacheKey, $cacheEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param string $association
|
||||
* @param array $ownerIdentifier
|
||||
* @param array $data
|
||||
*/
|
||||
private function putCollectionCacheEntry($className, $association, array $ownerIdentifier, array $data)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$cacheKey = new CollectionCacheKey($metadata->name, $association, $ownerIdentifier);
|
||||
$cacheEntry = new CollectionCacheEntry($data);
|
||||
$persister = $this->em->getUnitOfWork()->getCollectionPersister($metadata->getAssociationMapping($association));
|
||||
|
||||
$persister->getCacheRegion()->put($cacheKey, $cacheEntry);
|
||||
}
|
||||
|
||||
public function testImplementsCache()
|
||||
{
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache', $this->cache);
|
||||
}
|
||||
|
||||
public function testGetEntityCacheRegionAccess()
|
||||
{
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Region', $this->cache->getEntityCacheRegion(State::CLASSNAME));
|
||||
$this->assertNull($this->cache->getEntityCacheRegion(self::NON_CACHEABLE_ENTITY));
|
||||
}
|
||||
|
||||
public function testGetCollectionCacheRegionAccess()
|
||||
{
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Region', $this->cache->getCollectionCacheRegion(State::CLASSNAME, 'cities'));
|
||||
$this->assertNull($this->cache->getCollectionCacheRegion(self::NON_CACHEABLE_ENTITY, 'phonenumbers'));
|
||||
}
|
||||
|
||||
public function testContainsEntity()
|
||||
{
|
||||
$identifier = array('id'=>1);
|
||||
$className = Country::CLASSNAME;
|
||||
$cacheEntry = array_merge($identifier, array('name' => 'Brazil'));
|
||||
|
||||
$this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1));
|
||||
|
||||
$this->putEntityCacheEntry($className, $identifier, $cacheEntry);
|
||||
|
||||
$this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1));
|
||||
$this->assertFalse($this->cache->containsEntity(self::NON_CACHEABLE_ENTITY, 1));
|
||||
}
|
||||
|
||||
public function testEvictEntity()
|
||||
{
|
||||
$identifier = array('id'=>1);
|
||||
$className = Country::CLASSNAME;
|
||||
$cacheEntry = array_merge($identifier, array('name' => 'Brazil'));
|
||||
|
||||
$this->putEntityCacheEntry($className, $identifier, $cacheEntry);
|
||||
|
||||
$this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1));
|
||||
|
||||
$this->cache->evictEntity(Country::CLASSNAME, 1);
|
||||
$this->cache->evictEntity(self::NON_CACHEABLE_ENTITY, 1);
|
||||
|
||||
$this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1));
|
||||
}
|
||||
|
||||
public function testEvictEntityRegion()
|
||||
{
|
||||
$identifier = array('id'=>1);
|
||||
$className = Country::CLASSNAME;
|
||||
$cacheEntry = array_merge($identifier, array('name' => 'Brazil'));
|
||||
|
||||
$this->putEntityCacheEntry($className, $identifier, $cacheEntry);
|
||||
|
||||
$this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1));
|
||||
|
||||
$this->cache->evictEntityRegion(Country::CLASSNAME);
|
||||
$this->cache->evictEntityRegion(self::NON_CACHEABLE_ENTITY);
|
||||
|
||||
$this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1));
|
||||
}
|
||||
|
||||
public function testEvictEntityRegions()
|
||||
{
|
||||
$identifier = array('id'=>1);
|
||||
$className = Country::CLASSNAME;
|
||||
$cacheEntry = array_merge($identifier, array('name' => 'Brazil'));
|
||||
|
||||
$this->putEntityCacheEntry($className, $identifier, $cacheEntry);
|
||||
|
||||
$this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1));
|
||||
|
||||
$this->cache->evictEntityRegions();
|
||||
|
||||
$this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1));
|
||||
}
|
||||
|
||||
public function testContainsCollection()
|
||||
{
|
||||
$ownerId = array('id'=>1);
|
||||
$className = State::CLASSNAME;
|
||||
$association = 'cities';
|
||||
$cacheEntry = array(
|
||||
array('id' => 11),
|
||||
array('id' => 12),
|
||||
);
|
||||
|
||||
$this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1));
|
||||
|
||||
$this->putCollectionCacheEntry($className, $association, $ownerId, $cacheEntry);
|
||||
|
||||
$this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1));
|
||||
$this->assertFalse($this->cache->containsCollection(self::NON_CACHEABLE_ENTITY, 'phonenumbers', 1));
|
||||
}
|
||||
|
||||
public function testEvictCollection()
|
||||
{
|
||||
$ownerId = array('id'=>1);
|
||||
$className = State::CLASSNAME;
|
||||
$association = 'cities';
|
||||
$cacheEntry = array(
|
||||
array('id' => 11),
|
||||
array('id' => 12),
|
||||
);
|
||||
|
||||
$this->putCollectionCacheEntry($className, $association, $ownerId, $cacheEntry);
|
||||
|
||||
$this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1));
|
||||
|
||||
$this->cache->evictCollection($className, $association, $ownerId);
|
||||
$this->cache->evictCollection(self::NON_CACHEABLE_ENTITY, 'phonenumbers', 1);
|
||||
|
||||
$this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1));
|
||||
}
|
||||
|
||||
public function testEvictCollectionRegion()
|
||||
{
|
||||
$ownerId = array('id'=>1);
|
||||
$className = State::CLASSNAME;
|
||||
$association = 'cities';
|
||||
$cacheEntry = array(
|
||||
array('id' => 11),
|
||||
array('id' => 12),
|
||||
);
|
||||
|
||||
$this->putCollectionCacheEntry($className, $association, $ownerId, $cacheEntry);
|
||||
|
||||
$this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1));
|
||||
|
||||
$this->cache->evictCollectionRegion($className, $association);
|
||||
$this->cache->evictCollectionRegion(self::NON_CACHEABLE_ENTITY, 'phonenumbers');
|
||||
|
||||
$this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1));
|
||||
}
|
||||
|
||||
public function testEvictCollectionRegions()
|
||||
{
|
||||
$ownerId = array('id'=>1);
|
||||
$className = State::CLASSNAME;
|
||||
$association = 'cities';
|
||||
$cacheEntry = array(
|
||||
array('id' => 11),
|
||||
array('id' => 12),
|
||||
);
|
||||
|
||||
$this->putCollectionCacheEntry($className, $association, $ownerId, $cacheEntry);
|
||||
|
||||
$this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1));
|
||||
|
||||
$this->cache->evictCollectionRegions();
|
||||
|
||||
$this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1));
|
||||
}
|
||||
|
||||
public function testQueryCache()
|
||||
{
|
||||
$this->assertFalse($this->cache->containsQuery('foo'));
|
||||
|
||||
$defaultQueryCache = $this->cache->getQueryCache();
|
||||
$fooQueryCache = $this->cache->getQueryCache('foo');
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\QueryCache', $defaultQueryCache);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\QueryCache', $fooQueryCache);
|
||||
$this->assertSame($defaultQueryCache, $this->cache->getQueryCache());
|
||||
$this->assertSame($fooQueryCache, $this->cache->getQueryCache('foo'));
|
||||
|
||||
$this->cache->evictQueryRegion();
|
||||
$this->cache->evictQueryRegion('foo');
|
||||
$this->cache->evictQueryRegions();
|
||||
|
||||
$this->assertTrue($this->cache->containsQuery('foo'));
|
||||
|
||||
$this->assertSame($defaultQueryCache, $this->cache->getQueryCache());
|
||||
$this->assertSame($fooQueryCache, $this->cache->getQueryCache('foo'));
|
||||
}
|
||||
|
||||
public function testToIdentifierArrayShoudLookupForEntityIdentifier()
|
||||
{
|
||||
$identifier = 123;
|
||||
$entity = new Country('Foo');
|
||||
$metadata = $this->em->getClassMetadata(Country::CLASSNAME);
|
||||
$method = new \ReflectionMethod($this->cache, 'toIdentifierArray');
|
||||
$property = new \ReflectionProperty($entity, 'id');
|
||||
|
||||
$property->setAccessible(true);
|
||||
$method->setAccessible(true);
|
||||
$property->setValue($entity, $identifier);
|
||||
|
||||
$this->assertEquals(array('id'=>$identifier), $method->invoke($this->cache, $metadata, $identifier));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\Tests\Models\Cache\State;
|
||||
use Doctrine\Tests\Models\Cache\City;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\EntityCacheEntry;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\CollectionCacheEntry;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Cache\DefaultCollectionHydrator;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
class DefaultCollectionHydratorTest extends OrmFunctionalTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\CollectionHydrator
|
||||
*/
|
||||
private $structure;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->enableSecondLevelCache();
|
||||
parent::setUp();
|
||||
|
||||
$this->structure = new DefaultCollectionHydrator($this->_em);
|
||||
}
|
||||
|
||||
public function testImplementsCollectionEntryStructure()
|
||||
{
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\DefaultCollectionHydrator', $this->structure);
|
||||
}
|
||||
|
||||
public function testLoadCacheCollection()
|
||||
{
|
||||
$targetRegion = $this->_em->getCache()->getEntityCacheRegion(City::CLASSNAME);
|
||||
$entry = new CollectionCacheEntry(array(
|
||||
array('id'=>31),
|
||||
array('id'=>32),
|
||||
));
|
||||
|
||||
$targetRegion->put(new EntityCacheKey(City::CLASSNAME, array('id'=>31)), new EntityCacheEntry(City::CLASSNAME, array('id'=>31, 'name'=>'Foo')));
|
||||
$targetRegion->put(new EntityCacheKey(City::CLASSNAME, array('id'=>32)), new EntityCacheEntry(City::CLASSNAME, array('id'=>32, 'name'=>'Bar')));
|
||||
|
||||
$sourceClass = $this->_em->getClassMetadata(State::CLASSNAME);
|
||||
$targetClass = $this->_em->getClassMetadata(City::CLASSNAME);
|
||||
$key = new CollectionCacheKey($sourceClass->name, 'cities', array('id'=>21));
|
||||
$collection = new PersistentCollection($this->_em, $targetClass, new ArrayCollection());
|
||||
$list = $this->structure->loadCacheEntry($sourceClass, $key, $entry, $collection);
|
||||
|
||||
$this->assertNotNull($list);
|
||||
$this->assertCount(2, $list);
|
||||
$this->assertCount(2, $collection);
|
||||
|
||||
$this->assertInstanceOf($targetClass->name, $list[0]);
|
||||
$this->assertInstanceOf($targetClass->name, $list[1]);
|
||||
$this->assertInstanceOf($targetClass->name, $collection[0]);
|
||||
$this->assertInstanceOf($targetClass->name, $collection[1]);
|
||||
|
||||
$this->assertSame($list[0], $collection[0]);
|
||||
$this->assertSame($list[1], $collection[1]);
|
||||
|
||||
$this->assertEquals(31, $list[0]->getId());
|
||||
$this->assertEquals(32, $list[1]->getId());
|
||||
$this->assertEquals($list[0]->getId(), $collection[0]->getId());
|
||||
$this->assertEquals($list[1]->getId(), $collection[1]->getId());
|
||||
$this->assertEquals(UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($collection[0]));
|
||||
$this->assertEquals(UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($collection[1]));
|
||||
}
|
||||
|
||||
}
|
125
tests/Doctrine/Tests/ORM/Cache/DefaultEntityHydratorTest.php
Normal file
125
tests/Doctrine/Tests/ORM/Cache/DefaultEntityHydratorTest.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\EntityCacheEntry;
|
||||
use Doctrine\Tests\Models\Cache\State;
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
use Doctrine\ORM\Cache\DefaultEntityHydrator;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
class DefaultEntityHydratorTest extends OrmTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\EntityHydrator
|
||||
*/
|
||||
private $structure;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->em = $this->_getTestEntityManager();
|
||||
$this->structure = new DefaultEntityHydrator($this->em);
|
||||
}
|
||||
|
||||
public function testImplementsEntityEntryStructure()
|
||||
{
|
||||
$this->assertInstanceOf('\Doctrine\ORM\Cache\EntityHydrator', $this->structure);
|
||||
}
|
||||
|
||||
public function testCreateEntity()
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata(Country::CLASSNAME);
|
||||
$key = new EntityCacheKey($metadata->name, array('id'=>1));
|
||||
$entry = new EntityCacheEntry($metadata->name, array('id'=>1, 'name'=>'Foo'));
|
||||
$entity = $this->structure->loadCacheEntry($metadata, $key, $entry);
|
||||
|
||||
$this->assertInstanceOf($metadata->name, $entity);
|
||||
|
||||
$this->assertEquals(1, $entity->getId());
|
||||
$this->assertEquals('Foo', $entity->getName());
|
||||
$this->assertEquals(UnitOfWork::STATE_MANAGED, $this->em->getUnitOfWork()->getEntityState($entity));
|
||||
}
|
||||
|
||||
public function testLoadProxy()
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata(Country::CLASSNAME);
|
||||
$key = new EntityCacheKey($metadata->name, array('id'=>1));
|
||||
$entry = new EntityCacheEntry($metadata->name, array('id'=>1, 'name'=>'Foo'));
|
||||
$proxy = $this->em->getReference($metadata->name, $key->identifier);
|
||||
$entity = $this->structure->loadCacheEntry($metadata, $key, $entry, $proxy);
|
||||
|
||||
$this->assertInstanceOf($metadata->name, $entity);
|
||||
$this->assertSame($proxy, $entity);
|
||||
|
||||
$this->assertEquals(1, $entity->getId());
|
||||
$this->assertEquals('Foo', $entity->getName());
|
||||
$this->assertEquals(UnitOfWork::STATE_MANAGED, $this->em->getUnitOfWork()->getEntityState($proxy));
|
||||
}
|
||||
|
||||
public function testBuildCacheEntry()
|
||||
{
|
||||
$entity = new Country('Foo');
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$data = array('id'=>1, 'name'=>'Foo');
|
||||
$metadata = $this->em->getClassMetadata(Country::CLASSNAME);
|
||||
$key = new EntityCacheKey($metadata->name, array('id'=>1));
|
||||
|
||||
$entity->setId(1);
|
||||
$uow->registerManaged($entity, $key->identifier, $data);
|
||||
|
||||
$cache = $this->structure->buildCacheEntry($metadata, $key, $entity);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\CacheEntry', $cache);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $cache);
|
||||
|
||||
$this->assertArrayHasKey('id', $cache->data);
|
||||
$this->assertArrayHasKey('name', $cache->data);
|
||||
$this->assertEquals(array(
|
||||
'id' => 1,
|
||||
'name' => 'Foo',
|
||||
), $cache->data);
|
||||
}
|
||||
|
||||
public function testBuildCacheEntryOwningSide()
|
||||
{
|
||||
$country = new Country('Foo');
|
||||
$state = new State('Bat', $country);
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$countryData = array('id'=>11, 'name'=>'Foo');
|
||||
$stateData = array('id'=>12, 'name'=>'Bar', 'country' => $country);
|
||||
$metadata = $this->em->getClassMetadata(State::CLASSNAME);
|
||||
$key = new EntityCacheKey($metadata->name, array('id'=>11));
|
||||
|
||||
$country->setId(11);
|
||||
$state->setId(12);
|
||||
|
||||
$uow->registerManaged($country, array('id'=>11), $countryData);
|
||||
$uow->registerManaged($state, array('id'=>12), $stateData);
|
||||
|
||||
$cache = $this->structure->buildCacheEntry($metadata, $key, $state);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\CacheEntry', $cache);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $cache);
|
||||
|
||||
$this->assertArrayHasKey('id', $cache->data);
|
||||
$this->assertArrayHasKey('name', $cache->data);
|
||||
$this->assertArrayHasKey('country', $cache->data);
|
||||
$this->assertEquals(array(
|
||||
'id' => 11,
|
||||
'name' => 'Bar',
|
||||
'country' => array ('id' => 11),
|
||||
), $cache->data);
|
||||
}
|
||||
}
|
526
tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php
Normal file
526
tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php
Normal file
@ -0,0 +1,526 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache;
|
||||
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use Doctrine\Tests\Mocks\CacheRegionMock;
|
||||
use Doctrine\ORM\Cache\DefaultQueryCache;
|
||||
use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
use Doctrine\ORM\Cache\QueryCacheEntry;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
use Doctrine\Tests\Models\Cache\City;
|
||||
use Doctrine\Tests\Models\Cache\State;
|
||||
use Doctrine\Tests\Models\Cache\Travel;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Tests\Models\Generic\BooleanModel;
|
||||
use Doctrine\ORM\Cache\EntityCacheEntry;
|
||||
use Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
class DefaultQueryCacheTest extends OrmTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\DefaultQueryCache
|
||||
*/
|
||||
private $queryCache;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Tests\Mocks\CacheRegionMock
|
||||
*/
|
||||
private $region;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Tests\ORM\Cache\CacheFactoryDefaultQueryCacheTest
|
||||
*/
|
||||
private $cacheFactory;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->enableSecondLevelCache();
|
||||
|
||||
$this->em = $this->_getTestEntityManager();
|
||||
$this->region = new CacheRegionMock();
|
||||
$this->queryCache = new DefaultQueryCache($this->em, $this->region);
|
||||
$this->cacheFactory = new CacheFactoryDefaultQueryCacheTest($this->queryCache, $this->region);
|
||||
|
||||
$this->em->getConfiguration()->setSecondLevelCacheFactory($this->cacheFactory);
|
||||
}
|
||||
|
||||
public function testImplementQueryCache()
|
||||
{
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\QueryCache', $this->queryCache);
|
||||
}
|
||||
|
||||
public function testGetRegion()
|
||||
{
|
||||
$this->assertSame($this->region, $this->queryCache->getRegion());
|
||||
}
|
||||
|
||||
public function testClearShouldEvictRegion()
|
||||
{
|
||||
$this->queryCache->clear();
|
||||
|
||||
$this->assertArrayHasKey('evictAll', $this->region->calls);
|
||||
$this->assertCount(1, $this->region->calls['evictAll']);
|
||||
}
|
||||
|
||||
public function testPutBasicQueryResult()
|
||||
{
|
||||
$result = array();
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$metadata = $this->em->getClassMetadata(Country::CLASSNAME);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c');
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$name = "Country $i";
|
||||
$entity = new Country($name);
|
||||
$result[] = $entity;
|
||||
|
||||
$metadata->setFieldValue($entity, 'id', $i);
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id' => $i), array('name' => $name));
|
||||
}
|
||||
|
||||
$this->assertTrue($this->queryCache->put($key, $rsm, $result));
|
||||
$this->assertArrayHasKey('put', $this->region->calls);
|
||||
$this->assertCount(5, $this->region->calls['put']);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][0]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][1]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][2]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][3]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\QueryCacheKey', $this->region->calls['put'][4]['key']);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $this->region->calls['put'][0]['entry']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $this->region->calls['put'][1]['entry']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $this->region->calls['put'][2]['entry']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $this->region->calls['put'][3]['entry']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\QueryCacheEntry', $this->region->calls['put'][4]['entry']);
|
||||
}
|
||||
|
||||
public function testPutToOneAssociationQueryResult()
|
||||
{
|
||||
$result = array();
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$cityClass = $this->em->getClassMetadata(City::CLASSNAME);
|
||||
$stateClass = $this->em->getClassMetadata(State::CLASSNAME);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(City::CLASSNAME, 'c');
|
||||
$rsm->addJoinedEntityFromClassMetadata(State::CLASSNAME, 's', 'c', 'state', array('id'=>'state_id', 'name'=>'state_name'));
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$state = new State("State $i");
|
||||
$city = new City("City $i", $state);
|
||||
$result[] = $city;
|
||||
|
||||
$cityClass->setFieldValue($city, 'id', $i);
|
||||
$stateClass->setFieldValue($state, 'id', $i*2);
|
||||
|
||||
$uow->registerManaged($state, array('id' => $state->getId()), array('name' => $city->getName()));
|
||||
$uow->registerManaged($city, array('id' => $city->getId()), array('name' => $city->getName(), 'state' => $state));
|
||||
}
|
||||
|
||||
$this->assertTrue($this->queryCache->put($key, $rsm, $result));
|
||||
$this->assertArrayHasKey('put', $this->region->calls);
|
||||
$this->assertCount(9, $this->region->calls['put']);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][0]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][1]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][2]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][3]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][4]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][5]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][6]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][7]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\QueryCacheKey', $this->region->calls['put'][8]['key']);
|
||||
}
|
||||
|
||||
public function testPutToOneAssociationNullQueryResult()
|
||||
{
|
||||
$result = array();
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$cityClass = $this->em->getClassMetadata(City::CLASSNAME);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(City::CLASSNAME, 'c');
|
||||
$rsm->addJoinedEntityFromClassMetadata(State::CLASSNAME, 's', 'c', 'state', array('id'=>'state_id', 'name'=>'state_name'));
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$city = new City("City $i", null);
|
||||
$result[] = $city;
|
||||
|
||||
$cityClass->setFieldValue($city, 'id', $i);
|
||||
|
||||
$uow->registerManaged($city, array('id' => $city->getId()), array('name' => $city->getName(), 'state' => null));
|
||||
}
|
||||
|
||||
$this->assertTrue($this->queryCache->put($key, $rsm, $result));
|
||||
$this->assertArrayHasKey('put', $this->region->calls);
|
||||
$this->assertCount(5, $this->region->calls['put']);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][0]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][1]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][2]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][3]['key']);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\QueryCacheKey', $this->region->calls['put'][4]['key']);
|
||||
}
|
||||
|
||||
public function testPutToManyAssociationQueryResult()
|
||||
{
|
||||
$result = array();
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$cityClass = $this->em->getClassMetadata(City::CLASSNAME);
|
||||
$stateClass = $this->em->getClassMetadata(State::CLASSNAME);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(State::CLASSNAME, 's');
|
||||
$rsm->addJoinedEntityFromClassMetadata(City::CLASSNAME, 'c', 's', 'cities', array('id'=>'c_id', 'name'=>'c_name'));
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$state = new State("State $i");
|
||||
$city1 = new City("City 1", $state);
|
||||
$city2 = new City("City 2", $state);
|
||||
$result[] = $state;
|
||||
|
||||
$cityClass->setFieldValue($city1, 'id', $i + 11);
|
||||
$cityClass->setFieldValue($city2, 'id', $i + 22);
|
||||
$stateClass->setFieldValue($state, 'id', $i);
|
||||
|
||||
$state->addCity($city1);
|
||||
$state->addCity($city2);
|
||||
|
||||
$uow->registerManaged($city1, array('id' => $city1->getId()), array('name' => $city1->getName(), 'state' => $state));
|
||||
$uow->registerManaged($city2, array('id' => $city2->getId()), array('name' => $city2->getName(), 'state' => $state));
|
||||
$uow->registerManaged($state, array('id' => $state->getId()), array('name' => $state->getName(), 'cities' => $state->getCities()));
|
||||
}
|
||||
|
||||
$this->assertTrue($this->queryCache->put($key, $rsm, $result));
|
||||
$this->assertArrayHasKey('put', $this->region->calls);
|
||||
$this->assertCount(13, $this->region->calls['put']);
|
||||
}
|
||||
|
||||
public function testgGetBasicQueryResult()
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$entry = new QueryCacheEntry(array(
|
||||
array('identifier' => array('id' => 1)),
|
||||
array('identifier' => array('id' => 2))
|
||||
));
|
||||
|
||||
$data = array(
|
||||
array('id'=>1, 'name' => 'Foo'),
|
||||
array('id'=>2, 'name' => 'Bar')
|
||||
);
|
||||
|
||||
$this->region->addReturn('get', $entry);
|
||||
$this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $data[0]));
|
||||
$this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $data[1]));
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c');
|
||||
|
||||
$result = $this->queryCache->get($key, $rsm, $entry);
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
$this->assertInstanceOf(Country::CLASSNAME, $result[0]);
|
||||
$this->assertInstanceOf(Country::CLASSNAME, $result[1]);
|
||||
$this->assertEquals(1, $result[0]->getId());
|
||||
$this->assertEquals(2, $result[1]->getId());
|
||||
$this->assertEquals('Foo', $result[0]->getName());
|
||||
$this->assertEquals('Bar', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testCancelPutResultIfEntityPutFails()
|
||||
{
|
||||
$result = array();
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$metadata = $this->em->getClassMetadata(Country::CLASSNAME);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c');
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$name = "Country $i";
|
||||
$entity = new Country($name);
|
||||
$result[] = $entity;
|
||||
|
||||
$metadata->setFieldValue($entity, 'id', $i);
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id' => $i), array('name' => $name));
|
||||
}
|
||||
|
||||
$this->region->addReturn('put', false);
|
||||
|
||||
$this->assertFalse($this->queryCache->put($key, $rsm, $result));
|
||||
$this->assertArrayHasKey('put', $this->region->calls);
|
||||
$this->assertCount(1, $this->region->calls['put']);
|
||||
}
|
||||
|
||||
public function testCancelPutResultIfAssociationEntityPutFails()
|
||||
{
|
||||
$result = array();
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$cityClass = $this->em->getClassMetadata(City::CLASSNAME);
|
||||
$stateClass = $this->em->getClassMetadata(State::CLASSNAME);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(City::CLASSNAME, 'c');
|
||||
$rsm->addJoinedEntityFromClassMetadata(State::CLASSNAME, 's', 'c', 'state', array('id'=>'state_id', 'name'=>'state_name'));
|
||||
|
||||
$state = new State("State 1");
|
||||
$city = new City("City 2", $state);
|
||||
$result[] = $city;
|
||||
|
||||
$cityClass->setFieldValue($city, 'id', 1);
|
||||
$stateClass->setFieldValue($state, 'id', 11);
|
||||
|
||||
$uow->registerManaged($state, array('id' => $state->getId()), array('name' => $city->getName()));
|
||||
$uow->registerManaged($city, array('id' => $city->getId()), array('name' => $city->getName(), 'state' => $state));
|
||||
|
||||
$this->region->addReturn('put', true); // put root entity
|
||||
$this->region->addReturn('put', false); // association fails
|
||||
|
||||
$this->assertFalse($this->queryCache->put($key, $rsm, $result));
|
||||
}
|
||||
|
||||
public function testCancelPutToManyAssociationQueryResult()
|
||||
{
|
||||
$result = array();
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$cityClass = $this->em->getClassMetadata(City::CLASSNAME);
|
||||
$stateClass = $this->em->getClassMetadata(State::CLASSNAME);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(State::CLASSNAME, 's');
|
||||
$rsm->addJoinedEntityFromClassMetadata(City::CLASSNAME, 'c', 's', 'cities', array('id'=>'c_id', 'name'=>'c_name'));
|
||||
|
||||
$state = new State("State");
|
||||
$city1 = new City("City 1", $state);
|
||||
$city2 = new City("City 2", $state);
|
||||
$result[] = $state;
|
||||
|
||||
$stateClass->setFieldValue($state, 'id', 1);
|
||||
$cityClass->setFieldValue($city1, 'id', 11);
|
||||
$cityClass->setFieldValue($city2, 'id', 22);
|
||||
|
||||
$state->addCity($city1);
|
||||
$state->addCity($city2);
|
||||
|
||||
$uow->registerManaged($city1, array('id' => $city1->getId()), array('name' => $city1->getName(), 'state' => $state));
|
||||
$uow->registerManaged($city2, array('id' => $city2->getId()), array('name' => $city2->getName(), 'state' => $state));
|
||||
$uow->registerManaged($state, array('id' => $state->getId()), array('name' => $state->getName(), 'cities' => $state->getCities()));
|
||||
|
||||
$this->region->addReturn('put', true); // put root entity
|
||||
$this->region->addReturn('put', false); // collection association fails
|
||||
|
||||
$this->assertFalse($this->queryCache->put($key, $rsm, $result));
|
||||
$this->assertArrayHasKey('put', $this->region->calls);
|
||||
$this->assertCount(2, $this->region->calls['put']);
|
||||
}
|
||||
|
||||
public function testIgnoreCacheNonGetMode()
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$key = new QueryCacheKey('query.key1', 0, Cache::MODE_PUT);
|
||||
$entry = new QueryCacheEntry(array(
|
||||
array('identifier' => array('id' => 1)),
|
||||
array('identifier' => array('id' => 2))
|
||||
));
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c');
|
||||
|
||||
$this->region->addReturn('get', $entry);
|
||||
|
||||
$this->assertNull($this->queryCache->get($key, $rsm, $entry));
|
||||
}
|
||||
|
||||
public function testIgnoreCacheNonPutMode()
|
||||
{
|
||||
$result = array();
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$metadata = $this->em->getClassMetadata(Country::CLASSNAME);
|
||||
$key = new QueryCacheKey('query.key1', 0, Cache::MODE_GET);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c');
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$name = "Country $i";
|
||||
$entity = new Country($name);
|
||||
$result[] = $entity;
|
||||
|
||||
$metadata->setFieldValue($entity, 'id', $i);
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id' => $i), array('name' => $name));
|
||||
}
|
||||
|
||||
$this->assertFalse($this->queryCache->put($key, $rsm, $result));
|
||||
}
|
||||
|
||||
public function testGetShouldIgnoreOldQueryCacheEntryResult()
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$key = new QueryCacheKey('query.key1', 50);
|
||||
$entry = new QueryCacheEntry(array(
|
||||
array('identifier' => array('id' => 1)),
|
||||
array('identifier' => array('id' => 2))
|
||||
));
|
||||
$entities = array(
|
||||
array('id'=>1, 'name' => 'Foo'),
|
||||
array('id'=>2, 'name' => 'Bar')
|
||||
);
|
||||
|
||||
$entry->time = time() - 100;
|
||||
|
||||
$this->region->addReturn('get', $entry);
|
||||
$this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $entities[0]));
|
||||
$this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $entities[1]));
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c');
|
||||
|
||||
$this->assertNull($this->queryCache->get($key, $rsm, $entry));
|
||||
}
|
||||
|
||||
public function testGetShouldIgnoreNonQueryCacheEntryResult()
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$entry = new \ArrayObject(array(
|
||||
array('identifier' => array('id' => 1)),
|
||||
array('identifier' => array('id' => 2))
|
||||
));
|
||||
|
||||
$data = array(
|
||||
array('id'=>1, 'name' => 'Foo'),
|
||||
array('id'=>2, 'name' => 'Bar')
|
||||
);
|
||||
|
||||
$this->region->addReturn('get', $entry);
|
||||
$this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $data[0]));
|
||||
$this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $data[1]));
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c');
|
||||
|
||||
$this->assertNull($this->queryCache->get($key, $rsm, $entry));
|
||||
}
|
||||
|
||||
public function testGetShouldIgnoreMissingEntityQueryCacheEntry()
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$entry = new QueryCacheEntry(array(
|
||||
array('identifier' => array('id' => 1)),
|
||||
array('identifier' => array('id' => 2))
|
||||
));
|
||||
|
||||
$this->region->addReturn('get', $entry);
|
||||
$this->region->addReturn('get', null);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c');
|
||||
|
||||
$this->assertNull($this->queryCache->get($key, $rsm, $entry));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Doctrine\ORM\Cache\CacheException
|
||||
* @expectedExceptionMessage Entity association field "Doctrine\Tests\Models\Cache\City#travels" not configured as part of the second-level cache.
|
||||
*/
|
||||
public function testQueryNotCacheableAssociationException()
|
||||
{
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$cityClass = $this->em->getClassMetadata(City::CLASSNAME);
|
||||
$city = new City("City 1", null);
|
||||
$result = array(
|
||||
$city
|
||||
);
|
||||
|
||||
$cityClass->setFieldValue($city, 'id', 1);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(City::CLASSNAME, 'c');
|
||||
$rsm->addJoinedEntityFromClassMetadata(Travel::CLASSNAME, 't', 'c', 'travels', array('id' => 't_id'));
|
||||
$uow->registerManaged($city, array('id' => $city->getId()), array('name' => $city->getName(), 'state' => null));
|
||||
|
||||
$this->queryCache->put($key, $rsm, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Doctrine\ORM\Cache\CacheException
|
||||
* @expectedExceptionMessage Second level cache does not suport scalar results.
|
||||
*/
|
||||
public function testScalarResultException()
|
||||
{
|
||||
$result = array();
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
|
||||
$rsm->addScalarResult('id', 'u');
|
||||
|
||||
$this->queryCache->put($key, $rsm, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Doctrine\ORM\Cache\CacheException
|
||||
* @expectedExceptionMessage Entity "Doctrine\Tests\Models\Generic\BooleanModel" not configured as part of the second-level cache.
|
||||
*/
|
||||
public function testNotCacheableEntityException()
|
||||
{
|
||||
$result = array();
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$className = 'Doctrine\Tests\Models\Generic\BooleanModel';
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata($className, 'c');
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$entity = new BooleanModel();
|
||||
$boolean = ($i % 2 === 0);
|
||||
|
||||
$entity->id = $i;
|
||||
$entity->booleanField = $boolean;
|
||||
$result[] = $entity;
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id' => $i), array('booleanField' => $boolean));
|
||||
}
|
||||
|
||||
$this->assertFalse($this->queryCache->put($key, $rsm, $result));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CacheFactoryDefaultQueryCacheTest extends \Doctrine\ORM\Cache\DefaultCacheFactory
|
||||
{
|
||||
private $queryCache;
|
||||
private $region;
|
||||
|
||||
public function __construct(DefaultQueryCache $queryCache, CacheRegionMock $region)
|
||||
{
|
||||
$this->queryCache = $queryCache;
|
||||
$this->region = $region;
|
||||
}
|
||||
|
||||
public function buildQueryCache(EntityManagerInterface $em, $regionName = null)
|
||||
{
|
||||
return $this->queryCache;
|
||||
}
|
||||
|
||||
public function getRegion(array $cache)
|
||||
{
|
||||
return $this->region;
|
||||
}
|
||||
}
|
50
tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php
Normal file
50
tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\Cache\Region\DefaultRegion;
|
||||
use Doctrine\Tests\Mocks\CacheEntryMock;
|
||||
use Doctrine\Tests\Mocks\CacheKeyMock;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
class DefaultRegionTest extends AbstractRegionTest
|
||||
{
|
||||
protected function createRegion()
|
||||
{
|
||||
return new DefaultRegion('default.region.test', $this->cache);
|
||||
}
|
||||
|
||||
public function testGetters()
|
||||
{
|
||||
$this->assertEquals('default.region.test', $this->region->getName());
|
||||
$this->assertSame($this->cache, $this->region->getCache());
|
||||
}
|
||||
|
||||
public function testSharedRegion()
|
||||
{
|
||||
if ( ! extension_loaded('apc') || false === @apc_cache_info()) {
|
||||
$this->markTestSkipped('The ' . __CLASS__ .' requires the use of APC');
|
||||
}
|
||||
|
||||
$key = new CacheKeyMock('key');
|
||||
$entry = new CacheEntryMock(array('value' => 'foo'));
|
||||
$region1 = new DefaultRegion('region1', new \Doctrine\Common\Cache\ApcCache());
|
||||
$region2 = new DefaultRegion('region2', new \Doctrine\Common\Cache\ApcCache());
|
||||
|
||||
$this->assertFalse($region1->contains($key));
|
||||
$this->assertFalse($region2->contains($key));
|
||||
|
||||
$region1->put($key, $entry);
|
||||
$region2->put($key, $entry);
|
||||
|
||||
$this->assertTrue($region1->contains($key));
|
||||
$this->assertTrue($region2->contains($key));
|
||||
|
||||
$region1->evictAll();
|
||||
|
||||
$this->assertFalse($region1->contains($key));
|
||||
$this->assertTrue($region2->contains($key));
|
||||
}
|
||||
}
|
251
tests/Doctrine/Tests/ORM/Cache/FileLockRegionTest.php
Normal file
251
tests/Doctrine/Tests/ORM/Cache/FileLockRegionTest.php
Normal file
@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\Cache\Region\DefaultRegion;
|
||||
use Doctrine\ORM\Cache\Region\FileLockRegion;
|
||||
use Doctrine\ORM\Cache\ConcurrentRegion;
|
||||
use Doctrine\Tests\Mocks\CacheEntryMock;
|
||||
use Doctrine\Tests\Mocks\CacheKeyMock;
|
||||
use Doctrine\ORM\Cache\CacheKey;
|
||||
use Doctrine\ORM\Cache\Lock;
|
||||
|
||||
use RecursiveIteratorIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
class FileLockRegionTest extends AbstractRegionTest
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\ConcurrentRegion
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
if ( ! is_dir($this->directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->directory), RecursiveIteratorIterator::CHILD_FIRST) as $file) {
|
||||
$file->isFile()
|
||||
? @unlink($file->getRealPath())
|
||||
: @rmdir($file->getRealPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\ConcurrentRegion $region
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getFileName(ConcurrentRegion $region, CacheKey $key)
|
||||
{
|
||||
$reflection = new \ReflectionMethod($region, 'getLockFileName');
|
||||
|
||||
$reflection->setAccessible(true);
|
||||
|
||||
return $reflection->invoke($region, $key);
|
||||
}
|
||||
|
||||
protected function createRegion()
|
||||
{
|
||||
$this->directory = sys_get_temp_dir() . '/doctrine_lock_'. uniqid();
|
||||
|
||||
$region = new DefaultRegion('concurren_region_test', $this->cache);
|
||||
|
||||
return new FileLockRegion($region, $this->directory, 60);
|
||||
}
|
||||
|
||||
public function testGetRegionName()
|
||||
{
|
||||
$this->assertEquals('concurren_region_test', $this->region->getName());
|
||||
}
|
||||
|
||||
public function testLockAndUnlock()
|
||||
{
|
||||
$key = new CacheKeyMock('key');
|
||||
$entry = new CacheEntryMock(array('foo' => 'bar'));
|
||||
$file = $this->getFileName($this->region, $key);
|
||||
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertTrue($this->region->put($key, $entry));
|
||||
$this->assertTrue($this->region->contains($key));
|
||||
|
||||
$lock = $this->region->lock($key);
|
||||
|
||||
$this->assertFileExists($file);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock);
|
||||
$this->assertEquals($lock->value, file_get_contents($file));
|
||||
|
||||
// should be not available after lock
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertNull($this->region->get($key));
|
||||
|
||||
$this->assertTrue($this->region->unlock($key, $lock));
|
||||
$this->assertFileNotExists($file);
|
||||
}
|
||||
|
||||
public function testLockWithExistingLock()
|
||||
{
|
||||
$key = new CacheKeyMock('key');
|
||||
$entry = new CacheEntryMock(array('foo' => 'bar'));
|
||||
$file = $this->getFileName($this->region, $key);
|
||||
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertTrue($this->region->put($key, $entry));
|
||||
$this->assertTrue($this->region->contains($key));
|
||||
|
||||
file_put_contents($file, 'foo');
|
||||
$this->assertFileExists($file);
|
||||
$this->assertEquals('foo' , file_get_contents($file));
|
||||
|
||||
$this->assertNull($this->region->lock($key));
|
||||
$this->assertEquals('foo' , file_get_contents($file));
|
||||
$this->assertFileExists($file);
|
||||
|
||||
// should be not available
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertNull($this->region->get($key));
|
||||
}
|
||||
|
||||
public function testUnlockWithExistingLock()
|
||||
{
|
||||
$key = new CacheKeyMock('key');
|
||||
$entry = new CacheEntryMock(array('foo' => 'bar'));
|
||||
$file = $this->getFileName($this->region, $key);
|
||||
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertTrue($this->region->put($key, $entry));
|
||||
$this->assertTrue($this->region->contains($key));
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock = $this->region->lock($key));
|
||||
$this->assertEquals($lock->value, file_get_contents($file));
|
||||
$this->assertFileExists($file);
|
||||
|
||||
// change the lock
|
||||
file_put_contents($file, 'foo');
|
||||
$this->assertFileExists($file);
|
||||
$this->assertEquals('foo' , file_get_contents($file));
|
||||
|
||||
//try to unlock
|
||||
$this->assertFalse($this->region->unlock($key, $lock));
|
||||
$this->assertEquals('foo' , file_get_contents($file));
|
||||
$this->assertFileExists($file);
|
||||
|
||||
// should be not available
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertNull($this->region->get($key));
|
||||
}
|
||||
|
||||
public function testPutWithExistingLock()
|
||||
{
|
||||
$key = new CacheKeyMock('key');
|
||||
$entry = new CacheEntryMock(array('foo' => 'bar'));
|
||||
$file = $this->getFileName($this->region, $key);
|
||||
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertTrue($this->region->put($key, $entry));
|
||||
$this->assertTrue($this->region->contains($key));
|
||||
|
||||
// create lock
|
||||
file_put_contents($file, 'foo');
|
||||
$this->assertFileExists($file);
|
||||
$this->assertEquals('foo' , file_get_contents($file));
|
||||
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertFalse($this->region->put($key, $entry));
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
|
||||
$this->assertFileExists($file);
|
||||
$this->assertEquals('foo' , file_get_contents($file));
|
||||
}
|
||||
|
||||
public function testLockedEvict()
|
||||
{
|
||||
$key = new CacheKeyMock('key');
|
||||
$entry = new CacheEntryMock(array('foo' => 'bar'));
|
||||
$file = $this->getFileName($this->region, $key);
|
||||
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertTrue($this->region->put($key, $entry));
|
||||
$this->assertTrue($this->region->contains($key));
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock = $this->region->lock($key));
|
||||
$this->assertEquals($lock->value, file_get_contents($file));
|
||||
$this->assertFileExists($file);
|
||||
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertTrue($this->region->evict($key));
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertFileNotExists($file);
|
||||
}
|
||||
|
||||
public function testLockedEvictAll()
|
||||
{
|
||||
$key1 = new CacheKeyMock('key1');
|
||||
$entry1 = new CacheEntryMock(array('foo1' => 'bar1'));
|
||||
$file1 = $this->getFileName($this->region, $key1);
|
||||
|
||||
$key2 = new CacheKeyMock('key2');
|
||||
$entry2 = new CacheEntryMock(array('foo2' => 'bar2'));
|
||||
$file2 = $this->getFileName($this->region, $key2);
|
||||
|
||||
$this->assertFalse($this->region->contains($key1));
|
||||
$this->assertTrue($this->region->put($key1, $entry1));
|
||||
$this->assertTrue($this->region->contains($key1));
|
||||
|
||||
$this->assertFalse($this->region->contains($key2));
|
||||
$this->assertTrue($this->region->put($key2, $entry2));
|
||||
$this->assertTrue($this->region->contains($key2));
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock1 = $this->region->lock($key1));
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock2 = $this->region->lock($key2));
|
||||
|
||||
$this->assertEquals($lock2->value, file_get_contents($file2));
|
||||
$this->assertEquals($lock1->value, file_get_contents($file1));
|
||||
|
||||
$this->assertFileExists($file1);
|
||||
$this->assertFileExists($file2);
|
||||
|
||||
$this->assertTrue($this->region->evictAll());
|
||||
|
||||
$this->assertFileNotExists($file1);
|
||||
$this->assertFileNotExists($file2);
|
||||
|
||||
$this->assertFalse($this->region->contains($key1));
|
||||
$this->assertFalse($this->region->contains($key2));
|
||||
}
|
||||
|
||||
public function testLockLifetime()
|
||||
{
|
||||
$key = new CacheKeyMock('key');
|
||||
$entry = new CacheEntryMock(array('foo' => 'bar'));
|
||||
$file = $this->getFileName($this->region, $key);
|
||||
$property = new \ReflectionProperty($this->region, 'lockLifetime');
|
||||
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($this->region, -10);
|
||||
|
||||
$this->assertFalse($this->region->contains($key));
|
||||
$this->assertTrue($this->region->put($key, $entry));
|
||||
$this->assertTrue($this->region->contains($key));
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock = $this->region->lock($key));
|
||||
$this->assertEquals($lock->value, file_get_contents($file));
|
||||
$this->assertFileExists($file);
|
||||
|
||||
// outdated lock should be removed
|
||||
$this->assertTrue($this->region->contains($key));
|
||||
$this->assertNotNull($this->region->get($key));
|
||||
$this->assertFileNotExists($file);
|
||||
}
|
||||
}
|
@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache\Persister;
|
||||
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Persisters\CollectionPersister;
|
||||
|
||||
use Doctrine\Tests\Models\Cache\State;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
abstract class AbstractCollectionPersisterTest extends OrmTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Persisters\CollectionPersister
|
||||
*/
|
||||
protected $collectionPersister;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $regionMockMethods = array(
|
||||
'getName',
|
||||
'contains',
|
||||
'get',
|
||||
'put',
|
||||
'evict',
|
||||
'evictAll'
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $collectionPersisterMockMethods = array(
|
||||
'delete',
|
||||
'update',
|
||||
'deleteRows',
|
||||
'insertRows',
|
||||
'count',
|
||||
'slice',
|
||||
'contains',
|
||||
'containsKey',
|
||||
'removeElement',
|
||||
'removeKey',
|
||||
'get',
|
||||
);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
* @param \Doctrine\ORM\Persisters\CollectionPersister $persister
|
||||
* @param \Doctrine\ORM\Cache\Region $region
|
||||
* @param array $mapping
|
||||
*
|
||||
* @return Doctrine\ORM\Cache\Persister\AbstractCollectionPersister
|
||||
*/
|
||||
abstract protected function createPersister(EntityManager $em, CollectionPersister $persister, Region $region, array $mapping);
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->getSharedSecondLevelCacheDriverImpl()->flushAll();
|
||||
$this->enableSecondLevelCache();
|
||||
parent::setUp();
|
||||
|
||||
$this->em = $this->_getTestEntityManager();
|
||||
$this->region = $this->createRegion();
|
||||
$this->collectionPersister = $this->getMock('Doctrine\ORM\Persisters\CollectionPersister', $this->collectionPersisterMockMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
protected function createRegion()
|
||||
{
|
||||
return $this->getMock('Doctrine\ORM\Cache\Region', $this->regionMockMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\PersistentCollection
|
||||
*/
|
||||
protected function createCollection($owner, $assoc = null, $class = null, $elements = null)
|
||||
{
|
||||
$em = $this->em;
|
||||
$class = $class ?: $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\State');
|
||||
$assoc = $assoc ?: $class->associationMappings['cities'];
|
||||
$coll = new \Doctrine\ORM\PersistentCollection($em, $class, $elements ?: new ArrayCollection);
|
||||
|
||||
$coll->setOwner($owner, $assoc);
|
||||
$coll->setInitialized(true);
|
||||
|
||||
return $coll;
|
||||
}
|
||||
|
||||
protected function createPersisterDefault()
|
||||
{
|
||||
$assoc = $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\State')->associationMappings['cities'];
|
||||
|
||||
return $this->createPersister($this->em, $this->collectionPersister, $this->region, $assoc);
|
||||
}
|
||||
|
||||
public function testImplementsEntityPersister()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Persisters\CollectionPersister', $persister);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedPersister', $persister);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedCollectionPersister', $persister);
|
||||
}
|
||||
|
||||
public function testInvokeDelete()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('delete')
|
||||
->with($this->equalTo($collection));
|
||||
|
||||
$this->assertNull($persister->delete($collection));
|
||||
}
|
||||
|
||||
public function testInvokeUpdate()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
|
||||
$collection->setDirty(true);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('update')
|
||||
->with($this->equalTo($collection));
|
||||
|
||||
$this->assertNull($persister->update($collection));
|
||||
}
|
||||
|
||||
public function testInvokeDeleteRows()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('deleteRows')
|
||||
->with($this->equalTo($collection));
|
||||
|
||||
$this->assertNull($persister->deleteRows($collection));
|
||||
}
|
||||
|
||||
public function testInvokeInsertRows()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('insertRows')
|
||||
->with($this->equalTo($collection));
|
||||
|
||||
$this->assertNull($persister->insertRows($collection));
|
||||
}
|
||||
|
||||
public function testInvokeCount()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('count')
|
||||
->with($this->equalTo($collection))
|
||||
->will($this->returnValue(0));
|
||||
|
||||
$this->assertEquals(0, $persister->count($collection));
|
||||
}
|
||||
|
||||
public function testInvokEslice()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
$slice = $this->createCollection($entity);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('slice')
|
||||
->with($this->equalTo($collection), $this->equalTo(1), $this->equalTo(2))
|
||||
->will($this->returnValue($slice));
|
||||
|
||||
$this->assertEquals($slice, $persister->slice($collection, 1 , 2));
|
||||
}
|
||||
|
||||
public function testInvokeContains()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$element = new State("Bar");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('contains')
|
||||
->with($this->equalTo($collection), $this->equalTo($element))
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->assertFalse($persister->contains($collection,$element));
|
||||
}
|
||||
|
||||
public function testInvokeContainsKey()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('containsKey')
|
||||
->with($this->equalTo($collection), $this->equalTo(0))
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->assertFalse($persister->containsKey($collection, 0));
|
||||
}
|
||||
|
||||
public function testInvokeRemoveElement()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$element = new State("Bar");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('removeElement')
|
||||
->with($this->equalTo($collection), $this->equalTo($element))
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->assertFalse($persister->removeElement($collection, $element));
|
||||
}
|
||||
|
||||
public function testInvokeRemoveKey()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('removeKey')
|
||||
->with($this->equalTo($collection), $this->equalTo(0))
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->assertFalse($persister->removeKey($collection, 0));
|
||||
}
|
||||
|
||||
public function testInvokeGet()
|
||||
{
|
||||
$entity = new State("Foo");
|
||||
$element = new State("Bar");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$collection = $this->createCollection($entity);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->collectionPersister->expects($this->once())
|
||||
->method('get')
|
||||
->with($this->equalTo($collection), $this->equalTo(0))
|
||||
->will($this->returnValue($element));
|
||||
|
||||
$this->assertEquals($element, $persister->get($collection, 0));
|
||||
}
|
||||
}
|
@ -0,0 +1,416 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache\Persister;
|
||||
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Persisters\EntityPersister;
|
||||
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
abstract class AbstractEntityPersisterTest extends OrmTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Persisters\EntityPersister
|
||||
*/
|
||||
protected $entityPersister;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $regionMockMethods = array(
|
||||
'getName',
|
||||
'contains',
|
||||
'get',
|
||||
'put',
|
||||
'evict',
|
||||
'evictAll'
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $entityPersisterMockMethods = array(
|
||||
'getClassMetadata',
|
||||
'getResultSetMapping',
|
||||
'getInserts',
|
||||
'getInsertSQL',
|
||||
'getSelectSQL',
|
||||
'expandParameters',
|
||||
'getSelectConditionStatementSQL',
|
||||
'addInsert',
|
||||
'executeInserts',
|
||||
'update',
|
||||
'delete',
|
||||
'getOwningTable',
|
||||
'load',
|
||||
'loadById',
|
||||
'loadOneToOneEntity',
|
||||
'refresh',
|
||||
'loadCriteria',
|
||||
'loadAll',
|
||||
'getManyToManyCollection',
|
||||
'loadManyToManyCollection',
|
||||
'loadOneToManyCollection',
|
||||
'lock',
|
||||
'getOneToManyCollection',
|
||||
'exists'
|
||||
);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
* @param \Doctrine\ORM\Persisters\EntityPersister $persister
|
||||
* @param \Doctrine\ORM\Cache\Region $region
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata
|
||||
*
|
||||
* @return Doctrine\ORM\Cache\Persister\AbstractEntityPersister
|
||||
*/
|
||||
abstract protected function createPersister(EntityManager $em, EntityPersister $persister, Region $region, ClassMetadata $metadata);
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->getSharedSecondLevelCacheDriverImpl()->flushAll();
|
||||
$this->enableSecondLevelCache();
|
||||
parent::setUp();
|
||||
|
||||
$this->em = $this->_getTestEntityManager();
|
||||
$this->region = $this->createRegion();
|
||||
$this->entityPersister = $this->getMock('Doctrine\ORM\Persisters\EntityPersister', $this->entityPersisterMockMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
protected function createRegion()
|
||||
{
|
||||
return $this->getMock('Doctrine\ORM\Cache\Region', $this->regionMockMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Doctrine\ORM\Cache\Persister\AbstractEntityPersister
|
||||
*/
|
||||
protected function createPersisterDefault()
|
||||
{
|
||||
return $this->createPersister($this->em, $this->entityPersister, $this->region, $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\Country'));
|
||||
}
|
||||
|
||||
public function testImplementsEntityPersister()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Persisters\EntityPersister', $persister);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedPersister', $persister);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $persister);
|
||||
}
|
||||
|
||||
public function testInvokeAddInsert()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('addInsert')
|
||||
->with($this->equalTo($entity));
|
||||
|
||||
$this->assertNull($persister->addInsert($entity));
|
||||
}
|
||||
|
||||
public function testInvokeGetInserts()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('getInserts')
|
||||
->will($this->returnValue(array($entity)));
|
||||
|
||||
$this->assertEquals(array($entity), $persister->getInserts());
|
||||
}
|
||||
|
||||
public function testInvokeGetSelectSQL()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('getSelectSQL')
|
||||
->with($this->equalTo(array('name'=>'Foo')), $this->equalTo(array(0)), $this->equalTo(1), $this->equalTo(2), $this->equalTo(3), $this->equalTo(array(4)))
|
||||
->will($this->returnValue('SELECT * FROM foo WERE name = ?'));
|
||||
|
||||
$this->assertEquals('SELECT * FROM foo WERE name = ?', $persister->getSelectSQL(array('name'=>'Foo'), array(0), 1, 2, 3, array(4)));
|
||||
}
|
||||
|
||||
public function testInvokeGetInsertSQL()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('getInsertSQL')
|
||||
->will($this->returnValue('INSERT INTO foo (?)'));
|
||||
|
||||
$this->assertEquals('INSERT INTO foo (?)', $persister->getInsertSQL());
|
||||
}
|
||||
|
||||
public function testInvokeExpandParameters()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('expandParameters')
|
||||
->with($this->equalTo(array('name'=>'Foo')))
|
||||
->will($this->returnValue(array('name'=>'Foo')));
|
||||
|
||||
$this->assertEquals(array('name'=>'Foo'), $persister->expandParameters(array('name'=>'Foo')));
|
||||
}
|
||||
|
||||
public function testInvokeSelectConditionStatementSQL()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('getSelectConditionStatementSQL')
|
||||
->with($this->equalTo('id'), $this->equalTo(1), $this->equalTo(array()), $this->equalTo('='))
|
||||
->will($this->returnValue('name = 1'));
|
||||
|
||||
$this->assertEquals('name = 1', $persister->getSelectConditionStatementSQL('id', 1, array(), '='));
|
||||
}
|
||||
|
||||
public function testInvokeExecuteInserts()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('executeInserts')
|
||||
->will($this->returnValue(array('id' => 1)));
|
||||
|
||||
$this->assertEquals(array('id' => 1), $persister->executeInserts());
|
||||
}
|
||||
|
||||
public function testInvokeUpdate()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('update')
|
||||
->with($this->equalTo($entity));
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->assertNull($persister->update($entity));
|
||||
}
|
||||
|
||||
public function testInvokeDelete()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('delete')
|
||||
->with($this->equalTo($entity));
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->assertNull($persister->delete($entity));
|
||||
}
|
||||
|
||||
public function testInvokeGetOwningTable()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('getOwningTable')
|
||||
->with($this->equalTo('name'))
|
||||
->will($this->returnValue('t'));
|
||||
|
||||
$this->assertEquals('t', $persister->getOwningTable('name'));
|
||||
}
|
||||
|
||||
public function testInvokeLoad()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('load')
|
||||
->with($this->equalTo(array('id' => 1)), $this->equalTo($entity), $this->equalTo(array(0)), $this->equalTo(array(1)), $this->equalTo(2), $this->equalTo(3), $this->equalTo(array(4)))
|
||||
->will($this->returnValue($entity));
|
||||
|
||||
$this->assertEquals($entity, $persister->load(array('id' => 1), $entity, array(0), array(1), 2, 3, array(4)));
|
||||
}
|
||||
|
||||
public function testInvokeLoadAll()
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$rsm->addEntityResult(Country::CLASSNAME, 'c');
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('loadAll')
|
||||
->with($this->equalTo(array('id' => 1)), $this->equalTo(array(0)), $this->equalTo(1), $this->equalTo(2))
|
||||
->will($this->returnValue(array($entity)));
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('getResultSetMapping')
|
||||
->will($this->returnValue($rsm));
|
||||
|
||||
$this->assertEquals(array($entity), $persister->loadAll(array('id' => 1), array(0), 1, 2));
|
||||
}
|
||||
|
||||
public function testInvokeLoadById()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('loadById')
|
||||
->with($this->equalTo(array('id' => 1)), $this->equalTo($entity))
|
||||
->will($this->returnValue($entity));
|
||||
|
||||
$this->assertEquals($entity, $persister->loadById(array('id' => 1), $entity));
|
||||
}
|
||||
|
||||
public function testInvokeLoadOneToOneEntity()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('loadOneToOneEntity')
|
||||
->with($this->equalTo(array()), $this->equalTo('foo'), $this->equalTo(array('id' => 11)))
|
||||
->will($this->returnValue($entity));
|
||||
|
||||
$this->assertEquals($entity, $persister->loadOneToOneEntity(array(), 'foo', array('id' => 11)));
|
||||
}
|
||||
|
||||
public function testInvokeRefresh()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('refresh')
|
||||
->with($this->equalTo(array('id' => 1)), $this->equalTo($entity), $this->equalTo(0))
|
||||
->will($this->returnValue($entity));
|
||||
|
||||
$this->assertNull($persister->refresh(array('id' => 1), $entity), 0);
|
||||
}
|
||||
|
||||
public function testInvokeLoadCriteria()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
$criteria = new Criteria();
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('loadCriteria')
|
||||
->with($this->equalTo($criteria))
|
||||
->will($this->returnValue(array($entity)));
|
||||
|
||||
$this->assertEquals(array($entity), $persister->loadCriteria($criteria));
|
||||
}
|
||||
|
||||
public function testInvokeGetManyToManyCollection()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('getManyToManyCollection')
|
||||
->with($this->equalTo(array()), $this->equalTo('Foo'), $this->equalTo(1), $this->equalTo(2))
|
||||
->will($this->returnValue(array($entity)));
|
||||
|
||||
$this->assertEquals(array($entity), $persister->getManyToManyCollection(array(), 'Foo', 1 ,2));
|
||||
}
|
||||
|
||||
public function testInvokeGetOneToManyCollection()
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('getOneToManyCollection')
|
||||
->with($this->equalTo(array()), $this->equalTo('Foo'), $this->equalTo(1), $this->equalTo(2))
|
||||
->will($this->returnValue(array($entity)));
|
||||
|
||||
$this->assertEquals(array($entity), $persister->getOneToManyCollection(array(), 'Foo', 1 ,2));
|
||||
}
|
||||
|
||||
public function testInvokeLoadManyToManyCollection()
|
||||
{
|
||||
$mapping = $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\Country');
|
||||
$assoc = array('type' => 1);
|
||||
$coll = new PersistentCollection($this->em, 'Foo', $mapping);
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('loadManyToManyCollection')
|
||||
->with($this->equalTo($assoc), $this->equalTo('Foo'), $coll)
|
||||
->will($this->returnValue(array($entity)));
|
||||
|
||||
$this->assertEquals(array($entity), $persister->loadManyToManyCollection($assoc, 'Foo', $coll));
|
||||
}
|
||||
|
||||
public function testInvokeLoadOneToManyCollection()
|
||||
{
|
||||
$mapping = $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\Country');
|
||||
$assoc = array('type' => 1);
|
||||
$coll = new PersistentCollection($this->em, 'Foo', $mapping);
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country("Foo");
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('loadOneToManyCollection')
|
||||
->with($this->equalTo($assoc), $this->equalTo('Foo'), $coll)
|
||||
->will($this->returnValue(array($entity)));
|
||||
|
||||
$this->assertEquals(array($entity), $persister->loadOneToManyCollection($assoc, 'Foo', $coll));
|
||||
}
|
||||
|
||||
public function testInvokeLock()
|
||||
{
|
||||
$identifier = array('id' => 1);
|
||||
$persister = $this->createPersisterDefault();
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('lock')
|
||||
->with($this->equalTo($identifier), $this->equalTo(1));
|
||||
|
||||
$this->assertNull($persister->lock($identifier, 1));
|
||||
}
|
||||
|
||||
public function testInvokeExists()
|
||||
{
|
||||
$entity = new Country("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('exists')
|
||||
->with($this->equalTo($entity), $this->equalTo(array()));
|
||||
|
||||
$this->assertNull($persister->exists($entity, array()));
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache\Persister;
|
||||
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Persisters\CollectionPersister;
|
||||
use Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedCollectionPersister;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
class NonStrictReadWriteCachedCollectionPersisterTest extends AbstractCollectionPersisterTest
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createPersister(EntityManager $em, CollectionPersister $persister, Region $region, array $mapping)
|
||||
{
|
||||
return new NonStrictReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache\Persister;
|
||||
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\EntityCacheEntry;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Persisters\EntityPersister;
|
||||
use Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedEntityPersister;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
*/
|
||||
class NonStrictReadWriteCachedEntityPersisterTest extends AbstractEntityPersisterTest
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createPersister(EntityManager $em, EntityPersister $persister, Region $region, ClassMetadata $metadata)
|
||||
{
|
||||
return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
|
||||
}
|
||||
|
||||
public function testTransactionRollBackShouldClearQueue()
|
||||
{
|
||||
$entity = new Country("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache');
|
||||
|
||||
$property->setAccessible(true);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$persister->update($entity);
|
||||
$persister->delete($entity);
|
||||
|
||||
$this->assertCount(2, $property->getValue($persister));
|
||||
|
||||
$persister->afterTransactionRolledBack();
|
||||
|
||||
$this->assertCount(0, $property->getValue($persister));
|
||||
}
|
||||
|
||||
public function testInsertTransactionCommitShouldPutCache()
|
||||
{
|
||||
$entity = new Country("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1));
|
||||
$entry = new EntityCacheEntry(Country::CLASSNAME, array('id'=>1, 'name'=>'Foo'));
|
||||
$property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache');
|
||||
|
||||
$property->setAccessible(true);
|
||||
|
||||
$this->region->expects($this->once())
|
||||
->method('put')
|
||||
->with($this->equalTo($key), $this->equalTo($entry));
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('addInsert')
|
||||
->with($this->equalTo($entity));
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('getInserts')
|
||||
->will($this->returnValue(array($entity)));
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('executeInserts');
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$persister->addInsert($entity);
|
||||
$persister->executeInserts();
|
||||
|
||||
$this->assertCount(1, $property->getValue($persister));
|
||||
|
||||
$persister->afterTransactionComplete();
|
||||
|
||||
$this->assertCount(0, $property->getValue($persister));
|
||||
}
|
||||
|
||||
public function testUpdateTransactionCommitShouldPutCache()
|
||||
{
|
||||
$entity = new Country("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1));
|
||||
$entry = new EntityCacheEntry(Country::CLASSNAME, array('id'=>1, 'name'=>'Foo'));
|
||||
$property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache');
|
||||
|
||||
$property->setAccessible(true);
|
||||
|
||||
$this->region->expects($this->once())
|
||||
->method('put')
|
||||
->with($this->equalTo($key), $this->equalTo($entry));
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('update')
|
||||
->with($this->equalTo($entity));
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$persister->update($entity);
|
||||
|
||||
$this->assertCount(1, $property->getValue($persister));
|
||||
|
||||
$persister->afterTransactionComplete();
|
||||
|
||||
$this->assertCount(0, $property->getValue($persister));
|
||||
}
|
||||
|
||||
public function testDeleteTransactionCommitShouldEvictCache()
|
||||
{
|
||||
$entity = new Country("Foo");
|
||||
$persister = $this->createPersisterDefault();
|
||||
$key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1));
|
||||
$property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache');
|
||||
|
||||
$property->setAccessible(true);
|
||||
|
||||
$this->region->expects($this->once())
|
||||
->method('evict')
|
||||
->with($this->equalTo($key));
|
||||
|
||||
$this->entityPersister->expects($this->once())
|
||||
->method('delete')
|
||||
->with($this->equalTo($entity));
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo'));
|
||||
|
||||
$persister->delete($entity);
|
||||
|
||||
$this->assertCount(1, $property->getValue($persister));
|
||||
|
||||
$persister->afterTransactionComplete();
|
||||
|
||||
$this->assertCount(0, $property->getValue($persister));
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user