/** * Gets the id for a given name * * @param string $name for which the ID should be returned * * @return int * @throws \iveeCore\Exceptions\KeyNotFoundInCacheException if the names to ID array isn't found in pool or cache * @throws \iveeCore\Exceptions\TypeNameNotFoundException if the given name is not found in the mapping array */ public function getIdByName($name) { if (empty($this->nameToId)) { if ($this->cache instanceof ICache) { //try getting mapping array from cache, will throw an exception if not found $this->nameToId = $this->cache->getItem($this->classNick . 'Names'); } else { $KeyNotFoundInCacheExceptionClass = Config::getIveeClassName('KeyNotFoundInCacheException'); throw new $KeyNotFoundInCacheExceptionClass('Names not found in pool'); } } if (isset($this->nameToId[$name])) { return $this->nameToId[$name]; } $typeNameNotFoundExceptionClass = Config::getIveeClassName('TypeNameNotFoundException'); throw new $typeNameNotFoundExceptionClass('Type name not found'); }
/** * Gathers multipage endpoint responses and joins them into one array, using the passed callback functions to * traverse and index the data. The result of this (potentially expensive) operation can be cached. * * @param string $endpointHref the URL to the first page of the endpoint * @param callable $indexFunc function to be used to extract the ID from/for an individual Response object * @param callable $elementFunc function to be used to extract the desired data from an individual Response object * @param string $accept the representation to request from CREST * @param bool $cache whether the gathered data should be cached or not * @param int $ttl the time to live to be used for caching of the gathered data * @param string $subCommandKey to avoid cache namespace collisions when different gather requests access the same * endpoint URL, an additional subcommand key can be specified. * * @return array */ public function gather($endpointHref, callable $indexFunc = null, callable $elementFunc = null, $accept = null, $cache = true, $ttl = 3600, $subCommandKey = null) { $dataKey = 'gathered:' . $endpointHref . (isset($subCommandKey) ? ',' . $subCommandKey : ''); //we introduce another caching layer here because gathering and reindexing multipage data is expensive, even //when the individual CREST responses are already cached. try { $dataObj = $this->cache->getItem($dataKey); } catch (KeyNotFoundInCacheException $e) { //setup a cacheable array object $dataClass = Config::getIveeClassName('CacheableArray'); $dataObj = new $dataClass($dataKey, time() + $ttl); //gather all the pages into one compact array $dataObj->data = $this->gather2($endpointHref, $indexFunc, $elementFunc, $accept, false); if ($cache) { $this->cache->setItem($dataObj); } } return $dataObj->data; }
/** * Gets the key for a given name. * * @param string $classTypeNamesKey specific to the class of objects we want to look in * @param string $name for which the key should be returned * * @return string * @throws \iveeCore\Exceptions\KeyNotFoundInCacheException if the names to key array isn't found in pool or cache * @throws \iveeCore\Exceptions\TypeNameNotFoundException if the given name is not found in the mapping array * @throws \iveeCore\Exceptions\WrongTypeException if object returned from cache is not CacheableArray */ public function getKeyByName($classTypeNamesKey, $name) { if (empty($this->nameToKey[$classTypeNamesKey])) { if ($this->cache instanceof ICache) { //try getting mapping array from cache, will throw an exception if not found $cacheableArray = $this->cache->getItem($classTypeNamesKey); if (!$cacheableArray instanceof CacheableArray) { $WrongTypeExceptionClass = Config::getIveeClassName('WrongTypeException'); throw new $WrongTypeExceptionClass('Object given is not CacheableArray'); } $this->nameToKey[$classTypeNamesKey] = $cacheableArray->data; } else { $KeyNotFoundInCacheExceptionClass = Config::getIveeClassName('KeyNotFoundInCacheException'); throw new $KeyNotFoundInCacheExceptionClass('Names not found in pool'); } } if (isset($this->nameToKey[$classTypeNamesKey][$name])) { return $this->nameToKey[$classTypeNamesKey][$name]; } $typeNameNotFoundExceptionClass = Config::getIveeClassName('TypeNameNotFoundException'); throw new $typeNameNotFoundExceptionClass('Type name not found'); }
/** * Performs parallel asynchronous GET requests using callbacks to process incoming responses. * * @param array $hrefs the hrefs to request * @param array $header the header to be passed in all requests * @param callable $getAuthHeader that returns an appropriate bearer authentication header, for instance * Client::getBearerAuthHeader(). We do this on-the-fly as during large multi-GET batches the access token might * expire. * @param callable $callback a function expecting one iveeCrest\Response object as argument, called for every * successful response * @param callable $errCallback a function expecting one iveeCrest\Response object as argument, called for every * non-successful response * @param bool $cache whether the individual Responses should be cached * * @return void * @throws \iveeCrest\Exceptions\IveeCrestException on general CURL error */ public function asyncMultiGet(array $hrefs, array $header, callable $getAuthHeader, callable $callback, callable $errCallback = null, $cache = true) { //This method is fairly complex in part due to the tricky and ugly interface of multi-curl and moving window //logic. Ideas or patches how to make it nicer welcome! //separate hrefs that are already cached from those that need to be requested $hrefsToQuery = []; $keysToQuery = []; foreach ($hrefs as $href) { $responseKey = md5($this->refreshToken . '_get:' . $href); try { $callback($this->cache->getItem($responseKey)); } catch (KeyNotFoundInCacheException $e) { $hrefsToQuery[] = $href; $keysToQuery[] = $responseKey; } } // make sure the rolling window isn't greater than the number of hrefs $rollingWindow = count($hrefsToQuery) > 10 ? 10 : count($hrefsToQuery); //CURL options for all requests $stdOptions = array(CURLOPT_RETURNTRANSFER => true, CURLOPT_USERAGENT => $this->userAgent, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_CIPHER_LIST => 'TLSv1', CURLOPT_HTTPHEADER => $header); $responses = []; $master = curl_multi_init(); //setup the first batch of requests for ($i = 0; $i < $rollingWindow; $i++) { $href = $hrefsToQuery[$i]; $responses[$href] = $this->addHandleToMulti($master, $href, $keysToQuery[$i], $stdOptions, $getAuthHeader, $header); } $crestExceptionClass = Config::getIveeClassName('IveeCrestException'); $running = false; do { //execute whichever handles need to be started do { $execrun = curl_multi_exec($master, $running); } while ($execrun == CURLM_CALL_MULTI_PERFORM); if ($execrun != CURLM_OK) { throw new $crestExceptionClass("CURL Multi-GET error", $execrun); } //block until we have anything on at least one of the handles curl_multi_select($master); //a request returned, process it while ($done = curl_multi_info_read($master)) { $info = curl_getinfo($done['handle']); //find the Response object matching the URL $res = $responses[$info['url']]; //set info and content to Response object $res->setContentAndInfo(curl_multi_getcontent($done['handle']), $info); //execute the callbacks passing the response as argument if ($info['http_code'] == 200) { //cache it if configured if ($cache) { $this->cache->setItem($res); } $callback($res); } elseif (isset($errCallback)) { $errCallback($res); } else { throw new $crestExceptionClass('CREST http error ' . $info['http_code']); } //remove the reference to response to conserve memory on large batches $responses[$info['url']] = null; //start a new request (it's important to do this before removing the old one) if ($i < count($hrefsToQuery)) { $href = $hrefsToQuery[$i]; $responses[$href] = $this->addHandleToMulti($master, $href, $keysToQuery[$i], $stdOptions, $getAuthHeader, $header); $i++; } //remove the curl handle that just completed curl_multi_remove_handle($master, $done['handle']); } //don't waste too many CPU cycles on looping usleep(1000); //TODO: implement proper rate limiting } while ($running > 0); curl_multi_close($master); }