1
\$\begingroup\$

I would like to query a REST API (which uses Bearer token authentication), handling several exceptions that might occur. Could you tell me if the following code (which I explain later in this post) is any good, or do you have any advice on how to improve it?

Calling Script

This script asks the REST API to list all the products, which are paginated, thanks to calling the function getFromAkeneo. It delegates a closure as third parameter, which contains the iteration over the pages of the paginated collection returned by the REST API. This closure contains itself a call to getFromAkeneo in order to go to the next page.

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class Test extends Controller
{
    use \App\Http\Traits\AkeneoConnector;
    
    function test() {

        $response = $this->getFromAkeneo(config('akeneo.connections.rest_api.endpoint') . '/products', [
            'pagination_type' => 'search_after',
            'limit' => 100

        ], function($query_result) {

            do {

                $response_as_object = $query_result->object();
                var_dump($response_as_object->_links);
                
            } while(
                property_exists($response_as_object->_links, 'next') && $query_result = $this->getFromAkeneo($response_as_object->_links->next->href)
            );

        });

    }
    
}

Called Script

This script is the Trait called by the Calling Script. It handles the REST API's Bearer Authentication by storing in the file driver both access_token and refresh_token and overwritting them if needed (e.g. : access_token too old).

It defines the try / catch / catch. In the try block, the delegated function passed as third parameter by the Calling Script is called.

<?php
namespace App\Http\Traits;

use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Http;
use Carbon\Carbon;

use App\Exceptions\AkeneoTokenNotFoundException;

trait AkeneoConnector {

    private function authenticateForAkeneo() {
        if(empty(Storage::files('akeneo_access_token'))) {
            $queried_tokens = Http::withBasicAuth(config('akeneo.authentication.client_id'), config('akeneo.authentication.secret'))->post(config('akeneo.authentication.endpoint'), [
                "grant_type" => config('akeneo.authentication.grant_type'),
                "username" => config('akeneo.authentication.username'),
                "password" => config('akeneo.authentication.password')
            ])->throw();

        } elseif(now()->diffInSeconds(Carbon::createFromTimestamp(pathinfo(Storage::files('akeneo_access_token')[0])['filename'])) > 3000) {
            $queried_tokens = Http::withBasicAuth(config('akeneo.authentication.client_id'), config('akeneo.authentication.secret'))->post(config('akeneo.authentication.endpoint'), [
                "grant_type" => config('akeneo.authentication.grant_type_refresh'),
                "refresh_token" => Storage::get(Storage::files('akeneo_refresh_token')[0]),
            ])->throw();

        }

        if(isset($queried_tokens)) {
            $now = now();
            
            $queried_tokens_as_object = $queried_tokens->object();
            Storage::deleteDirectory('akeneo_access_token');
            Storage::put('akeneo_access_token/' . $now->timestamp, $queried_tokens_as_object->access_token);

            Storage::deleteDirectory('akeneo_refresh_token');
            Storage::put('akeneo_refresh_token/' . $now->timestamp, $queried_tokens_as_object->refresh_token);
        }
    }

    function getFromAkeneo($request_uri, $data = NULL, $delegated_function = NULL) {

        $query_result = NULL;

        try {
            $this->authenticateForAkeneo();

            $query_result = Http::withToken(Storage::get(Storage::files('akeneo_access_token')[0]))->get($request_uri, $data)->throw();

            if(isset($delegated_function)) {
                $delegated_function($query_result);
            }

        }
        catch(\Illuminate\Http\Client\RequestException | \Illuminate\Http\Client\ConnectionException $e) {
            if($e instanceof \Illuminate\Http\Client\RequestException) {
                switch($e->response->status()) {
                    case 403:
                        echo __('akeneo.errors.forbidden');
                        break;
                    case 404:
                        echo __('akeneo.errors.not_found');
                        break;
                    case 429:
                        echo __('akeneo.errors.too_many_requests');
                        break;
                }
            }
            
            echo __('akeneo.errors.connection_problem');
            report($e);

        }
        catch(\League\Flysystem\UnableToWriteFile | \Exception $e) {
            echo __('akeneo.errors.unable_to_query_unknown_reason');
            report($e);
        }

        return $query_result;
    }
    
}

The problems I can see

  • Disadvantage: A colleague who would discover the code and would try to write his own Calling Script, thus having to call the PHP trait, could wonder if the closure passed in the third parameter would not be a callback function executed in an asynchronous context defined by the trait (more precisely by the HTTP client of Laravel in the trait). But it is not: everything is synchronous. It is only a delegation of functions.

  • The try/catch/catch is executed several times by the Calling Script: at each iteration of the do-while loop (because of the call to getFromAkeneo in the while keyword conditional statement) and one time, at the very beginning of the Calling Script: the very first call to getFromAkeneo.

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.