diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2031,6 +2031,7 @@ 'PhabricatorCacheSetupCheck' => 'applications/config/check/PhabricatorCacheSetupCheck.php', 'PhabricatorCacheSpec' => 'applications/cache/spec/PhabricatorCacheSpec.php', 'PhabricatorCacheTTLGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php', + 'PhabricatorCachedClassMapQuery' => 'applications/cache/PhabricatorCachedClassMapQuery.php', 'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php', 'PhabricatorCachesTestCase' => 'applications/cache/__tests__/PhabricatorCachesTestCase.php', 'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php', @@ -6889,6 +6890,7 @@ 'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorCacheSpec' => 'Phobject', 'PhabricatorCacheTTLGarbageCollector' => 'PhabricatorGarbageCollector', + 'PhabricatorCachedClassMapQuery' => 'Phobject', 'PhabricatorCaches' => 'Phobject', 'PhabricatorCachesTestCase' => 'PhabricatorTestCase', 'PhabricatorCalendarApplication' => 'PhabricatorApplication', diff --git a/src/applications/cache/PhabricatorCachedClassMapQuery.php b/src/applications/cache/PhabricatorCachedClassMapQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/cache/PhabricatorCachedClassMapQuery.php @@ -0,0 +1,129 @@ +query = $query; + return $this; + } + + public function setMapKeyMethod($method) { + $this->mapKeyMethod = $method; + return $this; + } + + public function loadClasses(array $values) { + $cache = PhabricatorCaches::getRuntimeCache(); + + $cache_keys = $this->getCacheKeys($values); + $cache_map = $cache->getKeys($cache_keys); + + $results = array(); + $writes = array(); + foreach ($cache_keys as $value => $cache_key) { + if (isset($cache_map[$cache_key])) { + $class_name = $cache_map[$cache_key]; + try { + $result = $this->newObject($class_name); + if ($this->getObjectMapKey($result) === $value) { + $results[$value] = $result; + continue; + } + } catch (Exception $ex) { + // Keep going, we'll handle this immediately below. + } + + // If we didn't "continue;" above, there was either a direct issue with + // the cache or the cached class did not generate the correct map key. + // Wipe the cache and pretend we missed. + $cache->deleteKey($cache_key); + } + + if ($this->objectMap === null) { + $this->objectMap = $this->newObjectMap(); + } + + if (isset($this->objectMap[$value])) { + $results[$value] = $this->objectMap[$value]; + $writes[$cache_key] = get_class($results[$value]); + } + } + + if ($writes) { + $cache->setKeys($writes); + } + + return $results; + } + + public function loadClass($value) { + $result = $this->loadClasses(array($value)); + return idx($result, $value); + } + + private function getCacheKeys(array $values) { + if ($this->queryCacheKey === null) { + $this->queryCacheKey = $this->query->getCacheKey(); + } + + $key = $this->queryCacheKey; + $method = $this->mapKeyMethod; + + $keys = array(); + foreach ($values as $value) { + $keys[$value] = "classmap({$key}).{$method}({$value})"; + } + + return $keys; + } + + private function newObject($class_name) { + return newv($class_name, array()); + } + + private function newObjectMap() { + $map = $this->query->execute(); + + $result = array(); + foreach ($map as $object) { + $value = $this->getObjectMapKey($object); + if (isset($result[$value])) { + $other = $result[$value]; + throw new Exception( + pht( + 'Two objects (of classes "%s" and "%s") generate the same map '. + 'value ("%s"). Each object must generate a unique map value.', + get_class($object), + get_class($other), + $value)); + } + $result[$value] = $object; + } + + return $result; + } + + private function getObjectMapKey($object) { + return call_user_func(array($object, $this->mapKeyMethod)); + } + +}