I've developed an plugin where i make use of shortcodes. One of these shortcodes retrieves external information through communicate with an API.
The function that's called by the shortcode, communicates several times with the API. How could i cache all separate calls.
Let me try to explain it with some code
Example code
public function generateData($atts, $content = null, $tag = null){
// $this->API is a extended class to retrieve the API data
// example one calling the api
$data1 = $this->API->getData(array(
'teamlevel' => 'beginner'
));
$data2 = $this->API->getData(array(
'competition' => 'baseball'
));
}
add_shortcode('getdata', 'generateData');
class API {
public function getData($params){
// communicate with the api en return the data
}
}
Above code is working but without caching. Now i've tried to make use of wordpress get_transient and set_transient on the following way
public function generateData($atts, $content = null, $tag = null){
// $this->API is a extended class to retrieve the API data
// example one calling the api
// new chaining method cache
// every data call could be cached, i thought this could be based on shortcode name ($tag)
// set the shortcode name
$this->API->cacheShortcodeName = $tag; // getdata
$data1 = $this->API->cache()->getData(array(
'teamlevel' => 'beginner'
));
$data2 = $this->API->cache()->getData(array(
'competition' => 'baseball'
));
}
add_shortcode('getdata', 'generateData');
class API {
private $cacheDuration;
protected $cacheShortcodeName;
public function getData($params){
// before communication with the api check if there is cache with hasCache
// 1. $this->hasCache
// if result is true return cached data
// 2. return cached data
// else communicatie with the API en set new cache data
// 3. retrieve data from api en set new cached data
}
// default cache duration (1 day) in seconds
public function cache($cacheDuration = 86400){
$this->cacheDuration = $cacheDuration;
return $this;
}
public function hasCache(){
// check if there is cache
$cache = get_transient(md5($this->cacheShortcodeName));
if(!empty($cache)){
return true;
}
return false;
}
public function setCache($data){
set_transient(md5($this->cacheShortcodeName), $data, $this->cacheDuration);
}
public function getCache(){
$cache = get_transient(md5($this->cacheShortcodeName));
return $cache;
}
}
But the problem is when the cache is saved within the function setCache each time the shortcode name is set, and therefore i think its overriding itself. How could i store every call separately?
Related
I'm trying to create a custom omnipay driver for a local gateway called creditguard.
For this gateway you need to post the data to the endpoint and get back a redirect url for the payment form.
My question is how do you post and get the response before making the purchase?
Edit:
Gateway.php
class Gateway extends AbstractGateway
{
public function getName()
{
return 'Creditguard';
}
public function getDefaultParameters()
{
return array();
}
public function getEndpoint()
{
return 'https://verifonetest.creditguard.co.il/xpo/Relay';
}
public function purchase(array $parameters = array())
{
return $this->createRequest('\Nirz\Creditguard\Message\PurchaseRequest', $parameters);
}
public function completePurchase(array $parameters = array())
{
return $this->createRequest('\Nirz\Creditguard\Message\CompletePurchaseRequest', $parameters);
}
}
PurchaseRequest.php
class PurchaseRequest extends AbstractRequest
{
protected $liveEndpoint = 'https://verifonetest.creditguard.co.il/xpo/Relay';
protected $testEndpoint = 'https://verifonetest.creditguard.co.il/xpo/Relay';
public function getData()
{
$this->validate('amount');
// Either the nodifyUrl or the returnUrl can be provided.
// The returnUrl is deprecated, as strictly this is a notifyUrl.
if (!$this->getNotifyUrl()) {
$this->validate('returnUrl');
}
$data = array();
$data['user'] = 'user';
$data['password'] = 'password';
$data['tid'] = '11111111';
$data['mid'] = '111111';
$data['amount'] = '20000';
$data['int_in'] = '<ashrait>
<request>
<version>1000</version>
<language>HEB</language>
<dateTime></dateTime>
<command>doDeal</command>
<doDeal>
<terminalNumber>'.$data['tid'].'</terminalNumber>
<mainTerminalNumber/>
<cardNo>CGMPI</cardNo>
<total>'.$data['amount'].'</total>
<transactionType>Debit</transactionType>
<creditType>RegularCredit</creditType>
<currency>ILS</currency>
<transactionCode>Phone</transactionCode>
<authNumber/>
<numberOfPayments/>
<firstPayment/>
<periodicalPayment/>
<validation>TxnSetup</validation>
<dealerNumber/>
<user>'. $data['user'] .'</user>
<mid>'.$data['mid'].'</mid>
<uniqueid>'.time().rand(100,1000).'</uniqueid>
<mpiValidation>autoComm</mpiValidation>
<email>someone#creditguard.co.il</email>
<clientIP/>
<customerData>
<userData1/>
<userData2/>
<userData3/>
<userData4/>
<userData5/>
<userData6/>
<userData7/>
<userData8/>
<userData9/>
<userData10/>
</customerData>
</doDeal>
</request>
</ashrait>';
return $data;
}
public function sendData($data)
{
// $httpResponse = $this->httpClient->post($this->getEndpoint(), null, $data);
return $this->response = new PurchaseResponse($this, $data);
}
public function getEndpoint()
{
return $this->getTestMode() ? $this->testEndpoint : $this->liveEndpoint;
}
}
PurchaseResponse.php
class PurchaseResponse extends AbstractResponse implements RedirectResponseInterface
{
public function isSuccessful()
{
return false;
}
public function isRedirect()
{
return true;
}
public function getRedirectUrl()
{
// return $this->getRequest()->getEndpoint().'?'.http_build_query($this->data);
return $this->getRequest()->data['mpiHostedPageUrl'];
// return isset($this->data['mpiHostedPageUrl']) ? $this->data['mpiHostedPageUrl'] : null;
}
public function getRedirectMethod()
{
return 'GET';
}
public function getRedirectData()
{
return [];
}
}
Not sure how to get the response's mpiHostedPageUrl so I can redirect to the correct url.
Assuming this is the payment gateway documentation in question.
You just go ahead and make the transaction request, the customer won't be charged as they'll have to authorise it on the next page by entering in their payment details.
The response of that transaction request contains an element mpiHostedPageUrl, which you can see on page 13 of that document, that contains the URL you need to get from the response to provide the redirect.
I know this is very old question. First you need to set 'mpiHostedPageUrl' in your getData method of PurchaseRequest.php and access in PurchaseResponse.php by using $this->data['mpiHostedPageUrl'].
HATEOAS (Hypermedia as the Engine of Application State) is a way to organize the response for REST application.
In HATEOAS world, the response JSON may contains all URL the client may need. Example, in github API, the response contain URL for accessing to repository, user, pull request...
So, I suggest you to call the gateway with the first POST request and then, according to the JSON response, call to provided URL which will represent the redirection.
Another solution could be to use a ZUUL gateway (in Spring-Boot) which will perform the redirection for you.
You can find a description here : https://spring.io/guides/gs/routing-and-filtering/
I am using file cache in yii2 framework.
My question is
Is it possible to add some extra value to cache without refresh the cacheFile.Suppose i create cache file for my products now on each entry i update cache file. I want to add just the new product to cache.
How can i do that thanks in advance
This is my Code
public static function updateCache(){
$product_grid = Yii::$app->db->createCommand("CALL get_products()")->queryAll();
Yii::$app->cache->set('product_grid', $product_grid);
}
I write store procedure for getting all products,now when i add new product each time i am calling the updateCache function which regenerate the products and add it to cache due to which application speed may be effected.
This is the code for addingProduct and updateCache:
public function actionCreate($id = NULL) {
$model = new PrProducts();
if ($model->load(Yii::$app->request->post())) {
$model->save(false);
self::updateCache();
}
}
Native Yii2 cache components doesn't allow to update existing cache items partially.
But you can do this manually:
public static function addToCache($modelProduct) {
$productGrid = Yii::$app->cache->get('productGrid');
$productGrid[$modelProduct->id] = $modelProduct->attributes;
Yii::$app->cache->set('productGrid', $productGrid);
}
But I recommend other way: you can store each product record as separate cache item.
Firstly you can add multiple items:
public static function refreshProductCache() {
// Retrieve the all products
$products = Yii::$app->db->createCommand("CALL get_products()")->queryAll();
// Prepare for storing to cache
$productsToCache = [];
foreach ($products as $product) {
$productId = $product['id'];
$productsToCache['product_' . $productId] = $product;
}
// Store to cache (existing values will be replaced)
Yii::$app->cache->multiSet($productsToCache);
}
Secondly you can update cache when you read data. For instance:
public function actionView($id) {
$model = Yii::$app->cache->getOrSet('product_'.$id, function() use ($id) {
return PrProducts::find()
->andWhere(['id' => $id])
->one();
});
return $this->render('view', ['model' => $model]);
}
This code creates cache only one time for each $id that not yet present in the cache.
Thirdly you can add individual products to cache right after create/update. For instance:
public static function addToCache(PrProducts $modelProduct) {
$productId = $modelProduct->id;
Yii::$app->cache->set('product_' . $productId, $modelProduct);
}
I think this approach more flexible. Of course, it may be less efficient than you way. It very depends from code that reads your cache.
The script works fine and is setting the data, but the website code is unable to use it and is instead setting its own memcached values. My website code is written in codeIgniter framework. I don't know why this is happening.
My script code :-
function getFromMemcached($string) {
$memcached_library = new Memcached();
$memcached_library->addServer('localhost', 11211);
$result = $memcached_library->get(md5($string));
return $result;
}
function setInMemcached($string,$result,$TTL = 1800) {
$memcached_library = new Memcached();
$memcached_library->addServer('localhost', 11211);
$memcached_library->set(md5($string),$result, $TTL);
}
/*---------- Function stores complete product page as one function call cache -----------------*/
function getCachedCompleteProduct($productId,$brand)
{
$result = array();
$result = getFromMemcached($productId." product page");
if(true==empty($result))
{
//------- REST CODE storing data in $result------
setInMemcached($productId." product page",$result,1800);
}
return $result;
}
Website Code :-
private function getFromMemcached($string) {
$result = $this->memcached_library->get(md5($string));
return $result;
}
private function setInMemcached($string,$result,$TTL = 1800) {
$this->memcached_library->add(md5($string),$result, $TTL);
}
/*---------- Function stores complete product page as one function call cache -----------------*/
public function getCachedCompleteProduct($productId,$brand)
{
$result = array();
$result = $this->getFromMemcached($productId." product page");
if(true==empty($result))
{
// ----------- Rest Code storing data in $result
$this->setInMemcached($productId." product page",$result,1800);
}
return $result;
}
This is saving data in memcached. I checked by printing inside the if condition and checking the final result
Based on the CodeIgniter docs, you can make use of:
class YourController extends CI_Controller() {
function __construct() {
$this->load->driver('cache');
}
private function getFromMemcached($key) {
$result = $this->cache->memcached->get(md5($key));
return $result;
}
private function setInMemcached($key, $value, $TTL = 1800) {
$this->cache->memcached->save(md5($key), $value, $TTL);
}
public function getCachedCompleteProduct($productId,$brand) {
$result = array();
$result = $this->getFromMemcached($productId." product page");
if( empty($result) ) {
// ----------- Rest Code storing data in $result
$this->setInMemcached($productId." product page",$result,1800);
}
return $result;
}
}
Personally try to avoid 3rd party libraries if it already exists in the core framework. And I have tested this, it's working superbly, so that should fix this for you :)
Just remember to follow the instructions at http://ellislab.com/codeigniter/user-guide/libraries/caching.html#memcached to set the config as needed for the memcache server
I want a global array that I can access through controller functions, they can either add or delete any item with particular key. How do I do this? I have made my custom controller 'globals.php' and added it on autoload library.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
$notification_array = array();
$config['notification'] = $notification_array;
?>
following function on controller should add new item to my array
function add_data(){
array_unshift($this->config->item('notification'), "sample-data");
}
after add_data adds to the global array, whenever following function is called from client, it should give the updated array to the client.
function send_json()
{
header('content-type: application/json');
$target = $this->config->item('notification');
echo json_encode($target);
}
But my client always gets empty array. How can I make this happen? Please help.
Hi take advantage of OOP, like this
// put MY_Controller.php under core directory
class MY_Controller extends CI_Controller{
public $global_array = array('key1'=>'Value one','key2'=>'Value2'):
public function __construct() {
parent::__construct();
}
}
//page controller
class Page extends MY_Controller{
public function __construct() {
parent::__construct();
}
function send_json()
{
header('content-type: application/json');
$target = $this->global_array['key1'];
echo json_encode($target);
}
}
One solution I came up is to use session, its easy to use and its "fast" you need to do some benchmarking.
As I commented on both answers above/below there is no way you get same data in different controllers just because with each request everything is "reset", and to get to different controller you need to at least reload tha page. (note, even AJAX call makes new request)
Note that sessions are limited by size, you have a limit of 4kb (CodeIgniter stores session as Cookie) but wait, there is way around, store them in DB (to allow this go to config file and turn it on $config['sess_use_database'] = TRUE; + create table you will find more here)
Well lets get to the answer itself, as I understand you tried extending all your controllers if no do it and place some code in that core/MY_Controller.php file
as follows:
private function _initJSONSession() { //this function should be run in MY_Controller construct() after succesful login, $this->_initJSONSession(); //ignore return values
$json_session_data = $this->session->userdata('json');
if (empty($json_session_data )) {
$json_session_data['json'] = array(); //your default array if no session json exists,
//you can also have an array inside if you like
$this->session->set_userdata($ses_data);
return TRUE; //returns TRUE so you know session is created
}
return FALSE; //returns FALSE so you know session is already created
}
you also need these few functions they are self explainatory, all of them are public so you are free to use them in any controller that is extended by MY_Controller.php, like this
$this->_existsSession('json');
public function _existsSession( $session_name ) {
$ses_data = $this->session->userdata( $session_name );
if (empty( $ses_data )) return FALSE;
return TRUE;
}
public function _clearSession($session_name) {
$this->session->unset_userdata($session_name);
}
public function _loadSession($session_name) {
return (($this->_existsSession( $session_name )) ? $this->session->userdata($session_name) : FALSE );
}
the most interesting function is _loadSession(), its kind of self explainatory it took me a while to fully understand session itself, well in a few words you need to get (load) data that are in session already, do something with it ([CRUD] like add new data, or delete some) and than put back (REWRITE) all data in the same session.
Lets go to the example:
keep in mind that session is like 2d array (I work with 4+5d arrays myself)
$session['session_name'] = 'value';
$session['json'] = array('id' => '1', 'name' => 'asok', 'some_array' => array('array_in_array' => array()), 'etcetera' => '...');
so to write new (rewrite) thing in session you use
{
$session_name = 'json';
$session_data[$session_name] = $this->_loadSession($session_name);
//manipulate with array as you wish here, keep in mind that your variable is
$session_data[$session_name]['id'] = '2'; // also keep in mind all session variables are (string) type even (boolean) TRUE translates to '1'
//or create new index
$session_data[$session_name]['new_index'] = FALSE; // this retypes to (string) '0'
//now put session in place
$this->session->set_userdata($session_data);
}
if you like to use your own function add_data() you need to do this
well you need to pass some data to it first add_data($arr = array(), $data = ''){}
eg: array_unshift( $arr, $data );
{
//your default array that is set to _initJSONSession() is just pure empty array();
$session_name = 'json';
$session_data[$session_name] = $this->_loadSession( $session_name );
// to demonstrate I use native PHP function instead of yours add_data()
array_unshift( $session_data[$session_name], 'sample-data' );
$this->session->set_userdata( $session_data );
unset( $session_data );
}
That is it.
You can add a "global" array per controller.
At the top of your controller:
public $notification_array = array();
Then to access it inside of different functions you would use:
$this->notification_array;
So if you want to add items to it, you could do:
$this->notification_array['notification'] = "Something";
$this->notification_array['another'] = "Something Else";
My first question is basically asking for a code-review. Does the code I'm about to provide use a Factory to promote Polymorphism? Its written in PHP. Here are the basic requirements:
Pass a long url to a library and
return a shortened url. Along with the
long url, pass user properties to
attempt to locate the users specific
shortener service and API key.
Allow users to set API keys for specific URL shorteners. My code assumes this is already set in the database and Bitly is the only service supported.
If a user doesn't have an API key and service set, use the default API key and service. Again, my code assumes the default is set to Bitly in the database.
If the url shortener service fails, log the failure, but don't throw an exception. The library should silently fail. Instead of using the short url, we'll use the long url.
Here is an example of calling the code:
<?php
$long_url = 'http://www.news.com/story/1';
$account_id = 1;
$use_this_url = $long_url;
$meta = array(
'account_id' => $account_id,
// OPTIONS
// 'service_id' => $service_id,
// 'account_id' => $account_id,
);
$shortener = new Shortener_Factory($long_url, $meta);
if ($shortener->shorten_long_url() AND $shortener->save_short_url())
{
$use_this_url = $shortener->short_url;
}
echo $use_this_url;
Here are the classes:
<?php
interface ShortenerServiceInterface {
public function save_short_url();
public function shorten_long_url();
}
abstract class ShortenerServiceAbstract implements ShortenerServiceInterface {
// Url to be shortened
public $long_url = '';
// Long url unique id
public $url_id = 0;
// Service unique id
public $shorturlservice_id = 0;
// Service account unique id
public $shorturlserviceaccount_id = 0;
// Short url service unique API login
public $api_login = '';
// Short url service unique API key
public $api_key = '';
// Short url service unique hash which maps to original url value
public $hash = '';
// Shortened url string
public $short_url = '';
// Attempt to call shortner service three times before failing
public $attempts = 3;
// Shorten long url with specific service API/logic
public function shorten_long_url()
{
// Can't save a short url when one doesn't exist
if (!$this->long_url OR !$this->api_login OR !$this->api_key) {
log('error', 'ShortenerServiceAbstract::shorten_long_url - no long url to shorten - '.var_export($this, TRUE));
return FALSE;
}
}
// Save short url and related meta-data to shorturls table
public function save_short_url()
{
// Can't save a short url when one doesn't exist
if (!$this->url_id OR !$this->hash OR !$this->shorturlservice_id OR !$this->shorturlserviceaccount_id) {
log('error', 'ShortenerServiceAbstract::save_short_url - no short url to save - '.var_export($this, TRUE));
return FALSE;
}
// Insert a new short url, or update an existing record
$saved = Shorturl_Model::insert_on_dup_key_update($this->url_id, $this->hash, $this->shorturlservice_id, $this->shorturlserviceaccount_id);
if (!$saved) {
log('error', 'ShortenerServiceAbstract::save_short_url - short url record can not be saved - '.var_export($this, TRUE));
return FALSE;
} else {
return TRUE;
}
}
}
// Bitly, a simple url shortener
// #link http://code.google.com/p/bitly-api/wiki/ApiDocumentation
class ShortenerServiceBitly extends ShortenerServiceAbstract {
public function shorten_long_url()
{
// Make sure we have required members set
parent::shorten_long_url();
$urlencoded = urlencode($this->long_url);
$bitlyurl = 'http://api.bit.ly/shorten?version=2.0.1&longUrl='.$urlencoded.'&login='.$this->api_login.'&apiKey='.$this->api_key.'&history=1';
$attempts = 1;
while ($attempts <= 3) {
$json_result = file_get_contents($bitlyurl);
if ($json_result) {
// Return an assoc array
$json_decode = json_decode($json_result, TRUE);
if (is_array($json_decode) AND isset($json_decode['errorCode']) AND $json_decode['errorCode'] == 0) {
// Don't compare sent URL with returned URL
// Bitly removes invalid poritions of URLs
// The camparison might fail even though the URLs are the "same"
$shortened = current($json_decode['results']);
break;
} else {
log('error', 'ShortenerServiceBitly::shorten_long_url - bit.ly json decoded - '.var_export($json_decode, TRUE));
}
} else {
log('error', 'ShortenerServiceBitly::shorten_long_url - bit.ly http response - '.var_export($json_result, TRUE));
}
$attempts++;
}
if (isset($shortened)) {
$this->short_url = $shortened['shortUrl'];
$this->hash = $shortened['userHash'];
return TRUE;
} else {
return FALSE;
}
}
}
// Shortener Factory
class Shortener_Factory {
// Shortener service account parameter object
// #param object shortener account properties
private $_account;
// Shortener service object created by factory
//#param object shorterner service functions
private $_service;
// Url to be shortened
private $_long_url;
// Set url members, service parameter object and finally the service itself
public function __construct($long_url, $meta=array())
{
$this->_long_url = $long_url;
$this->_set_account($meta);
$this->_set_service();
}
// Set shortener service account parameter object
// #param $meta array determine parameters for the current service object
private function _set_account($meta=array())
{
$s = FALSE;
// Get shorturl service account
if (isset($meta['account_id'])) {
$s = Shorturlserviceaccount_Model::get_by_account_id($meta['account_id']);
} elseif (isset($meta['service_id'])) {
$s = Shorturlserviceaccount_Model::get_by_service_id($meta['service_id']);
}
// Account not found, lets use default
if (!$s) {
$s = Shorturlserviceaccount_Model::get_default();
}
// Must have a service to use
if ($s === FALSE) {
log('error', 'Shortener_Core::_set_account - _account not found - '.var_export($this, TRUE));
return FALSE;
} else {
$this->_account = $s;
}
}
// Use shortener service account parameter object to set shortener service object
private function _set_service()
{
switch ($this->_account->name) {
case 'bitly':
$this->_service = new ShortenerServiceBitly;
break;
default:
log('error', 'Shortener_Core::_set_service - _account not set - '.var_export($this, TRUE));
return FALSE;
}
$this->_service->long_url = $this->_long_url;
$this->_service->shorturlserviceaccount_id = $this->_account->id;
$this->_service->shorturlservice_id = $this->_account->shorturlservice_id;
$this->_service->api_login = $this->_account->api_login;
$this->_service->api_key = $this->_account->api_key;
}
// Public API for shortener service object methods
public function __call($name, $arguments)
{
if (!$this->_service) {
log('error', 'Shortener_Core::__call - _service not set - '.var_export($this, TRUE));
return FALSE;
}
return $this->_service->$name();
}
// Public API for shortener service object members
public function __get($name)
{
return ($this->_service->$name) ? $this->_service->$name : NULL;
}
}
The job of the factory pattern is to abstract away the creation of objects. The reason this is useful is because the way in which objects are created may not the same as just:
$instance = new Object();
any time you create it. For example if you first need to deal with loading an include file or you need to pick one of a few derived classes based on some parameter that is not known before runtime.
A factory can be as simple as something like:
function getInstance($objectType, $params)
{
if (!class_exists($objectType)) {
throw new Exception('Bad class');
}
$instance = new $objectType($params);
return $instance;
}
Or can be as complex as you like, but these are the basic rules to follow. check out the wikipedia article here for a PHP example