Create a gist now

Instantly share code, notes, and snippets.

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