/CachedObjectHydrator.php Secret
Created
October 17, 2011 22:51
Star
You must be signed in to star a gist
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# For Symfony integration | |
doctrine: | |
orm: | |
default: | |
hydrators: | |
cache_object: Vendor\Namespace\To\CachedObjectHydrator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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