The Kroger API examples below collect grocery store location and product JSON data. As a bonus, we’ll use PHP Guzzle async to run concurrent requests (saving 10X the time). This will give you a leg up on utilizing the Kroger API in any PHP project.
Due to Kroger subsidiaries, this becomes a Ralphs API example, a Fred Meyer API example, Smith’s Food and Drug API example, and King Soopers API example. In addition, you may use this for any Kroger-owned grocer, market, gas station, or company.
The steps below will walk through the logic. However, I put the complete Kroger API Examples code on Github. I built this for Laravel, but it could be adapted to other PHP frameworks.
Let’s get started.
Sign Up For A Kroger API Account
- Sign up for a Kroger API account.
– Complete the steps to create and verify your account. - Once logged, click the “Manage” button in the upper right corner.
- On the “Manage Organizations” page, click the blue “New Org” button.
- Register your organization’s name and description.
– chose Production or Certification (Dev) environment - Save, Download, and/or Print your registration details and click “Continue”.
– especially your client_id and client_secret.
Generate API Auth Key And Save
Within your terminal tool, generate an API Key.
# replace the values below and run
echo -n 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' | base64
# you will end up with a value like this:
# MTIzNDU2Nzg5MTIzNDU2Nzg5MTIzNDU2Nzg5MTIzNDU2Nzg5MTIzNDU2Nzg5MTIzNDU2Nzg5MTIzNDU2Nzg5MDoxMjM0NTY3ODkxMjM0NTY3ODkxMjM0NTY3ODkxMjM0NTY3ODkxMjM0
Update Your Laravel .env file with the API URL and Auth Key.
# Dev
KROGER_API_URL="https://api-ce.kroger.com/v1"
KROGER_API_AUTH_KEY="........."
Make a Kroger API Database Table
You don’t need MySQL to use the Kroger API examples. However, we’ll use a table to track API access.
- track the number of API calls made each day.
– Kroger limits it to 10,000 per day - save and access generated API tokens while they are valid
– token stay valid for 1800 seconds (30 minutes)
– so reusing a valid token reduces the number of calls we make each day.
Carrying on, I will use Laravel Sail to execute Laravel 9 command line code.
./vendor/bin/sail artisan make:migration create_kroger_api_table
Edit the migration file.
<?php // file: database/migrations/0000_00_00_000000_create_kroger_api_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (!Schema::hasTable("kroger_api")) {
DB::unprepared("
CREATE TABLE `m_kroger_api` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`api_day` timestamp NULL DEFAULT NULL,
`api_call_count` bigint DEFAULT '0',
`token_generic_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`token_generic_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`token_generic_expires_in` bigint DEFAULT NULL,
`token_generic_created_at` timestamp NULL DEFAULT NULL,
`token_product_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ,
`token_product_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL ,
`token_product_expires_in` bigint DEFAULT NULL,
`token_product_created_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `api_day` (`api_day`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists("kroger_api");
}
};
Then migrate the table.
./vendor/bin/sail artisan migrate
You will end up with a table like this.
Create A Kroger API Class
Run the following in your command line.
./vendor/bin/sail artisan make:model KrogerApi
Add the class structure that will hold all our methods/functions for the Kroger API examples.
I will explain each as we fill out the code below.
<?php // file: app/Models/KrogerApi.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Pool;
class KrogerApi extends Model
{
use HasFactory;
protected $table = "mkroger_api";
private static $api_context;
private static function get_context() {}
private static function get_token() {}
private static function increment_api_call_count() {}
public static function get_locations() {}
public static function get_products() {}
private static function guzzle_pool() {}
}
Get Context
Here we create an OOP Singleton that manages the “self::$api_context” variable.
- We get today’s API row from the database.
- If it doesn’t exist, we’ll create it.
- It’s saved in the Laravel active record format.
- In this way, updates to the variable will save to the database.
private static function get_context()
{
/*
* get_context()
*
* - manages the "m_kroger_api" row for today
* - main duties
* - count how many api calls we made today
* - store valid generic and product tokens
* - and update tokens when they expire
*/
if (empty(self::$api_context)) {
// get the api row for today
// OR create it
$date = Carbon::now(config("app.timezone"))->format("Y-m-d");
self::$api_context = self::whereRaw("DATE(created_at) = DATE(?)", [
$date,
])->firstOr(function () {
// create a new active record
$date_object = Carbon::now(config("app.timezone"));
$date = $date_object->format("Y-m-d");
$timestamp = $date_object->toDateTimeString();
$context = new self();
$context->api_day = $date;
$context->api_call_count = 0;
$context->created_at = $timestamp;
$context->updated_at = $timestamp;
$context->save();
// return it to the static variable
return $context;
});
}
return self::$api_context;
}
Get Token
The method manages our tokens within the API context.
Purpose & Output:
- Manage valid tokens and return the appropriate token for locations or products.
Inputs:
- Our Kroger API examples use two token URL GET variable scopes:
– blank scope (“scope=”) which we’ll use for getting locations.
– and product scope (“scope=product.compact”) which we’ll use for products. - This function receives “generic” (blank) and “product” to let us know what token we want.
Order of Operations:
- Get the current API context
- Get the token type from the API content
- If it exists and is not expired, then return the value
- If it is expired or doesn’t exist, then…
– get a new token
– save it to API context (and the database)
– return the new value.
Here’s our first call to PHP Guzzle async pool.
- in this case, it calls to get the Kroger API token we need.
- we’ll cover this when we go over that method.
/*
* get_token()
*
* - manages the tokens inside self::$api_context
* - (both the generic and product tokens)
* - checks if it exists
* - checks if it's expired
* - if it doesn't exist or is expired, then this...
* - gets a new token from the Kroger API
* - updates the database and local context
*
* - input: type ("generic" or "product")
*
* - output: token value string
*/
private static function get_token($type = "generic")
{
// get current context
self::get_context();
// set context key names
$type = $type == "product" ? "product" : "generic";
$token_value_key = "token_" . $type . "_value";
$token_type_key = "token_" . $type . "_type";
$token_expires_in_key = "token_" . $type . "_expires_in";
$token_created_at_key = "token_" . $type . "_created_at";
// get context values
$access_token = self::$api_context->{$token_value_key} ?? "";
$expires_in = self::$api_context->{$token_expires_in_key} ?? "";
$created_at = self::$api_context->{$token_created_at_key} ?? "";
// test if token is expired
// - Kroger API tokens expires in 30 minutes (1800 seconds).
// - The API also tells you how many seconds it will expire it.
// - We save this in the database and context.
// - So if this value changes, this will dynamically check
$token_is_expired = true;
if (!empty($access_token) && !empty($created_at) && !empty($expires_in)) {
$created_object = new Carbon($created_at);
$now = Carbon::now(config("app.timezone"));
$seconds_diff = $created_object->diffInSeconds($now);
// find if token expired (with a 3 minute buffer)
$token_is_expired = intval($seconds_diff + 180) > intval($expires_in);
}
// if the token expired or we don't have a token
// then create a new one
if (empty($access_token) || (!empty($access_token) && $token_is_expired)) {
// set scope
$scope = $type == "product" ? "product.compact" : "";
// set curl data
$api = env("KROGER_API_URL");
$url = "$api/connect/oauth2/token?grant_type=client_credentials&scope=$scope";
$urls_data = [
[
"url" => $url,
"method" => "POST",
],
];
// call
$responses = self::guzzle_pool($urls_data, "basic");
$response = $responses[0] ?? [];
$status = $response["status"] ?? "";
if ($status != "success") {
$message = $response["message"] ?? "error getting api token";
throw new Exception($message, 1);
}
// get output values
$data = $response["data"] ?? [];
$access_token = trim($data["access_token"] ?? "");
$expires_in = intval($data["expires_in"] ?? "");
$token_type = trim($data["token_type"] ?? "");
$timestamp = Carbon::now(config("app.timezone"))->toDateTimeString();
// update context
self::$api_context->{$token_value_key} = $access_token;
self::$api_context->{$token_expires_in_key} = $expires_in;
self::$api_context->{$token_type_key} = $token_type;
self::$api_context->{$token_created_at_key} = $timestamp;
self::$api_context->save();
}
return self::$api_context->{$token_value_key};
}
Increment API Call Count
We’ll keep track of our daily API call count to monitor usage.
- It gets the current count from the API context.
- Then the function will increment it.
- Finally, it will save it back to the context and the database.
private static function increment_api_call_count()
{
// make sure we have context
self::get_context();
// update values
$timestamp = Carbon::now(config("app.timezone"))->toDateTimeString();
self::$api_context->api_call_count += 1;
self::$api_context->updated_at = $timestamp;
// save
self::$api_context->save();
return;
}
Get Locations
The Kroger API locations docs allow you to search by many factors.
For my purposes, I centered this function around a search by postal code.
Purpose & Output:
- return all unique location addresses based on an array of provided zip codes.
Inputs:
- Search a batch of zip codes (required)
- Also, search by only locations with specific departments (optional)
- Limit the number of stores to return (Kroger allows 1 to 200)
- Limit the number of miles to search nearby (1 to 100 miles)
Order of operations:
- First, build a URL for each postal code we will search.
- Then it calls the PHP Guzzle async pool to concurrently gather responses from the Kroger API.
- Next, it gathers each unique location from all responses.
- Along with that, it formats the location address/details in a more compact format.
- Finally, the function returns a breakdown of status, (error) message, and data (locations).
/*
* get_locations()
*
* Inputs:
* - postal_codes: array of 5 digit strings
* [ "32118", "32207", "32216", "32218", "32256 ]
* - optional departments: comma separated two digit codes
* - this finds stores with these departments
* - limit: number of stores to return
* - Kroger allows between 1 to 200
* - radius: number of miles to search near by
* - Kroger allows 1 to 100 miles
*
* Output:
* - Kroger store locations array in $result["data"]
*
* Kroger API location docs
* - https://developer.kroger.com/reference/#tag/Locations
*/
public static function get_locations(
$postal_codes = [], // required
$departments = "04,05", // optional comma separated two digit codes
$limit = 200, // 1 to 200
$radius_miles = 100
) {
try {
$start_time = microtime(true);
// if postal codes is a string, recast as an array
if (!empty($postal_codes) && is_string($postal_codes)) {
$postal_codes = preg_split("/(,|;)/", $postal_codes);
}
// clean postal codes
if (is_array($postal_codes)) {
$postal_codes = array_map(function ($code) {
// allow only numbers
$code = preg_replace("/[^0-9]/", "", $code);
// only first 5 characters
$code = substr($code, 0, 5);
return $code;
}, $postal_codes);
}
// if we have no postal codes, then return
if (empty($postal_codes) || count($postal_codes) < 1) {
throw new Exception("no valid zip codes sent", 1);
}
// set and clean values
$api = env("KROGER_API_URL");
$limit = intval($limit) > 0 ? intval($limit) : 200;
$radius_miles = intval($radius_miles) > 0 ? intval($radius_miles) : 100;
$urls_data = [];
foreach ($postal_codes as $postal_code) {
// build url
$url = "$api/locations?filter.zipCode.near=$postal_code";
$url .= "&filter.limit=$limit&filter.radiusInMiles=$radius_miles";
if ($departments) {
$url .= "&filter.department=$departments";
}
$urls_data[] = [
"url" => $url,
"method" => "GET",
];
}
// call
$responses = [];
if (count($urls_data) > 0) {
$responses = self::guzzle_pool($urls_data, "generic");
}
// consolidate locations
$locations = [];
foreach ($responses as $response) {
// did this fail?
if (($response["status"] ?? "") != "success") {
$message = $response["message"] ?? "error getting api token";
throw new Exception($message, 1);
}
$api_locations = $response["data"]["data"] ?? [];
$meta = $response["data"]["meta"] ?? [];
if (count($api_locations) < 1) {
continue;
}
$required_fields = [
"locationId",
"chain",
"name",
"zipCode",
"state",
"county",
"city",
"locationId",
];
foreach ($api_locations as $key => $location) {
// merge address to root of array
$location_id = $location["locationId"] ?? "-1";
$location = array_merge($location, $location["address"] ?? []);
$missing_required = false;
foreach ($required_fields as $required) {
if (empty($location[$required])) {
$missing_required = true;
break;
}
}
// if already in new array or missing required fields, then skip
if (!empty($locations[$location_id]) || $missing_required) {
unset($api_locations[$key]);
continue;
}
// standardize and add to new array
$new_location = [
"store_parent" => "KROGER",
"store_chain" => $location["chain"],
"location_id" => "" . $location["locationId"] . "",
"store_name" => $location["name"],
"address_line1" => $location["addressLine1"] ?? "",
"address_line2" => $location["addressLine2"] ?? "",
"address_line3" => $location["addressLine3"] ?? "",
"city" => $location["city"],
"region" => $location["state"],
"postal_code" => $location["zipCode"],
"county" => $location["county"],
"latitude" => $location["geolocation"]["latitude"] ?? "",
"longitude" => $location["geolocation"]["longitude"] ?? "",
"timezone" => $location["hours"]["timezone"] ?? "",
"phone" => $location["phone"] ?? "",
];
// add to new locations array
$locations[$location["locationId"]] = $new_location;
// free up memory
unset($api_locations[$key]);
}
}
$status = "success";
$message = "script complete";
} catch (Exception $e) {
$status = "fail";
$message = $e->getMessage();
$error_line = $e->getLine();
$error_file = $e->getFile();
}
return [
"status" => $status ?? "fail",
"message" => $message ?? "",
"error_line" => $error_line ?? 0,
"error_file" => $error_file ?? "",
"inputs" => [
"postal_codes" => $postal_codes ?? [],
"departments" => $departments ?? "",
"limit" => $limit ?? "",
"radius_miles" => $radius_miles ?? "",
],
"data" => $locations ?? [],
];
}
Get Products
Like locations, the Kroger Products docs allow you to search by many variables.
Purpose & Output:
- Return the first 1,000 products based on a search phrase.
Inputs:
- search term: fuzzy search based on input (ex. “milk”, “fish sticks”, “candy”)
- brand: limit products to a brand (ex. “Kellog’s”, “Red Baron”, “Coffee-Mate”)
- location ID: limit search to a specific store
– if no ID, then it will return national products with no prices
– if it has an ID, it will return products from that store with prices - start at: this is the number of items in the results to start at.
– the search returns results in batches of 1 to 50 products.
– in order to return 200 results you have to search 4 times (start at 1, 51, 101, and 151)
– this defaults to “1”.
– the function will auto loop through all “start at” batches up to 1,000 products.
Order of Operations:
- First, build a URL for each postal code we will search.
- Then it calls the PHP Guzzle async pool to concurrently gather responses from the Kroger API.
- Next, it gathers each unique location from all responses.
- Along with that, it formats the location address/details in a more compact format.
- Finally, the function returns a breakdown of status, (error) message, and data (locations).
public static function get_products(
$search_term = "",
$brand = "",
$location_id = "",
$start_at = 1
) {
try {
$start_time = microtime(true);
// what batch do we need to start at?
$start_at = intval($start_at) > 0 ? intval($start_at) : 1;
$limit = 50;
// build batch product urls
$urls_data = [];
for ($i = $start_at; $i < 1000; $i += $limit) {
// build url
$api = env("KROGER_API_URL");
$url = "$api/products?filter.limit=$limit&filter.start=$i";
// add other url vars
if ($search_term != "") {
$url .= "&filter.term=" . urlencode($search_term);
}
if ($brand != "") {
$url .= "&filter.brand=" . urlencode($brand);
}
if ($location_id != "") {
$url .= "&filter.locationId=" . urlencode($location_id);
}
$urls_data[] = [
"url" => $url,
"method" => "GET",
];
// if ($i >= 200) {
//
// }
}
$responses = [];
if (count($urls_data) > 0) {
$responses = self::guzzle_pool($urls_data, "product");
}
// consolidate products
$all_products = [];
foreach ($responses as $key => $response) {
// did this fail?
if (($response["status"] ?? "") != "success") {
$message = $response["message"] ?? "error getting api token";
throw new Exception($message, 1);
}
// set vars
$api_products = $response["data"]["data"] ?? [];
// if no products then skip
if (count($api_products) < 1) {
continue;
}
// add each product to all_products
foreach ($api_products as $key => $product) {
// id
$upc = $product["upc"];
// if this exists, then skip
if (!empty($all_products[$upc])) {
continue;
}
// collect "front" images
if (!empty($product["images"])) {
$product["image"] = [];
foreach ($product["images"] as $view) {
if (($view["perspective"] ?? "") == "front") {
foreach ($view["sizes"] as $image) {
$product["image"][$image["size"]] = $image["url"];
}
break;
}
}
}
$all_products[$upc] = $product;
// remove this product
// so we don't process it again
unset($api_products[$key]);
}
}
foreach ($all_products as $upc => $product) {
// gather prices
$prices = $product["items"][0]["price"] ?? [];
$retail = $prices["regular"] ?? 0;
$promo = $prices["promo"] ?? 0;
$discount = $retail - $promo;
if ($retail > 0 && $promo > 0 && $discount > 0) {
$discount_percent = round(($discount / $retail) * 100);
} else {
$discount_percent = 0;
}
// format keys
$all_products[$upc] = [
"product_id" => $product["productId"] ?? "",
"upc" => $product["upc"] ?? "",
"brand" => $product["brand"] ?? "",
"description" => $product["description"] ?? "",
"price_regular" => $retail,
"price_promo" => $promo,
"price_discount" => $discount,
"price_discount_percent" => $discount_percent,
"images" => json_encode($product["image"] ?? []),
"store_categories" => json_encode($product["categories"] ?? []),
];
}
$status = "success";
$message = "script complete";
} catch (Exception $e) {
$status = "fail";
$message = $e->getMessage();
$error_line = $e->getLine();
$error_file = $e->getFile();
}
return [
"status" => $status ?? "fail",
"message" => $message ?? "",
"error_line" => $error_line ?? 0,
"error_file" => $error_file ?? "",
"inputs" => [
"search_term" => $search_term ?? "",
"brand" => $brand ?? "",
"location_id" => $location_id ?? "",
"$start_at" => $$start_at ?? "",
],
"data" => $all_products ?? [],
];
}
PHP Guzzle Async Pool
Now we will handle all Kroger API examples URLs.
Purpose & Output:
- Concurrently handle all URL requests asynchronously
- Wait until all are done and return an array of responses.
Inputs:
- Receives an array of URLs in the format:
[
["url" => "http://domain.com/this/action", "method" => "GET"],
[...],
[...]
,]
- Also receives, the types of calls it is making (“generic”, “basic”, “product”).
– “generic”: the blank scope for locations
– “product”: the product scope for products
– “basic”: the type of authorization for generating a new token.
Order of Operations:
- Recieve, URLs data array.
- Start a PHP “do/while” loop attempting these calls 3 times.
- Make the authorization headers options for the type of calls we’re making.
- Create a function closure variable that we can trigger guzzle async requests.
- Call the closure variable with PHP guzzle async Pool.
- Loop through the results to find the “Response” (success) and “Exception” (fail) objects.
- If there are any serious errors, throw an Exception and stop.
- If there are any minor errors (timeouts, API temporarily down), loopback and try it again.
- Once complete, return an array of responses.
/*
* about guzzle_pool()
*
* - sends multiple requests concurrently and waits for all responses
* - retries up 3 times for any requests that return an invalid response
* - example: Kroger API will often return "all origins are down"
* which means we need to retry the request
* - if we retry, then we will generate a new header
* so that the tokens are all current and valid
* - NOTE: this pool expects the batch URLs to be of the same type.
* - example: all product API urls or all location API urls
*
* - returns an array of responses
* - example: [
* [
* "code" => string
* "reason" => string
* "body" => array
* ],
* [...],
* [...],
* ]
*
* - reference:
* - https://docs.guzzlephp.org/en/stable/quickstart.html?highlight=pool
* - https://hotexamples.com/examples/guzzlehttp/Pool/batch/php-pool-batch-method-examples.html
*/
private static function guzzle_pool($urls_data = [], $auth_type = "generic")
{
$ret = [];
$start_time = microtime(true);
if (
!is_array($urls_data) ||
count($urls_data) < 1 ||
!in_array($auth_type, ["generic", "product", "basic"])
) {
return $ret;
}
// set authorization
if ($auth_type == "generic") {
$options = [
"headers" => [
"Accept" => "application/json",
"Authorization" => "Bearer " . self::get_token("generic"),
],
];
} elseif ($auth_type == "product") {
$options = [
"headers" => [
"Accept" => "application/json",
"Authorization" => "Bearer " . self::get_token("product"),
],
];
} elseif ($auth_type == "basic") {
$options = [
"headers" => [
"Content-Type" => "application/x-www-form-urlencoded",
"Authorization" => "Basic " . env("KROGER_API_AUTH_KEY"),
],
];
}
// get client
$client = new Client();
// loop through batches
$good_responses = [];
$attempt_count = 0;
if (count($urls_data) > 0) {
do {
$attempt_count += 1;
// use closure that returns a promise
$requests = function ($urls_data, $options) use ($client) {
// loop through batches
foreach ($urls_data as $url_data) {
yield function () use ($client, $url_data, $options) {
// type of request
if (strtoupper($url_data["method"]) == "GET") {
// if GET
self::increment_api_call_count();
return $client->getAsync($url_data["url"], $options);
} elseif (strtoupper($url_data["method"]) == "POST") {
// if POST
self::increment_api_call_count();
return $client->postAsync($url_data["url"], $options);
}
};
}
};
// - Guzzle pool has a static method that will
// async call the requests and wait.
// - Then we translate this into a readable array
$results = Pool::batch($client, $requests($urls_data, $options));
// handle the responses
foreach ($results as $key => $response) {
$ret[$key] = [];
$obj_class = is_object($response)
? get_class($response)
: "not object";
$ret[$key]["object_class"] = $obj_class;
// success
if (
$obj_class == "GuzzleHttp\\Message\\Response" ||
$obj_class == "GuzzleHttp\\Psr7\\Response"
) {
// SUCCESS: response we're looking for
$headers = $response->getHeaders();
$contentType = $headers["Content-Type"][0];
$contentType = explode(";", $contentType);
$ret[$key]["status"] = "success";
$ret[$key]["status_code"] = $response->getStatusCode();
$ret[$key]["message"] = $response->getReasonPhrase();
$ret[$key]["reason"] = $response->getReasonPhrase();
$ret[$key]["content_type"] = $contentType[0];
if ($contentType[0] == "application/json") {
$ret[$key]["content_type"] = "json";
$ret[$key]["data"] = json_decode($response->getBody(), true);
} else {
$ret[$key]["data"] = $response->getBody();
$ret[$key]["full"] = $response;
}
} elseif (strpos($obj_class, "Exception") !== false) {
// FAIL: guzzle raised an exception
$message = strtolower($response->getMessage() ?? "");
$details = "(Kroger API: $message)";
$ret[$key]["status"] = "fail";
$ret[$key]["message"] = $message;
// decode the message
if (strpos($message, "invalid credentials") !== false) {
// Kroger API base64 key is missing
// stop and fix
throw new Exception("invalid credentials $details", 1);
} elseif (strpos($message, "404 not found") !== false) {
// The URL is incorrect
// stop and fix
throw new Exception("url not found $details", 1);
} elseif (strpos($message, "missing parameter") !== false) {
// URL get parameter missing
// stop and fix
throw new Exception("url not found $details", 1);
} elseif (strpos($message, "invalid access token") !== false) {
// access token is invalid,
} elseif (strpos($message, "all origins are down") !== false) {
// or Kroger API is down (temporarily)
} elseif (strpos($message, "internal server Error") !== false) {
// Kroger API had a problem
}
$ret[$key]["status"] = "fail";
$ret[$key]["message"] = "guzzle connection error, try again";
$ret[$key]["full"] = $response;
// echo "<pre>return: " . print_r($ret[$key], true) . "</pre>\n";
} else {
$ret[$key]["status"] = "fail";
$ret[$key]["full"] = $response;
// echo "<pre>return: " . print_r($ret[$key], true) . "</pre>\n";
}
}
// gather good responses
// and URLs to check again
$check_again = [];
foreach ($ret as $key => $result) {
// if success
if (($result["status"] ?? "") == "success") {
$good_responses[] = $result;
unset($urls_data[$key]);
continue;
}
// if fail
$result_json = json_encode($result);
echo "<pre>(check again) result: $result_json</pre>\n";
$check_again[] = $urls_data[$key];
}
$urls_data = $check_again;
// testing output
// echo "<pre>good responses: " . count($good_responses) . "</pre>\n";
// echo "<pre>check again count: " . count($urls_data) . "</pre>\n";
// if (count($urls_data) > 0) {
// echo "<pre>check again: " . print_r($urls_data, true) . "</pre>\n";
// }
// $exec_time = round(microtime(true) - $start_time, 3) . " seconds";
// echo "<p>exec_time: $exec_time</p>\n";
// loop again if we need to
} while (count($urls_data) > 0 && $attempt_count <= 3);
}
// clean up memory
unset($client);
return $good_responses;
}
Add A Testing Controller and Route
Create A Controller
Run the following.
./vendor/bin/sail artisan make:controller KrogerApiController
Edit the file.
<?php // file: app/Http/Controllers/KrogerApiController.php
<?php
namespace App\Http\Controllers;
use App\Models\M\KrogerApi;
use Illuminate\Http\Request;
class KrogerApiController extends Controller
{
public static function get_locations(Request $request)
{
$r = $request;
$pattern = "/[^0-9,;]/";
// call the api
$response = KrogerApi::get_locations(
preg_replace($pattern, "", $r->input("postal_codes", "")),
preg_replace($pattern, "", $r->input("departments", "")),
intval($r->input("limit", 20)),
intval($r->input("radius_miles", 10))
);
// output json
return response()->json($response);
}
public static function get_products(Request $request)
{
$r = $request;
$pattern = "/[^a-zA-Z0-9\s\-\'\,\.\&]/";
// call api
$reponse = KrogerApi::get_products(
preg_replace($pattern, "", $r->input("search_term", "")),
preg_replace($pattern, "", $r->input("brand", "")),
preg_replace("/[^0-9]/", "", $r->input("location_id", "")),
intval($r->input("start_at", 1))
);
return response()->json($reponse);
}
}
Create a Route
Edit the routes/api.php file.
// file: routes/api.php
use App\Http\Controllers\KrogerApiController;
Route::controller(KrogerApiController::class)->group(function () {
Route::get("/get_locations", "get_locations");
Route::get("/get_products", "get_products");
});
Kroger API Examples Results
Now we can view results in the browser.
http://localhost/api/get_locations?postal_codes=75201,75244
http://localhost/api/get_products?search_term=apples&location_id=03500529