/**
  * @task cache
  */
 private function getUserCacheQueryParts(AphrontDatabaseConnection $conn)
 {
     $cache_selects = array();
     $cache_joins = array();
     $cache_map = array();
     $keys = array();
     $types_map = array();
     $cache_types = PhabricatorUserCacheType::getAllCacheTypes();
     foreach ($cache_types as $cache_type) {
         foreach ($cache_type->getAutoloadKeys() as $autoload_key) {
             $keys[] = $autoload_key;
             $types_map[$autoload_key] = $cache_type;
         }
     }
     $cache_table = id(new PhabricatorUserCache())->getTableName();
     $cache_idx = 1;
     foreach ($keys as $key) {
         $join_as = 'ucache_' . $cache_idx;
         $select_as = 'ucache_' . $cache_idx . '_v';
         $cache_selects[] = qsprintf($conn, '%T.cacheData %T', $join_as, $select_as);
         $cache_joins[] = qsprintf($conn, 'LEFT JOIN %T AS %T ON u.phid = %T.userPHID
       AND %T.cacheIndex = %s', $cache_table, $join_as, $join_as, $join_as, PhabricatorHash::digestForIndex($key));
         $cache_map[$select_as] = $key;
         $cache_idx++;
     }
     if ($cache_selects) {
         $cache_selects = ', ' . implode(', ', $cache_selects);
     } else {
         $cache_selects = '';
     }
     if ($cache_joins) {
         $cache_joins = implode(' ', $cache_joins);
     } else {
         $cache_joins = '';
     }
     return array($cache_selects, $cache_joins, $cache_map, $types_map);
 }
 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);
 }