/**
  * @param array $cacheKeys
  * @param callable $loadCallback
  * @return array
  */
 public function getByKey(array $cacheKeys, $loadCallback)
 {
     if (!$cacheKeys) {
         return array();
     }
     $result = array();
     $multiRes = $this->cache->getMulti(array_keys($cacheKeys));
     if ($multiRes === false) {
         // Falls through to query only backend
         wfDebugLog('Flow', __METHOD__ . ': Failure querying memcache');
     } else {
         // Memcached BagOStuff only returns found keys, but the redis bag
         // returns false for not found keys.
         $multiRes = array_filter($multiRes, function ($val) {
             return $val !== false;
         });
         foreach ($multiRes as $key => $value) {
             $idx = $cacheKeys[$key];
             if ($idx instanceof UUID) {
                 $idx = $idx->getAlphadecimal();
             }
             $result[$idx] = $value;
             unset($cacheKeys[$key]);
         }
     }
     if (count($cacheKeys) === 0) {
         return $result;
     }
     $res = call_user_func($loadCallback, array_values($cacheKeys));
     if (!$res) {
         // storage failure of some sort
         return $result;
     }
     $invCacheKeys = array();
     foreach ($cacheKeys as $cacheKey => $id) {
         if ($id instanceof UUID) {
             $id = $id->getAlphadecimal();
         }
         $invCacheKeys[$id] = $cacheKey;
     }
     foreach ($res as $id => $row) {
         // If we failed contacting memcache a moment ago don't bother trying to
         // push values either.
         if ($multiRes !== false) {
             $this->cache->set($invCacheKeys[$id], $row);
         }
         $result[$id] = $row;
     }
     return $result;
 }
 /**
  * @expectedException \Flow\Exception\DataModelException
  */
 public function testFailingInsert()
 {
     global $wgFlowCacheTime;
     // Catch the exception and test the cache result then re-throw the exception,
     // otherwise the exception would skip the cache result test
     $cache = new BufferedCache(new BufferedBagOStuff(new \HashBagOStuff()), $wgFlowCacheTime);
     try {
         $treeRepository = new TreeRepository($this->mockDbFactory(false), $cache);
         $this->assertNull($treeRepository->insert($this->descendant, $this->ancestor));
     } catch (\Exception $e) {
         $reflection = new ReflectionClass('\\Flow\\Repository\\TreeRepository');
         $method = $reflection->getMethod('cacheKey');
         $method->setAccessible(true);
         $this->assertSame($cache->get($method->invoke($treeRepository, 'rootpath', $this->descendant)), false);
         $this->assertSame($cache->get($method->invoke($treeRepository, 'parent', $this->descendant)), false);
         throw $e;
     }
 }
 /**
  * Given a list of nodes, find the path from each node to the root of its tree.
  * the root must be the first element of the array, $node must be the last element.
  * @param UUID[] $descendants Array of UUID objects to find the root paths for.
  * @return UUID[][] Associative array, key is the post ID in hex, value is the path as an array.
  */
 public function findRootPaths(array $descendants)
 {
     // alphadecimal => cachekey
     $cacheKeys = array();
     // alphadecimal => cache result ( distance => parent uuid obj )
     $cacheValues = array();
     // list of binary values for db query
     $missingValues = array();
     // alphadecimal => distance => parent uuid obj
     $paths = array();
     foreach ($descendants as $descendant) {
         $cacheKeys[$descendant->getAlphadecimal()] = $this->cacheKey('rootpath', $descendant);
     }
     $cacheResult = $this->cache->getMulti(array_values($cacheKeys));
     foreach ($descendants as $descendant) {
         $alpha = $descendant->getAlphadecimal();
         if (isset($cacheResult[$cacheKeys[$alpha]])) {
             $cacheValues[$alpha] = $cacheResult[$cacheKeys[$alpha]];
         } else {
             $missingValues[] = $descendant->getBinary();
             $paths[$alpha] = array();
         }
     }
     if (!count($missingValues)) {
         return $cacheValues;
     }
     $dbr = $this->dbFactory->getDB(DB_SLAVE);
     $res = $dbr->select($this->tableName, array('tree_descendant_id', 'tree_ancestor_id', 'tree_depth'), array('tree_descendant_id' => $missingValues), __METHOD__);
     if (!$res || $res->numRows() === 0) {
         return $cacheValues;
     }
     foreach ($res as $row) {
         $alpha = UUID::create($row->tree_descendant_id)->getAlphadecimal();
         $paths[$alpha][$row->tree_depth] = UUID::create($row->tree_ancestor_id);
     }
     foreach ($paths as $descendantId => &$path) {
         if (!$path) {
             $path = null;
             continue;
         }
         // sort by reverse distance, so furthest away
         // parent (root) is at position 0.
         ksort($path);
         $path = array_reverse($path);
         $this->cache->set($cacheKeys[$descendantId], $path);
     }
     return $paths + $cacheValues;
 }
 /**
  * Returns a boolean true/false if the findMulti()-operation for the given
  * attributes has already been resolves and doesn't need to query any
  * outside cache/database.
  * Determining if a find() has not yet been resolved may be useful so that
  * additional data may be loaded at once.
  *
  * @param array $queries Queries to findMulti()
  * @param array[optional] $options Options to findMulti()
  * @return bool
  */
 public function foundMulti(array $queries, array $options = array())
 {
     if (!$queries) {
         return true;
     }
     // get cache keys for all queries
     $cacheKeys = $this->getCacheKeys($queries);
     // check if cache has a way of identifying what's stored locally
     if (!method_exists($this->cache, 'has')) {
         return false;
     }
     // check if keys matching given queries are already known in local cache
     foreach ($cacheKeys as $key) {
         if (!$this->cache->has($key)) {
             return false;
         }
     }
     $keyToQuery = array();
     foreach ($cacheKeys as $i => $key) {
         // These results will be merged into the query results, and as such need binary
         // uuid's as would be received from storage
         if (!isset($keyToQuery[$key])) {
             $keyToQuery[$key] = $queries[$i];
         }
     }
     // retrieve from cache - this is cheap, it's is local storage
     $cached = $this->cache->getMulti($cacheKeys);
     foreach ($cached as $i => $result) {
         $limit = isset($options['limit']) ? $options['limit'] : $this->getLimit();
         $cached[$i] = array_splice($result, 0, $limit);
     }
     // if we have a shallow compactor, the returned data are PKs of objects
     // that need to be fetched too
     if ($this->rowCompactor instanceof ShallowCompactor) {
         // test of the keys to be expanded are already in local cache
         $duplicator = $this->rowCompactor->getResultDuplicator($cached, $keyToQuery);
         $queries = $duplicator->getUniqueQueries();
         if (!$this->rowCompactor->getShallow()->foundMulti($queries)) {
             return false;
         }
     }
     return true;
 }