private function fillUserCaches(array $users)
 {
     if (!$this->cacheKeys) {
         return;
     }
     $user_map = mpull($users, null, 'getPHID');
     $keys = array_keys($this->cacheKeys);
     $hashes = array();
     foreach ($keys as $key) {
         $hashes[] = PhabricatorHash::digestForIndex($key);
     }
     $types = PhabricatorUserCacheType::getAllCacheTypes();
     // First, pull any available caches. If we wanted to be particularly clever
     // we could do this with JOINs in the main query.
     $cache_table = new PhabricatorUserCache();
     $cache_conn = $cache_table->establishConnection('r');
     $cache_data = queryfx_all($cache_conn, 'SELECT cacheKey, userPHID, cacheData, cacheType FROM %T
     WHERE cacheIndex IN (%Ls) AND userPHID IN (%Ls)', $cache_table->getTableName(), $hashes, array_keys($user_map));
     $skip_validation = array();
     // After we read caches from the database, discard any which have data that
     // invalid or out of date. This allows cache types to implement TTLs or
     // versions instead of or in addition to explicit cache clears.
     foreach ($cache_data as $row_key => $row) {
         $cache_type = $row['cacheType'];
         if (isset($skip_validation[$cache_type])) {
             continue;
         }
         if (empty($types[$cache_type])) {
             unset($cache_data[$row_key]);
             continue;
         }
         $type = $types[$cache_type];
         if (!$type->shouldValidateRawCacheData()) {
             $skip_validation[$cache_type] = true;
             continue;
         }
         $user = $user_map[$row['userPHID']];
         $raw_data = $row['cacheData'];
         if (!$type->isRawCacheDataValid($user, $row['cacheKey'], $raw_data)) {
             unset($cache_data[$row_key]);
             continue;
         }
     }
     $need = array();
     $cache_data = igroup($cache_data, 'userPHID');
     foreach ($user_map as $user_phid => $user) {
         $raw_rows = idx($cache_data, $user_phid, array());
         $raw_data = ipull($raw_rows, 'cacheData', 'cacheKey');
         foreach ($keys as $key) {
             if (isset($raw_data[$key]) || array_key_exists($key, $raw_data)) {
                 continue;
             }
             $need[$key][$user_phid] = $user;
         }
         $user->attachRawCacheData($raw_data);
     }
     // If we missed any cache values, bulk-construct them now. This is
     // usually much cheaper than generating them on-demand for each user
     // record.
     if (!$need) {
         return;
     }
     $writes = array();
     foreach ($need as $cache_key => $need_users) {
         $type = PhabricatorUserCacheType::getCacheTypeForKey($cache_key);
         if (!$type) {
             continue;
         }
         $data = $type->newValueForUsers($cache_key, $need_users);
         foreach ($data as $user_phid => $raw_value) {
             $data[$user_phid] = $raw_value;
             $writes[] = array('userPHID' => $user_phid, 'key' => $cache_key, 'type' => $type, 'value' => $raw_value);
         }
         foreach ($need_users as $user_phid => $user) {
             if (isset($data[$user_phid]) || array_key_exists($user_phid, $data)) {
                 $user->attachRawCacheData(array($cache_key => $data[$user_phid]));
             }
         }
     }
     PhabricatorUserCache::writeCaches($writes);
 }
 private function purgeUserCache()
 {
     $table = new PhabricatorUserCache();
     $conn_w = $table->establishConnection('w');
     queryfx($conn_w, 'TRUNCATE TABLE %T', $table->getTableName());
 }