Drupal 8: Consuming a Third-Party API with Drupal::httpClient(Guzzle) as a Service

Jun
2020
29

For a recent project, i was asked to build a service that would consume the client's internal data from his API. I got lucky with this because it was a very well structured and documented API. The only thing that i had not tried before was to use the new Drupal::httpClient() functionality in combination with a raw body post of (form-params) data (it was really hard - for me - to find a well written tutorial for this). The API could be used as a resource for getting POIs (Point of Interest) but the admin could also "create - post" pois in demand from his back office.

This is the working custom module awesome_api_client with all the necessary files in order to work right.

So first we need a .info.yml file for the module

name: 'Awesome Api'
type: module
description: 'A REST API custom module for retrieving and updating data through the Awesome API'
core: 8.x
package: 'Pixelthis'

Then we need a .services.yml file, for registering the service.

services:
  awesome_client:
    class: Drupal\awesome_api\AwesomeClient
    arguments:
     - '@http_client_factory'

in awesome.module file i use hook_cron for registering the API - GET resources call in recursive 15' cron tasks

awesome.module
<?php
function awesome_cron() {
 
$awesomeServiceClient = \Drupal::service('aweome_client');
?>

in web/custom/modules/awesome_api/src/AwesomeClient.php i added the actual PHP Client Class Service

<?php
namespace Drupal\awesome_api;
use
Drupal\Component\Serialization\Json;

//we need these for adding nodes and terms programmaticaly
use \Drupal\node\Entity\Node;
use
Drupal\taxonomy\Entity\Term;
/**
 * Class AwesoneClient.
 */
class AwesoneClient {

 
/**
   * @var \GuzzleHttp\Client
   */
 
protected $client;

 
/**
   * AwesoneClient constructor.
   *
   * @param $http_client_factory \Drupal\Core\Http\ClientFactory
   */
 
public function __construct($http_client_factory) {
    try {
     
$this->client = $http_client_factory->fromOptions([
       
'base_uri' => 'http://awesome_api_ip:15333/',
       
// 'headers' => [
        //   'Content-Type' => 'application/json',
        // ],
     
]);
    }
    catch (
RequestException $e) {
     
watchdog_exception('awesome_module', $e);
    }
  }

 
/**
  * Get all available pois
  *
  * @param int $amount
  *
  * @return array
  */
 
public function pois() {

  try {
   
$response = $this->client->get('pois', []);
   
$data = Json::decode($response->getBody());
    return
$data;
  }
  catch (
RequestException $e) {
   
watchdog_exception('awesome_module', $e);
  }

 }

 
/**
  * Get all available pois
  *
  * @param int $amount
  *
  * @return array
  */
 
public function poi(int $id) {

    try {

     
$response = $this->client->get('poi/'.$id);
     
$data = Json::decode($response->getBody());
      return
$data;
    }
    catch (
RequestException $e) {
     
watchdog_exception('awesome_module', $e);
    }

  }

  
/**
  * Add a new Poi from website to API
  *
  * @param array $api_data
  *
  * @return object
  */
 
public function add_api_poi(array $api_data) {

    try {

     
$headers = ['Content-Type' => 'application/json'];
     
$options = [
       
'body' => Json::encode($api_data),
       
'headers' => $headers,
      ];

      return
$response;

    }
    catch (
RequestException $e) {
     
watchdog_exception('awesome_module', $e);
    }

  }

 
/**
  * Get all available poi crowdiness stats
  *
  * @param int $id
  *
  * @return array
  */
 
public function poi_crowdiness(int $id,$timestamp_start = '2020-01-01 00:00:00',$timestamp_end = '2020-12-01 00:00:00') {

    try {

     
$options =  [
       
'form_params' => [
         
//%Y-%m-%d %H:%M:%S
         
'timestamp_start' => $timestamp_start,
         
'timestamp_end' => $timestamp_end,
        ],
      ];

     
$response = $this->client->get('poi/'.$id.'/stats',$options);
     
$data = Json::decode($response->getBody());
      return
$data;
    }
    catch (
RequestException $e) {
     
watchdog_exception('awesome_module', $e);
    }

  }


  public function
manage_poi($poi_api_data){

   
$poi_exists_id = $this->poi_exists_to_db($poi_api_data['id']);

    if (!
$poi_exists_id){
     
$this->add_poi_to_db($poi_api_data);
    }
    else {
     
$this->update_poi_to_db($poi_api_data,$poi_exists_id);
    }

  }

  private function
poi_exists_to_db($poi_api_id){

   
$query = \Drupal::entityQuery('node')
              ->
condition('type', 'poi')
              ->
condition('field_api_id', $poi_api_id);
     
$nids = $query->execute();

      if (!empty(
$nids)){
        return
current($nids);
      }

      return
false;

  }

  private function
update_poi_to_db($poi_api_data,$id){

   
$node = Node::load($id);

    if (
$node instanceof NodeInterface) {
      try {
       
$lat_lng = explode(',',$poi_api_data['geo_data']);

       
$node->set('title', $poi_api_data['name']);
       
$node->set('field_max_poi_population', $poi_api_data['max_poi_population']);
       
$node->set('field_poi_type_ref',$this->manage_term($poi_api_data['type'],'poi_type'));
       
$node->set('field_poi_address',$poi_api_data['address']);
       
$node->set('field_poi_category_ref',$this->manage_term($poi_api_data['category'],'poi_category'));
       
$node->set('field_geo_point',['lat' => trim($lat_lng[0]), 'lng' => trim($lat_lng[1])]);
       
$node->save();
      }
      catch (\
Exception $e) {
     
watchdog_exception('Poi API ERROR UPDATE', $e);
      }
    }

  }

  private function
add_poi_to_db($poi_api_data){

   
// 'address' => string(12) "Ormos Thiras"
    // 'category' => string(4) "Port"
    // 'geo_data' => string(20) "36.386122, 25.428413"
    // 'id' => integer1
    // 'max_poi_population' => integer2500
    // 'name' => string(14) "Santorini Port"
    // 'type' => string(20) "terminal_station_poi"

  
$lat_lng = explode(',',$poi_api_data['geo_data']);
   
$node = Node::create([
     
'type' => 'poi',
     
'title' => $poi_api_data['name'],
     
'field_api_id' => $poi_api_data['id'],
     
'field_geo_point' => ['lat' => trim($lat_lng[0]), 'lng' => trim($lat_lng[1])],
     
'field_max_poi_population' => $poi_api_data['max_poi_population'],
     
'field_poi_type_ref' => $this->manage_term($poi_api_data['type'],'poi_type'),
     
'field_poi_address' => $poi_api_data['address'],
     
'field_poi_category_ref' => $this->manage_term($poi_api_data['category'],'poi_category'),
    ]);

  
$node->save();

  }

  private function
manage_term($term_name,$vid){

   
$term = \Drupal::entityTypeManager()->getStorage('taxonomy_term')
    ->
loadByProperties(['name' => $term_name, 'vid' => $vid]);
   
$term = reset($term);

    if (
$term){
      return
$term->id();
    }

   
$term =Term::create([

     
'name' => $term_name,

     
'vid' => $vid,

      ])

      ->
save();
     
//Returns $term id value e.g 2,3,577 <-- integer
     
return $term;

  }

}
?>

Now i just wanted to add a custom Block too just for testing how to apply the Service with a Dependency Injection. So i added a /web/custom/modules/src/Plugin/Block/AwesomePois.php

<?php
<?php

namespace Drupal\awesome_api\Plugin\Block;

use
Drupal\Core\Block\BlockBase;
use
Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use
Symfony\Component\DependencyInjection\ContainerInterface;

use \
Drupal\node\Entity\Node;
use
Drupal\taxonomy\Entity\Term;

use
Drupal\Core\Http\ClientFactory;
/**
 * Provides a 'DefaultBlock' block.
 *
 * @Block(
 *  id = "awesome_pois",
 *  admin_label = @Translation("People Flow API Pois"),
 * )
 */
class PeopleFlowPois extends BlockBase implements ContainerFactoryPluginInterface {

 
/**
   * @var \Drupal\awesome_api\AwesomeClient
   */
 
protected $AwesomeClient;

 
/**
   * AwesomeClient constructor.
   *
   */
 
public function __construct(array $configuration, $plugin_id, $plugin_definition, $awesome_client) {
   
parent::__construct($configuration, $plugin_id, $plugin_definition);
   
$this->AwesomeClient = $awesome_client;
  }

   
/**
   * {@inheritdoc}
   */
 
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
     
$configuration,
     
$plugin_id,
     
$plugin_definition,
     
$container->get('awesome_client')
    );
  }


 
/**
   * {@inheritdoc}
   */
 
public function build() {

   
//Get all available Pois
   
$pois_list = $this->peopleFlowClient->pois();

    return [
     
'#theme' => 'item_list',
     
'#items' => $pois_list,
    ];
  }

}
?>
And that's about it :)
Tags: 
Cats: 
this is an arrow pointing back to the top of the page