@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 129 lines 3.6 kB view raw
1<?php 2 3/** 4 * Cached @{class:PhutilClassMapQuery} which can perform lookups for single 5 * classes efficiently. 6 * 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). 10 * 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. 15 */ 16final class PhabricatorCachedClassMapQuery 17 extends Phobject { 18 19 private $query; 20 private $queryCacheKey; 21 private $mapKeyMethod; 22 private $objectMap; 23 24 public function setClassMapQuery(PhutilClassMapQuery $query) { 25 $this->query = $query; 26 return $this; 27 } 28 29 public function setMapKeyMethod($method) { 30 $this->mapKeyMethod = $method; 31 return $this; 32 } 33 34 public function loadClasses(array $values) { 35 $cache = PhabricatorCaches::getRuntimeCache(); 36 37 $cache_keys = $this->getCacheKeys($values); 38 $cache_map = $cache->getKeys($cache_keys); 39 40 $results = array(); 41 $writes = array(); 42 foreach ($cache_keys as $value => $cache_key) { 43 if (isset($cache_map[$cache_key])) { 44 $class_name = $cache_map[$cache_key]; 45 try { 46 $result = $this->newObject($class_name); 47 if ($this->getObjectMapKey($result) === $value) { 48 $results[$value] = $result; 49 continue; 50 } 51 } catch (Exception $ex) { 52 // Keep going, we'll handle this immediately below. 53 } 54 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); 59 } 60 61 if ($this->objectMap === null) { 62 $this->objectMap = $this->newObjectMap(); 63 } 64 65 if (isset($this->objectMap[$value])) { 66 $results[$value] = $this->objectMap[$value]; 67 $writes[$cache_key] = get_class($results[$value]); 68 } 69 } 70 71 if ($writes) { 72 $cache->setKeys($writes); 73 } 74 75 return $results; 76 } 77 78 public function loadClass($value) { 79 $result = $this->loadClasses(array($value)); 80 return idx($result, $value); 81 } 82 83 private function getCacheKeys(array $values) { 84 if ($this->queryCacheKey === null) { 85 $this->queryCacheKey = $this->query->getCacheKey(); 86 } 87 88 $key = $this->queryCacheKey; 89 $method = $this->mapKeyMethod; 90 91 $keys = array(); 92 foreach ($values as $value) { 93 $keys[$value] = "classmap({$key}).{$method}({$value})"; 94 } 95 96 return $keys; 97 } 98 99 private function newObject($class_name) { 100 return newv($class_name, array()); 101 } 102 103 private function newObjectMap() { 104 $map = $this->query->execute(); 105 106 $result = array(); 107 foreach ($map as $object) { 108 $value = $this->getObjectMapKey($object); 109 if (isset($result[$value])) { 110 $other = $result[$value]; 111 throw new Exception( 112 pht( 113 'Two objects (of classes "%s" and "%s") generate the same map '. 114 'value ("%s"). Each object must generate a unique map value.', 115 get_class($object), 116 get_class($other), 117 $value)); 118 } 119 $result[$value] = $object; 120 } 121 122 return $result; 123 } 124 125 private function getObjectMapKey($object) { 126 return call_user_func(array($object, $this->mapKeyMethod)); 127 } 128 129}