4 * Cached @{class:PhutilClassMapQuery} which can perform lookups for single
7 * Some class trees (like Conduit methods and PHID types) contain a huge number
8 * of classes but are frequently accessed by looking for a specific class by
9 * a known identifier (like a Conduit method name or a PHID type constant).
11 * Loading the entire class map for these cases has a small but measurable
12 * performance cost. Instead, we can build a cache from each Conduit method
13 * name to just the class required to serve that request. This means that we
14 * load fewer classes and have less overhead to execute API calls.
16 final class PhabricatorCachedClassMapQuery
20 private $queryCacheKey;
21 private $mapKeyMethod;
24 public function setClassMapQuery(PhutilClassMapQuery
$query) {
25 $this->query
= $query;
29 public function setMapKeyMethod($method) {
30 $this->mapKeyMethod
= $method;
34 public function loadClasses(array $values) {
35 $cache = PhabricatorCaches
::getRuntimeCache();
37 $cache_keys = $this->getCacheKeys($values);
38 $cache_map = $cache->getKeys($cache_keys);
42 foreach ($cache_keys as $value => $cache_key) {
43 if (isset($cache_map[$cache_key])) {
44 $class_name = $cache_map[$cache_key];
46 $result = $this->newObject($class_name);
47 if ($this->getObjectMapKey($result) === $value) {
48 $results[$value] = $result;
51 } catch (Exception
$ex) {
52 // Keep going, we'll handle this immediately below.
55 // If we didn't "continue;" above, there was either a direct issue with
56 // the cache or the cached class did not generate the correct map key.
57 // Wipe the cache and pretend we missed.
58 $cache->deleteKey($cache_key);
61 if ($this->objectMap
=== null) {
62 $this->objectMap
= $this->newObjectMap();
65 if (isset($this->objectMap
[$value])) {
66 $results[$value] = $this->objectMap
[$value];
67 $writes[$cache_key] = get_class($results[$value]);
72 $cache->setKeys($writes);
78 public function loadClass($value) {
79 $result = $this->loadClasses(array($value));
80 return idx($result, $value);
83 private function getCacheKeys(array $values) {
84 if ($this->queryCacheKey
=== null) {
85 $this->queryCacheKey
= $this->query
->getCacheKey();
88 $key = $this->queryCacheKey
;
89 $method = $this->mapKeyMethod
;
92 foreach ($values as $value) {
93 $keys[$value] = "classmap({$key}).{$method}({$value})";
99 private function newObject($class_name) {
100 return newv($class_name, array());
103 private function newObjectMap() {
104 $map = $this->query
->execute();
107 foreach ($map as $object) {
108 $value = $this->getObjectMapKey($object);
109 if (isset($result[$value])) {
110 $other = $result[$value];
113 'Two objects (of classes "%s" and "%s") generate the same map '.
114 'value ("%s"). Each object must generate a unique map value.',
119 $result[$value] = $object;
125 private function getObjectMapKey($object) {
126 return call_user_func(array($object, $this->mapKeyMethod
));