forked from phacility/phabricator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPhabricatorCachedClassMapQuery.php
129 lines (106 loc) · 3.54 KB
/
PhabricatorCachedClassMapQuery.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?php
/**
* Cached @{class:PhutilClassMapQuery} which can perform lookups for single
* classes efficiently.
*
* Some class trees (like Conduit methods and PHID types) contain a huge number
* of classes but are frequently accessed by looking for a specific class by
* a known identifier (like a Conduit method name or a PHID type constant).
*
* Loading the entire class map for these cases has a small but measurable
* performance cost. Instead, we can build a cache from each Conduit method
* name to just the class required to serve that request. This means that we
* load fewer classes and have less overhead to execute API calls.
*/
final class PhabricatorCachedClassMapQuery
extends Phobject {
private $query;
private $queryCacheKey;
private $mapKeyMethod;
private $objectMap;
public function setClassMapQuery(PhutilClassMapQuery $query) {
$this->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));
}
}