Best way of handling pagination in a PHP client library - php

Assume I have a REST API that supports pagination (and filtering). Upon executing a GET request to myapi.com/api/users, it returns something like this:
{
total: 100
data: . . .
first_page_url: "/?page=1"
from: 0
next_page_url: "/?page=2"
path: "/"
per_page: 10
prev_page_url: null
to: 10
}
And then in my PHP client library I have something similar to this for a function that performs this request and returns the JSON response as an associative array:
public function users($page = 1, $verified = null, $admin = null, $joined = null, $active_until = null, $banned_until = null, $referral_code = null) {
. . .
}
What would be the best way to fetch the next result?
I am envisioning something like this:
$users = [];
$request = users(); // gets the first page
while ($request) {
$request = next_users();
array_push($users, $request['data']);
}
The problem is that I have no idea where to begin when it comes to putting the result of users() into next_users() while preserving any filter values (the optional parameters of users()), all while doing it elegantly. I have tried looking around, but I can't find many results of API clients handling pagination. As demonstrated above, the ideal scenario would be having the ability to keep getting the next page of results until there are none left with next_users().

You just keep your filter and go through all pages. Something like this:
$filter = [
$admin = true,
$joined = null,
$active_until = null,
$banned_until = null,
$referral_code = "RJ08"
];
$page = 1;
$response = users($page, ...$filter); // gets the first page
$usersData = $response['data'];
while (null !== $response['next_page_url']) {
$response = users(++$page, ...$filter);
array_push($usersData, $response['data']);
}

Related

How to fetch columns data from a table using the foreign key in CodeIgniter 3

Hi first of all I should tell you that English is not my first language so excuse any misunderstandings. What I'm trying here is to access to the "donors" table data using the foreign key in the "packets" table and I need to get that data in the controller to use. I refer PacketID in my view and in the staff controller I need to get the specific DonorMobile and DonorName to send an SMS to that donor. I have tried creating multiple model functions but didn't work. Can it be done by that way or is there any other way? TIA!
Screenshots of donors and packets tables
donors table and
packets table
Staff controller - I know you can't access data like $donorData->DonorID in the controller
public function markAsUsed($packet)
{
$this->load->Model('Donor_Model');
$donorData = $this->Donor_Model->getDonors($packet);
$this->load->Model('Donor_Model');
$data = $this->Donor_Model->markAsUsed($donorData->DonorID);
$sid = 'twilio sid';
$token = 'twilio token';
$donorMobile = '+94' . $donorData->DonorMobile;
$twilioNumber = 'twilio number';
$client = new Twilio\Rest\Client($sid, $token);
$message = $client->messages->create(
$donorMobile, array(
'from' => $twilioNumber,
'body' => 'Thank you ' . $donorData->DonorName . ' for your blood donation. Your donation has just saved a life.'
)
);
if ($message->sid) {
$this->load->Model('Donor_Model');
$this->Donor_Model->changeStatus($data->isAvailable);
redirect('staff/viewpackets');
}
}
Model
function getDonors($packet) {
$data = $this->db->get_where('packets', array('PacketID' => $packet));
return $data->row();
}
function markAsUsed($donor)
{
$data = $this->db->get_where('donors', array('DonorID' => $donor));
return $data->row();
}
function changeStatus($packet)
{
$data = array(
'isAvailable' => False,
);
return $this->db->update('packets', $data, ['PacketID' => $packet]);
}
What you did in the answer is not advisable, using loops to fetch from tables will slow the system by large when you have large datasets. What was expected is to use JOIN queries to join the donors and packets tables as shown below:
$this->db->select('donors_table.DonorName,donors_table.DonorMobile,packets_table.*')->from('packets_table');
$this->db->join('donors_table','donors_table.DonorID=packets_table.DonorID');
$query = $this->db->get();
$result = $this->db->result();
Then you can iterate through each donor as below:
foreach($result as $row){
$donarName = $row->DonorName;
$donorMobile = $row->DonorMobile;
}
NB: It is always advisable to perform any fetch, insert, delete or update operations from the model, that is why Codeigniter is an MVC. Stop fetching data directly from database within Controllers.
Found the answer! You can do this only using the controller.
$packetData = $this->db->get_where('packets', array('PacketID' => $packet));
foreach ($packetData->result() as $row) {
$donorID = $row->DonorID;
$packetDonatedDate = $row->DonatedDate;
}
$donorData = $this->db->get_where('donors', array('DonorID' => $donorID));
foreach ($donorData->result() as $row) {
$donarName = $row->DonorName;
$donorMobile = $row->DonorMobile;
}

How to loop through all assets and check for a key, if the key matches, continue and display all assets? PHP/Laravel

I have a collection of $liquidAssets and I want to loop through all of them and display only the $liquidAssets which maches the key, how could I do this? Do I need to firstly make a collection an array?
public function displayAssets()
{
$name = 'name';
$token = 'token';
$themeId = '123';
$assetAPI = new Asset($name, $token, $themeId);
$assets = $assetAPI->all();
$liquidAssets = collect($assets->assets)->where('content_type', 'text/x-liquid');
$liquidAssets = $liquidAssets->all();
foreach($liquidAssets as $asset) {
$response = $assetAPI->get($asset->key);
dd($response);
}
}
The logic is this:
loop through all assets
check if in array there is a key ld+json
display all assets with ld+json
Now I get only the assets of a one asset
public function displayAssets()
{
$name = 'name';
$token = 'token';
$themeId = '123';
//$assetAPI = new Asset($name, $token, $themeId); // Is this model populated with assets rows? if yes this must be:
$liquidAssets = Asset::where('content_type', 'text/x-liquid')->get(); // already filtered
//$assets = $assetAPI->all(); // this is wrong since you are trying to get data from a new object
//$liquidAssets = collect($assets->assets)->where('content_type', 'text/x-liquid'); // same here
//$liquidAssets = $liquidAssets->all(); // same here
foreach($liquidAssets as $asset) {
$response = $assetAPI->get($asset->key);
dd($response);
}
}

Count Array where Column Value Equals Given

I have a controller where an array of checkbox values are passed on to the controller and then sent on to do a variety of tasks. My problem is focused on getting a count of the arrays' shipment's customer's billing method, but I'm not entirely sure how I would get those values since I'm posting an array and I want a count.
Here's my controller:
public function sendNow(Request $request)
{
$now = Carbon::now();
$sendNowShipment = array();
$method = array();
$sendNowShipment = request('send');
foreach($sendNowShipment as $SNS){
$shipment = Shipment::findOrFail($SNS);
if($shipment->billtoAccount->billingMethods->label == "Mail"){
$timestamp = Carbon::now()->timestamp;
$shipment_details = $shipment->shipment_details;
$path = 'temp/freightbill_'.$shipment->pro_number.'-'.$timestamp.'.pdf';
$sessionPath[] = $path;
$pdf = PDF::loadView('shipments.pdf', compact('shipment', 'shipment_details'))
->save($path);
}elseif($shipment->billtoAccount->billingMethods->label == "Email"){
$billToAccount = $shipment->billtoAccount;
$billToAccountUsers = $billToAccount->users;
if ($billToAccount->primary_email){
$billToEmail[] = $billToAccount->primary_email;
}
if ($billToAccountUsers->count() > 0){
foreach ($billToAccountUsers as $billToAccountUser){
$billToEmail[] = $billToAccountUser->email;
}
}
foreach ($billToEmail as $bte){
Mail::to($bte)->send(new newBillToShipment($shipment));
}
}
}
if(count($shipment->billtoAccount->billingMethods->label == "Mail") > 0){// count where shipments->customers->billingMethods = Mail) > 0
$pdf = new PDFMerger();
// Add 2 PDFs to the final PDF
foreach($sessionPath as $sp){
$pdf->addPDF($sp, 'all');
}
// Merge the files and retrieve its PDF binary content
$timestamp = Carbon::now()->timestamp;
$binaryContent = $pdf->merge('download', $timestamp."-printBatch.pdf");
// Return binary content as response
//return response($binaryContent)
// ->header('Content-type' , 'application/pdf');
return back();
//dd($sendNowShipment,$sendMethod);
}
}
If you look at this line (a little past the middle):
if(count($shipment->billtoAccount->billingMethods->label == "Mail") > 0){
// count where shipments->customers->billingMethods = Mail) > 0
You'll see that I'm going through a multitude of relationships just to get the value of "Mail" that I am looking for.
So I'm wondering if this should be a DB:: query through laravel, but I'm not sure how I would perform the query using the given checkbox array I have being POSTed to this controller.

Persist filter values in codeigniter with pagination library

I use the default codeiginter pagination library. I tried implementing this in a previously created page which shows all vacancies, but since we are getting TOO many on the site, the performance is terrible. This is why I need pagination on this page. Note that this is not the cleanest solution, there is a new track going on which overhauls the entire page and starts from scratch. This is a quick & dirty solution because we need to keep it working on our live environment until the rework is done.
This is the controller code I have:
public function overviewVacancies($city = null)
{
$this->is_logged_in();
// Load Models
$this->load->model('vacancy/vacancies_model');
$this->load->model('perk/interests_model');
$this->load->model('perk/engagement_model');
$this->load->model('user/userSavedVacancies_model');
// Passing Variables
$data['title'] = lang("page_title_activities_overview");
$data['description'] = lang('meta_desc_activities');
$data['class'] = 'vacancy';
$data['engagements'] = $this->engagement_model->getAll();
$data['interests'] = $this->interests_model->getAllInterests();
$data['bread'] = $this->breadcrumbmanager->getDashboardBreadcrumb($this->namemanager->getDashboardBreadName("0"), $this->namemanager->getDashboardBreadName("1"));
$data['tasks'] = $this->interests_model->getAllTasks();
// Set session data
$this->session->set_userdata('previous',"http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
$this->session->set_userdata('previous-title', "1");
$filterdata = array(
'interests' => $this->input->post('interests'),
'skills' => $this->input->post('skills'),
'fulldate' => $this->input->post('daterange'),
'location' => $this->input->post('location'),
'city' => $this->input->post('sublocality_level_1'),
'capital' => $this->input->post('locality')
);
if (!empty($filterdata['interests'])) {
$filterdata['interests'] = rtrim($filterdata['interests'], ";");
$filterdata['interests'] = str_replace(' ', '', $filterdata['interests']);
$filterdata['interests'] = str_replace(';', ',', $filterdata['interests']);
}
if (!empty($filterdata['skills'])) {
$filterdata['skills'] = str_replace(' ', '', $filterdata['skills']);
$filterdata['skills'] = explode(",", $filterdata['skills']);
}
//Manually clear the commune and city variables if the location was empty
if (empty($filterdata['location'])) {
$filterdata['city'] = '';
$filterdata['capital'] = '';
}
if($city == null){
$orgId = $this->organization_model->getOrgIdByName(LOCATION);
}
else{
$orgId = $this->organization_model->getOrgIdByName($city);
$data['bread'] = $this->breadcrumbmanager->getLocalBreadcrumb($this->namemanager->getDashboardBreadName("0"), $city, $data['title'], $data['vacancydetails']);
}
//Set the location to the subdomain automatically (e.g. when the link is clicked) so the activities of that subdomain only show up
if (!empty(LOCATION)) {
$data['title'] = sprintf(lang('page_title_local_activities'), ucwords(LOCATION));
$data['description'] = sprintf(lang('meta_desc_local_activities'), ucwords(LOCATION));
$filterdata['location'] = LOCATION;
$data['bgcolor'] = $this->localSettings_model->getBgColorHexValueForOrgId($orgId->org_id);
}
if (!empty($filterdata['fulldate'])) {
$splitfromandto = explode(" - ", $filterdata['fulldate']);
$filterdata['datefrom'] = $splitfromandto[0];
$filterdata['dateto'] = $splitfromandto[1];
} else {
$filterdata['datefrom'] = null;
$filterdata['dateto'] = null;
}
//Put these variables in the data variable so we can prefill our filters again with the previous values
//This is necessary because we don't use AJAX yet
$data['filterdata'] = $filterdata;
//Pagination : We do it here so we can re-use the filter query to count all our results
$this->load->library('pagination');
$data['all_vacancies'] = $this->vacancies_model->getFilteredVacancies($filterdata);
$pagconfig['base_url'] = base_url().VACANCY_OVERVIEW;
$pagconfig['total_rows'] = count($data['all_vacancies']);
$pagconfig['per_page'] = 1;
$pagconfig['uri_segment'] = 2;
$pagconfig['use_page_numbers'] = TRUE;
$this->pagination->initialize($pagconfig);
$data['links'] = $this->pagination->create_links();
$start = max(0, ( $this->uri->segment(2) -1 ) * $pagconfig['per_page']);
//This variable contains all the data necessary for a vacancy to be displayed on the vacancy overview page
$data['vacancies'] = $this->vacancies_model->getFilteredVacancies($filterdata, false, null, false, null, false, false, $pagconfig['per_page'], $start);
// Template declaration
$partials = array('head' => '_master/header/head', 'navigation' => '_master/header/navigation_dashboard', 'content' => 'dashboard/vacancy/overview', 'footer' => '_master/footer/footer');
$data['vacancygrid'] = $this->load->view('dashboard/vacancy/vacancygrid', $data, TRUE);
$this->template->load('_master/master', $partials, $data);
}
As you can see in the code i keep a variable called $filterdata, this contains all data which is used in our filters, since we don't use AJAX in this version, we need to pass it to the view every time to fill it up again and present it to the visitor.
However, using pagination this breaks because it just reloads my controller method and thus the values in $filterdata are lost.
How do I go about this?
Note: it does not have to be a clean solution, it just needs to work since this page is going offline in a couple of weeks anyway.
Thanks a lot in advance!

Issues with paging through failure and rejection events returned by mailgun's api

So, I've developed the following code:
const MAILGUN_API_MAX_LIMIT = 300; //max in documentation
$mgClient = new Mailgun\Mailgun("<mailgun_key>");
$domain = "<my_domain>";
$resultItems = array();
try {
$result = null;
$endTime_Epoch = time();
$startTime_Epoch = $endTime_Epoch - 1440000; //400 hours
$queryParams = array(
'begin' => $endTime_Epoch,
'end' => $startTime_Epoch,
'limit' => MAILGUN_API_MAX_LIMIT,
'ascending' => 'no',
'event' => 'rejected OR failed',
);
$request_url = "$domain/events";
do {
$result = $mgClient->get($request_url, $queryParams);
if(!$result->http_response_body) {
/*...*/
} else if($result->http_response_code != 200) {
/*...*/
} else {
$resultItems[] = $result->http_response_body->items;
if(count($result->http_response_body->items) == MAILGUN_API_MAX_LIMIT) {
$request_url = $result->http_repsonse_body->paging->next;
}
}
} while($result && $result->http_response_body && count($result->http_response_body->items) == MAILGUN_API_MAX_LIMIT);
} catch (Exception $e) {
echo $e->getMessage()
}
But I'm having trouble getting it to page through the next set of requests if the limit ever gets hit (it probably won't, but it'd be bad in the event that something happened that made it happen).
I've tried reading mailgun's api doc, but i cannot, for the life of me, figure out what it is talking about with this:
require 'vendor/autoload.php';
use Mailgun\Mailgun;
# Instantiate the client.
$mgClient = new Mailgun('YOUR_API_KEY');
$domain = 'YOUR_DOMAIN_NAME';
$nextPage = 'W3siYSI6IGZhbHNlLC';
# Make the call to the client.
$result = $mgClient->get("$domain/events/$nextPage");
But i can't figure out what that $nextPage is supposed to be. I've tried peeling off the start of the paging->next so it just ends up being $domain/events/, but that doesn't seem to be paging though it. I'm not really sure what to do here, or even what I'm supposed to do.
I know this is a bit later than you would've hoped, but I've also been doing some stuff with the Mailgun and PHP lately.
So $nextPage is a value provided after your first API requests, so you don't need it at the start. Simply make your normal request like so
$result = $mailgun->get("$domain/events", $queryString);
// then we can get our 'Next Page' uri provided by the request
$nextURI = $result->http_response_body->paging->next;
// now we can use this to grab our next page of results
$secondPageResults = $mailgun->get($nextURI, $queryString);
Once thing worth noting though, is that Mailgun seems to always provide a 'next' link even when you've got all the results you want.
In my project I collected all results using a while loop after getting my initial records:
$hasNext = count($items) >= $queryLimit;
while($hasNext) {
$nextResult = $mailgun->get($lastResult->http_response_body->paging->next, $queryString);
$nextItems = $nextResult->http_response_body->items;
$items = array_merge($items, $nextItems);
$hasNext = count($nextItems) >= $queryLimit;
$lastResult = $nextResult;
}
Hope this helps!

Categories