Using APITools in your Module

Last updated on
7 March 2024

This documentation needs review. See "Help improve this page" in the sidebar.

APITools provides a base Plugin you can extend in your own module. Defining a plugin will provide you with:

  1. A module configuration form with built-in support for the Key module (handles sensitive credentials)
  2. An API client that formats your URL and adds your defined auth() method to every request.

1. Add APITools as a Dependency

You can add in your module's composer.json file or in your projects composer.json. If developing a contributed module, add it to the module's composer.json file.

composer require 'drupal/apitools:^2.0@alpha'

See ZoomAPI module if you want an example of how to include in a module's composer.json file.

Add the dependency to your yourmodule.info.yml file.

name: Your Module API
type: module
description: 'Your module description'
package: Web Services
core_version_requirement: ^9.1 || ^10
configure: apitools.client_config_form.yourmodule
dependencies:
  - apitools:apitools

2. Create your APITools Plugin

There are no required methods when extending Drupal\apitools\Api\Client\ClientBase but there are some important things to note.

The annotation is very important for defining your configuration form.

You can hard variables in your Plugin or APITools provides an easy way to create a configuration form.

Currently, there are two field types by default. "type" = "key_select" will integrate with Drupal's Key module to store sensitive data you don't want stored as plain text in your configuration. The default field type is just a textfield. 

There is no required configuration, but it does assume you are using:

base_uri for the base uri of the api (ex. https://api.yourapi.com)

base_path for the base path of the api (ex. v2)

Defining those will automatically build your URL correctly when making API calls.

 *   config = {
 *     "client_id" = @Translation("Client ID"),
 *     "client_secret" = {
 *       "type" = "key_select",
 *       "title" = @Translation("Client Secret"),
 *     },
 *     "base_uri" = @Translation("Base URI"),
 *   }

The auth() Method is called before any request.

This is where you can add in any sort of authentication that is required by the API you are integrating with. You can modify $this->options and can modify any of the options that ultimately get passed along to Guzzle.

Other Methods

Out of the box, ClientBase provides you with typical put, patch, get, post, delete methods. They are calling $this->auth()->request($method, $path, $options); If you want to create your own more specific methods, you are welcome to.

Example: /src/Plugin/ApiTools/Client.php

<?php

namespace Drupal\yourmodule\Plugin\ApiTools;

use Drupal\apitools\Api\Client\ClientBase;
use Drupal\apitools\ClientManagerInterface;
use Drupal\apitools\ClientResourceManagerInterface;
use Drupal\Component\Datetime\Time;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\key\KeyRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a client to connect to the YOUR API.
 *
 * @ApiToolsClient(
 *   id = "yourpluginid",
 *   admin_label = @Translation("YourAPI"),
 *   api = "yourpluginid",
 *   config = {
 *     "account_id" = @Translation("Account ID"),
 *     "client_id" = @Translation("Client ID"),
 *     "client_secret" = {
 *       "type" = "key_select",
 *       "title" = @Translation("Client Secret"),
 *     },
 *     "event_secret_token" = {
 *       "type" = "key_select",
 *       "title" = @Translation("Event Secret Token"),
 *     },
 *     "base_uri" = @Translation("Base URI"),
 *     "base_path" = @Translation("Base Path"),
 *     "auth_token_url" = @Translation("Auth Token URL")
 *   }
 * )
 */
class Client extends ClientBase {

  /**
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, ClientManagerInterface $client_manager, ClientResourceManagerInterface $resource_manager, ConfigFactoryInterface $config_factory, KeyRepositoryInterface $key_repository, Time $time, LoggerChannelInterface $logger) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $client_manager, $resource_manager, $config_factory, $key_repository, $time);
    $this->logger = $logger;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('plugin.manager.apitools_client'),
      $container->get('plugin.manager.apitools_client_resource'),
      $container->get('config.factory'),
      $container->get('key.repository'),
      $container->get('datetime.time'),
      $container->get('logger.channel.yourmodule')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return parent::defaultConfiguration() + [
      'base_uri' => 'https://api.yourapi.com',
      'base_path' => 'v2',
      'auth_token_url' => 'https://api.yourapi.com/oauth/token',
    ];
  }

  /**
   * {@inheritdoc}
   */
  protected function auth() {
    // This can be adjusted to authenticate however you need.
    // auth() is called before every request.
    if ($access_token = $this->ensureAccessToken()) {
      $this->options->set('headers', [
        'authorization' => 'Bearer ' . $access_token,
      ]);
    }

    return $this;
  }

  /**
   * Ensures the request is made with an access token.
   *
   * @return string
   *   A zoom access token.
   */
  private function ensureAccessToken() {
    if (!$this->getToken('access_token')) {
      $account_id = $this->getConfigValue('account_id');
      $client_id = $this->getConfigValue('client_id');
      $client_secret = $this->getConfigValue('client_secret');
      $options = [
        'auth' => [$client_id, $client_secret],
        'form_params' => [
          'account_id' => $account_id,
          'grant_type' => 'account_credentials',
        ],
      ];
      $response_data = $this->request('POST', $this->getConfigValue('auth_token_url'), $options);
      if (!empty($response_data['access_token']) && !empty($response_data['token_type']) && $response_data['token_type'] === 'bearer') {
        $this->setTokenExpiresIn('access_token', $response_data['expires_in']);
        $this->setToken('access_token', $response_data['access_token']);
      }
    }
    return $this->getToken('access_token');
  }

  /**
   * {@inheritdoc}
   */
  protected function postRequest($response) {
    return Json::decode($response);
  }

  /**
   * Checks for required config and then attempts to create an access token.
   *
   * @return bool
   *   TRUE or FALSE.
   */
  public function validateConfiguration() {
    $account_id = $this->getConfigValue('account_id');
    $client_id = $this->getConfigValue('client_id');
    $client_secret = $this->getConfigValue('client_secret');
    if (empty($account_id) || empty($client_id) || empty($client_secret)) {
      return FALSE;
    }
    return $this->ensureAccessToken();
  }

}

3. Reference the plugin in your yourmodule.services.yml file

You can always do something like:

\Drupal::service('apitools.client_manager')->load('yourapipluginid') without defining your own service, this may make things more convenient for you:

services:
  logger.channel.yourmodule:
    parent: logger.channel_base
    arguments: ['yourmodule']
  yourmodule.client:
    class: Drupal\yourmodule\Plugin\ApiTools\Client
    parent: apitools.client_base
    arguments: ['yourmodule']

4. Access the configuration screen

Ignore this part if you chose to not include configuration in your plugin annotation.

APITools automatically provides a configuration screen at this route apitools.client_config_form.yourpluginid or at this url /admin/config/services/yourpluginid

5. Make API calls

Examples will vary based on how the various methods available in your plugin. Out of the box, ClientBase provides you with typical put, patch, get, post, delete methods. They are calling $this->auth()->request($method, $path, $options);

$client = \Drupal::service('yourmodule.client');

// Get Example.

$client->get('users');

Help improve this page

Page status: Needs review

You can: