Closed kumarmanishc closed 3 years ago
Hi @kumarmanishc! Thanks for the interest in this tiny project.
Let me clarify the question.
Do I guess correct so far?
If I guessed correct, what you need is to implement Ob_Ivan\Throttler\JobInterface
with a class that takes the first portion of your list as a constructor argument (use array_slice()
on your long list of URLs before passing it to the constructor). The next()
method would shift the list and tell if the list became empty. This is needed to make the throttler stop once all calls have been made. Lastly, the execute()
method would call ExecutecallClass::ExecuteCurlCallGet()
with the URL that is the first in the list.
I would say that what you want is to wrap ExecutecallClass
with a job rather that use ExecutecallClass
as a job.
I hope this gives a hint about the supposed usage of the Throttler
class.
Please don't hesitate to ask further questions or correct any of the assumptions I relied on.
@ob-ivan Thank You for your reply. I want to mention to this question,
Aha, I see now.
Please note that an Ob_Ivan\Throttler\Throttler
object carries its state over after executing a job.
This means that if you supply it with single-URL jobs, it will sleep before executing the 101st API call.
So what you need is to:
$throttler->execute(new SingleJob($url))
wherever you need an API call.As long as the same throttler object is injected everywhere, it will limit the API call rate.
Your SingleJob
class can be outlined like following:
class SingleJob implements Ob_Ivan\Throttler\JobInterface {
private $url;
private $result = null;
public function __construct($url) { $this->url = $url; }
public function next() { return $this->result === null; }
public function execute() {
$executeCall = new ExecutecallClass();
$this->result = $executeCall->ExecuteCurlCallGet($this->url);
}
public function getResult() { return $this->result; }
}
Please note that due to the known bug #1, the current implementation may occasionally sleep even when no sleep is needed.
I also recommend hiding a throttler object behind an adapter class to avoid vendor lock.
Hope this helps.
Of course this will not work well for you:
$throttler->execute(new SingleJob($url))
because you won't be able to retrieve the results then!
What you need is to create a job object, pass it to execute()
, then retrieve the results, like following:
$job = new SingleJob($url);
$this->throttler->execute($job);
$result = $job->getResult();
But then again, if you hide it behind your own adapter, this might look like a single call:
interface ThrottledApiCall {
public function call($url);
}
class ObIvanThrottledApiCall implements ThrottledApiCall {
private $throttler;
public function __construct() { $this->throttler = new Ob_Ivan\Throttler\Throttler(100, 60); }
public function call($url) {
$job = new SingleJob($url);
$this->throttler->execute($job);
return $job->getResult();
}
}
This way if you want to adopt an alternative throttling library, you just create a new implementation of ThrottledApiCall
and you don't need to change the way you use it in the rest of your code:
function doSomethingUseful(ThrottledApiCall $apiCall) {
// do something else, maybe find out the URL
// ...
$result = $apiCall->call($url);
// process the results
// ...
}
Thank You @ob-ivan for helping me out. I will try to your suggestion if we can get it working then will update you.
This is code how we are making api calls. This is snippets I am adding so you can understand my scenario,
// Function can be called in loop.
sync_groupitem_recursively($page, $category, $source);
//Function to make api call
function sync_groupitem_recursively($page, $source)
{
$url = $zoho_inventory_url . 'api/v1/pricebooks?organization_id=' . $zoho_inventory_oid;
// This is making api calls
$executeCurlCallHandle = new ExecutecallClass();
$json = $executeCurlCallHandle->ExecuteCurlCallGet($url);
$json2 = json_encode($json);
return json_decode($json2, true);
}
Function of ExecuteCallClass, but there are other functions as well,
class ExecuteCallClass {
function ExecuteCurlCallImageGet($url, $image_name) {
$handlefunction = new Classfunctions;
$zoho_inventory_access_token = $this->config['ExecutecallZI']['ATOKEN'];
$zoho_inventory_refresh_token = $this->config['ExecutecallZI']['RTOKEN'];
$zoho_inventory_timestamp = $this->config['ExecutecallZI']['EXPIRESTIME'];
$current_time = strtotime(date('Y-m-d H:i:s'));
if($zoho_inventory_timestamp < $current_time){
$respoAtJs = $handlefunction->GetServiceZIRefreshToken($zoho_inventory_refresh_token);
$zoho_inventory_access_token = $respoAtJs['access_token'];
update_option('zoho_inventory_access_token', $respoAtJs['access_token']);
update_option('zoho_inventory_timestamp', strtotime(date('Y-m-d H:i:s'))+$respoAtJs['expires_in']);
}
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => array(
"Authorization: Bearer ".$zoho_inventory_access_token,
),
));
$response = curl_exec($curl);
curl_close($curl);
$upload = wp_upload_dir();
$absolute_upload_path = $upload['basedir'].'/zoho_image';
$url_upload_path = $upload['baseurl'].'/zoho_image';
$img = '/'.rand().$image_name;
//$img = '/image'.rand().'.jpg';
$upload_dir = $absolute_upload_path.$img;
if(!is_dir($absolute_upload_path)){
mkdir($absolute_upload_path);
}
file_put_contents($upload_dir, $response);
return $url_upload_path.$img;
}
} // Closing of class.
@kumarmanishc, I'm not sure I understand whether you still have a question, and if you do, what the question is.
What I understand is that you have a class ExecuteCallClass
, and it features many methods, some of them including a cURL request written out verbosely, probably due to the high variance of their options and the purposes the response is used for.
What I wanted to point out is that the throttling will not work unless you have a throttler object shared by all calls to the same API. I guess you can use some WordPress magic to have a throttler singleton or a globally shared variable, whatever you like, but I'm not much of an expert in WordPress's best practices, so you will have to figure that out.
If the URL is not the only input parameter of an API call, you could implement the SingleJob
class as a wrapper for a $curl
resource, which execute()
s by calling $this->response = curl_exec($curl)
, then you expose the response via a getter.
If you still have a question, please ask a question.
@ob-ivan You said throttling will not work unless you have a throttler object shared by all calls to the same API
What I understand is creating instance of ObIvanThrottledApiCall and share among all api calls we are making ?
as follows,
$apiCall = new ObIvanThrottledApiCall();
doSomethingUseful($apiCall);
function doSomethingUseful(ThrottledApiCall $apiCall) {
// do something else, maybe find out the URL
// ...
$result = $apiCall->call($url);
// process the results
// ...
}
Please let me know if I'm wrong. Also if not that scenario, Can you please work with us for completion of this task ?
@kumarmanishc You said in the OP:
ExecuteCurlCallGet is being called from various places in my project.
I don't know how your project is structured, but let me assume something.
Let's say you have ClassA
and ClassB
. Each has its own method to make an API call:
class ClassA {
public function makeApiCall($parameter1) {
$curl = curl_init();
... set options ...
$result = curl_exec($curl);
curl_close($curl);
return $result;
}
}
class ClassB {
public function makeApiCall($parameter1) {
$curl = curl_init();
etc...
}
}
What I was trying to say is that if you instantiate a throttler each time you need to make an API call like this:
class ClassA {
public function makeApiCall(...) {
$throttler = new Throttler();
...
}
}
class ClassB {
public function makeApiCall(...) {
$throttler = new Throttler();
...
}
}
then each throttler will track its request rate separately. And if you let a throttler object be destroyed (by leaving the function scope), then its information about how many requests has been executed in what time frame will be lost, meaning that the subsequent requests will not be limited to the target request rate and the API will reject your requests.
So if you have such classes, you need to make sure that each class receives the very same object of the throttler, for example, like this:
class ClassWhereAllThingsHappen {
public function functionThatMakesEveryoneHappy() {
$throttler = new Throttler(100, 60); // This will be the object that traces the request rate.
$classA = new ClassA($throttler); // Make ClassA's requests be traced.
$classB = new ClassB($throttler); // Make ClassB's requests be traced.
... do fun stuff with $classA and $class B ...
}
}
Where will the throttler object be initialized in your project, how will the throttler object reach its consumers --- this all depends on how your project is structured, and this is something you will have to figure out by yourself.
@ob-ivan If you are available for paid task for this issue please shoot one mail at info@roadmapstudios.com
@kumarmanishc Thank you for the offer, but I am not.
Thanks for the interest in this little project. The original question was discussed at length already. If the information provided is insufficient, please try to narrow down your question and specify a concrete problem you would like to address.
I have one class and function is responsible for making api call. and ExecuteCurlCallGet is being called from various places in my project. And I want to limit the execution of this function to execute for only 100 api calls.
In documentation it states that $throttler->run($job);
When I implement class will have next and execute method and we can create instance as follows, $job = new ExecutecallClass();
But my question is how can I call my function to other places in my project.
If I am miss understanding something please let me know.
Thank You