Skip to content

Instantly share code, notes, and snippets.

@xicond
Last active October 24, 2017 03:01
Show Gist options
  • Save xicond/5c3392bbfe3eac2725bc7dc97806ee36 to your computer and use it in GitHub Desktop.
Save xicond/5c3392bbfe3eac2725bc7dc97806ee36 to your computer and use it in GitHub Desktop.
<?php
namespace app\components;
use Yii;
use yii\base\Exception;
class ApiCached extends ApiWrapper
{
const DEFAULT_CACHE_DURATION = 600;
/**
* @param string $name
* @param array $args
* @return mixed|null|string
*/
public function __call($name, $args)
{
switch ($name) {
case 'get':
return $this->getCachedAPIData($name, $args);
break; // will never get here :)
case 'put':
case 'post':
case 'delete':
return parent::__call($name, $args);
break; // will never get here :)
// @todo: Need to discuss with zeeshan where to put this
case 'getCacheKey':
$cache_key = null;
if (Yii::$app->params['cacheAPIResponse']) { // hard coding the 'get' instead of the name as only the get caches data
$cache_key = $this->generateCacheKey(array_merge(['get'], $args));
}
return $cache_key;
default:
trigger_error('Method does not exists', E_ERROR);
}
return null;
}
/**
* @param $name
* @param $args
* @return mixed|null
*/
private function getCachedAPIData($name, $args)
{
if (Yii::$app->params['cacheAPIResponse']) {
$tmpArgs = $args;
// Do not include Cache duration in generating KEY
if (isset($tmpArgs[2])) {
unset($tmpArgs[2]);
}
$cache_key = $this->generateCacheKey(array_merge([$name], $tmpArgs));
// Attempt to load the data from the cache, based on the key
$res = Yii::$app->cache->get($cache_key);
$doRefresh = (isset($_GET['refreshCache']) && $_GET['refreshCache'] == 'force') ? true : false;
$doForceCache = $doRefresh || !$res;
if ($doForceCache) { // we didn't get it from the cache
$res = parent::__call($name, $args);
if ($res) {
if ($args &&
is_array($args) &&
array_key_exists(1, $args) &&
is_array($args[1]) &&
array_key_exists('callback_filter_action', $args[1])) {
try {
$res = call_user_func($args[1]['callback_filter_action'], $res);
} catch (Exception $e) {
Yii::error($e);
}
}
$this->setCachedAPIData($cache_key, $res);
} else {
if ($doRefresh) {
Yii::$app->cache->delete($cache_key);
}
}
}
return $res;
} else {
return parent::__call($name, $args);
}
}
/**
* @param $args
* @return string
*/
private function generateCacheKey($args)
{
$string = (is_array($args) || is_object($args)) ? json_encode($args) : $args;
return md5($string);
}
/**
* @param $cacheKey
* @param $data
*/
private function setCachedAPIData($cacheKey, $data)
{
if (!is_null($this->cacheDuration)) {
$cache = ($this->cacheDuration ?: (Yii::$app->params['cacheAPIDuration'] ?: self::DEFAULT_CACHE_DURATION));
Yii::$app->cache->set($cacheKey, $data, $cache);
}
}
}
<?php
namespace app\components;
require_once(__DIR__ . '/../vendor/autoload.php');
use Yii;
use yii\base\Component;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\RequestException;
class ApiWrapper extends Component
{
private $__request;
protected $baseUrl;
protected $headers = [];
protected $cacheDuration = null;
// Default OK
public $responseCode = 200;
public $response = null;
/**
* @param string $baseUrl
* @param array $headers
*/
public function __construct($baseUrl = '', $headers = [])
{
$this->baseUrl = $baseUrl;
$this->headers = $headers;
}
/**
* @param $type
* @param $url
* @param null $parameter
* @param array $headers
* @param bool $json
* @return null|string
*/
private function restCall($type, $url, $parameter = null, $headers = [], $json = false)
{
if (!$url) {
Yii::error("Parameter $url cannot be empty");
return null;
}
$client = new Client(['base_url' => $this->baseUrl]);
unset($parameter['files']);
try {
$requestTimeout = Yii::$app->params['timeout']['request'];
if (isset($parameter['request_timeout'])) {
$requestTimeout = $parameter['request_timeout'];
unset($parameter['request_timeout']);
}
$key = $type == 'get' ? 'query' : 'body';
$parameter = is_array($parameter) ? ["$key" => $parameter] : [];
$headers = (empty($headers)) ? $this->headers : array_merge($this->headers, $headers);
$headers['client_user_agent'] = (Yii::$app->request->getIsConsoleRequest()) ? 'console-request' : Yii::$app->request->getUserAgent();
$parameter['headers'] = $headers;
$parameter['timeout'] = $requestTimeout;
$parameter['connect_timeout'] = Yii::$app->params['timeout']['connection'];
if ($json) {
$parameter["$key"] = json_encode($parameter["$key"]);
$parameter['headers']['content-type'] = 'application/json';
}
// This is for Debugging errors
$GLOBALS['_API'] = ['url' => $url, 'parameter' => $parameter];
$this->__request = $client->$type($url, $parameter);
// $this->__request->addHeader('Accept-Encoding', 'compress, gzip');
return (string)$this->__request->getBody();
} catch (ServerException $e) {
Yii::error('Server error on API:::' . print_r($e->getMessage(), true));
$this->responseCode = $e->getResponse()->getStatusCode();
$this->response = $e->getResponse();
return null;
} catch (ClientException $e) {
if ($e->getResponse()->getStatusCode() != '404') {
Yii::error('Client error on API:::' . print_r($e->getMessage(), true));
}
$this->responseCode = $e->getResponse()->getStatusCode();
$this->response = $e->getResponse();
return null;
} catch (RequestException $e) {
Yii::error('Request error on API:::' . print_r($e->getMessage(), true));
return null;
} catch (\Exception $e) {
Yii::error('Error on API:::' . print_r($e->getMessage(), true));
return null;
}
}
/**
* Make a Http GET call to babase on url + uri
*
* @param string $uri Uri to call base on base url
* @param array $parameter Model's attribute
* @param int $cacheDuration
* @param bool $json
* @param array $headers
*
* @return array array('error' => null, 'result' => null) array Error or result
*/
private function get($uri, $parameter = null, $cacheDuration = null, $json = false, $headers = [])
{
$this->cacheDuration = $cacheDuration;
return $this->restCall('get', $uri, $parameter, $headers, $json);
}
/**
* Make a Http POST call to babase on url + uri
*
* @param string $uri Uri to call base on base url
* @param array $parameter Model's attribute
* @param bool $json
* @param array $headers
* @internal param array $header
*
* @return array array('error' => null, 'result' => null) array Error or result
*/
private function post($uri, $parameter = null, $json = false, $headers = [])
{
return $this->restCall('post', $uri, $parameter, $headers, $json);
}
/**
* Make a Http PUT call to babase on url + uri
*
* @param string $uri Uri to call base on base url
* @param array $parameter Model's attribute
*
* @return array array('error' => null, 'result' => null) array Error or result
*/
private function put($uri, $parameter = null)
{
return $this->restCall('put', $uri, $parameter);
}
/**
* Make a Http DELETE call base on base url + uri
*
* @param string $uri Uri to call base on base url
* @param array $parameter Model's attribute
*
* @return array array('error' => null, 'result' => null) array Error or result
*/
private function delete($uri, $parameter = null)
{
return $this->restCall('delete', $uri, $parameter);
}
/**
* @param $value
*/
public function setBaseUrl($value)
{
$this->baseUrl = $value;
}
/**
* @return string
*/
public function getBaseUrl()
{
return $this->baseUrl;
}
// this exists for technical reasons. only allow get,put,post,delete functions through
// these functions are not public so that any inherited class can implement its own __call
// without having to override each and every function
/**
* @param string $name
* @param array $args
* @return mixed|null
*/
public function __call($name, $args)
{
switch ($name) {
case 'get':
case 'put':
case 'post':
case 'delete':
return call_user_func_array([$this, $name], $args);
break; // will never get here :)
default:
trigger_error('Method does not exists', E_ERROR);
}
return null;
}
/**
* @param $headers
*/
public function setDefaultHeaders($headers)
{
$this->headers = $headers;
}
}
<?php
use Codeception\Util\Stub;
// extends \PHPUnit_Framework_TestCase
class FIPTest extends \Codeception\Test\Unit
{
public $FIPService;
protected $helper;
protected $fbArticles;
protected function setUp()
{
// We create an object of the V3Servcie wihtout running the consturctor
$this->FIPService = Stub::make('app\services\FIPService');
// Mocking FIP Service
$fip = Stub::make('app\services\readers\FIP');
$fbArticles = $this->fbArticles = new \stdClass;
// Mocking ApiCached
$ApiCached = Stub::make('app\components\ApiCached', []);
$reflection = new \ReflectionClass($ApiCached);
$method = $reflection->getMethod('restCall');
$method->setAccessible(true);
$ApiCached->restCall =
function ($type, $url, $parameter = null, $headers = [], $json = false) use (&$fbArticles) {
// Mock(simulate) mechanism from FBIA
if (substr($this->baseUrl, 0, 8) !== 'https://') {
return '{"error":{"message":"You must use https:\/\/ when passing an access token","type":"OAuthException","code":1,"fbtrace_id":"BT0uWlxKOBa"}}';
} elseif ($type == 'post' && !isset($parameter['html_source'])) {
return '{"error":{"message":"(#100) The parameter html_source is required","type":"OAuthException","code":100,"fbtrace_id":"G\/NYWaDfBG3"}}';
} elseif ($type == 'post') {
$dom = new DOMDocument;
$dom->loadHTML($parameter['html_source']);
$cnonical = null;
foreach ($dom->getElementsByTagName('link') as $link) {
/**
* @var DOMElement $link
*/
if ($link->hasAttribute('rel') && $link->hasAttribute('href') && $link->getAttribute('rel')=='canonical') {
$canonical = $link->getAttribute('href');
}
}
$importID = time();
if (empty($canonical)) {
$fbArticles[$importID] = '{"id":"'.$importID.'","status":"FAILED","errors":[{"level":"ERROR","message":"Missing Article\'s Canonical URL: There is no URL specified for this article. A canonical URL needs to be claimed and placed within the HTML to generate an Instant Article. Refer to URLs under Publishing Articles in the Instant Articles documentation for more information on claiming and inserting a canonical URL."}]}';
} else {
}
return '{"id":"'.$importID.'"}';
} elseif ($type == 'get') {
list($importID,) = explode('/', $url, 2);
if (isset($fbArticles[$importID])) return $fbArticles[$importID];
}
};
// FIP reader, Stub property api to new ApiCached with stubbed restCall
$reflection = new \ReflectionClass($fip);
$property = $reflection->getProperty('api');
$property->setAccessible(true);
$property->setValue($fip, $ApiCached);
// FIPService, Stub property reader to new FIP stubbed service
$reflection = new \ReflectionClass($this->FIPService);
$property = $reflection->getProperty('reader');
$property->setAccessible(true);
$property->setValue($this->FIPService, $fip);
$this->helper = $this->getModule('\Helper\Unit');
}
protected function tearDown()
{
}
// add
public function testAddArticle()
{
// $this->helper->arrays_are_similar([],[]);
$import = $this->FIPService->setArticle('<html><head></head><body></body></html>');
$this->assertNotContains('error', array_flip((array)$import));
//die(var_dump($import));
if (isset($import->id)) {
$this->assertNotContains('errors', array_flip((array)$this->FIPService->getArticleImportStatus($import->id)));
}
}
// delete
public function testDeleteArticle()
{
}
// update
public function testUpdateArticle()
{
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment