Skip to content

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
A hydrator for caching Objects at the sql result level, will add them to identity map on cache hit! Workaround for http://www.doctrine-project.org/jira/browse/DDC-217
<?php
namespace Whitewashing\Doctrine;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
/** Reeally nasty hack to get cashing on the DBAL/PDO Statement level working. Dont do this at home :) */
class CachedObjectHydrator extends ObjectHydrator
{
public function closeCursor() {}
protected function _hydrateAll()
{
$result = array();
$cache = array();
$resultCache = $this->_em->getConfiguration()->getResultCacheImpl();
$cacheKey = false;
if ($resultCache && isset($this->_hints['whitewashing.object_cache_key'])) {
$cacheKey = $this->_hints['whitewashing.object_cache_key'];
}
if ($cacheKey && $rows = $resultCache->fetch($cacheKey)) {
foreach ($rows AS $row) {
$this->_hydrateRow($row, $cache, $result);
}
} else {
$rows = array();
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($row, $cache, $result);
if ($cacheKey) {
$rows[] = $row;
}
}
if ($cacheKey) {
$resultCache->save($cacheKey, $rows);
}
}
// This is not the hack you are looking for.
$reflObject = new \ReflectionClass("Doctrine\ORM\Internal\Hydration\ObjectHydrator");
$reflProp = $reflObject->getProperty("_initializedCollections");
$reflProp->setAccessible(true);
$cols = $reflProp->getValue($this);
// Take snapshots from all newly initialized collections
foreach ($cols as $coll) {
$coll->takeSnapshot();
}
return $result;
}
public function executeQuery($query)
{
// another hack nobody has seen!
$sql = $query->getSQL();
$reflObject = new \ReflectionClass("Doctrine\ORM\Query");
$reflProp = $reflObject->getProperty("_parserResult");
$reflProp->setAccessible(true);
$parserResult = $reflProp->getValue($query);
$rsm = $parserResult->getResultSetMapping();
$resultCache = $this->_em->getConfiguration()->getResultCacheImpl();
$hints = $query->getHints();
if ($resultCache && isset($hints['whitewashing.object_cache_key']) && $resultCache->contains($hints['whitewashing.object_cache_key'])) {
$stmt = $this;
} else {
$executor = $parserResult->getSqlExecutor();
// Prepare parameters
$paramMappings = $parserResult->getParameterMappings();
$params = $query->getParameters();
$reflObject = new \ReflectionClass("Doctrine\ORM\AbstractQuery");
$reflProp = $reflObject->getProperty("_types");
$reflProp->setAccessible(true);
$types = $reflProp->getValue($query);
if (count($paramMappings) != count($params)) {
throw \Doctrine\ORM\QueryException::invalidParameterNumber();
}
list($sqlParams, $types) = $this->processParameterMappings($paramMappings, $params, $types);
$stmt = $executor->execute($this->_em->getConnection(), $sqlParams, $types);
}
return $this->hydrateAll($stmt, $rsm, $hints);
}
/**
* Processes query parameter mappings
*
* @param array $paramMappings
* @return array
*/
private function processParameterMappings($paramMappings, $params, $types)
{
$sqlParams = $types = array();
foreach ($params as $key => $value) {
if ( ! isset($paramMappings[$key])) {
throw QueryException::unknownParameter($key);
}
if (isset($types[$key])) {
foreach ($paramMappings[$key] as $position) {
$types[$position] = $this->_paramTypes[$key];
}
}
$sqlPositions = $paramMappings[$key];
$value = array_values($this->processParameterValue($value));
$countValue = count($value);
for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
$sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
}
}
if ($sqlParams) {
ksort($sqlParams);
$sqlParams = array_values($sqlParams);
}
return array($sqlParams, $types);
}
private function processParameterValue($value)
{
switch (true) {
case is_array($value):
for ($i = 0, $l = count($value); $i < $l; $i++) {
$paramValue = $this->processParameterValue($value[$i]);
// TODO: What about Entities that have composite primary key?
$value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
}
return array($value);
case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)):
if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
return array_values($this->_em->getUnitOfWork()->getEntityIdentifier($value));
}
$class = $this->_em->getClassMetadata(get_class($value));
return array_values($class->getIdentifierValues($value));
default:
return array($value);
}
}
}
# For Symfony integration
doctrine:
orm:
default:
hydrators:
cache_object: Vendor\Namespace\To\CachedObjectHydrator
<?php
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
require_once "common.php";
/**
* @Entity
*/
class TestObject
{
/** @Id @GeneratedValue @Column(type="integer") */
public $id;
}
$paths = array(__DIR__ . "/lib", __DIR__ . "/lib/Foo");
$isDevMode = true;
$cache = new \Doctrine\Common\Cache\ArrayCache();
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$config->addCustomHydrationMode('cache_object', 'CachedObjectHydrator');
$config->setResultCacheImpl($cache);
$em = EntityManager::create($dbParams, $config);
$st = new \Doctrine\ORM\Tools\SchemaTool($em);
$st->createSchema(array($em->getClassMetadata('TestObject')));
$to = new TestObject();
$em->persist($to);
$em->flush();
$em->clear();
$query = $em->createQuery("SELECT t FROM TestObject t");
$query->setHint('whitewashing.object_cache_key', 'test_obj1');
$hydrator = $em->newHydrator("cache_object");
$obj = $hydrator->executeQuery($query);
$em->clear();
$query = $em->createQuery("SELECT t FROM TestObject t");
$query->setHint('whitewashing.object_cache_key', 'test_obj1');
$hydrator = $em->newHydrator("cache_object");
$obj = $hydrator->executeQuery($query);
var_dump($em->contains($obj[0])); // returns true, its in the identity map, yay!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.