Drupal 8, Plugins, Guzzle, CMI, Caching... If those buzzwords trigger your interest, you should keep reading this article. We will cover those topics as we are building one of our first Drupal 8 modules. Recently one of our clients requested a solution to integrate a custom feed called IBP Catalog. The IBP Catalog is a filterable XML feed, which enable to easily collect web component like banners, documents or even audio files. Those components are selected by the broker through a dedicated website.
So to recap, each broker has its own feed which can be filtered to return a list of web component urls. Those components could be injected into blocks through iframes for example.
How did we build our IBP catalog module? Let's start with the main ideas:
-
Create a HTTP client to fetch the XML feed
-
Implement a custom block
-
Define a caching strategy
-
Store and retrieve some config values
HTTP Client API
Drupal 7 provides its own - but quite minimal - outgoing HTTP capability through the drupal_http_request() method. Well, in Drupal 8, it's all gone away and replaced by \Drupal::httpClient(). Behind the static function you will find the very powerful and flexible Guzzle framework. Guzzle is a PHP HTTP client that makes it very easy to consume web services. Luckily Drupal 8 has already adopted its latest version (version 4), so you can enjoy all its trimmings. Guzzle has many advantages over the old drupal_http_request(). It's well documented, full of awesome features and very well supported. It's a huge step forward when it comes to consume external web services. Anyway enough talking and on with some code. For a full and workable version of the module, check out the contrib module called IBP Catalog on drupal.org.
// Create a HTTP client.
$client = \Drupal::httpClient();
// Set default options for our HTTP request.
// Define a 2 secondes timeout.
$client->setDefaultOption('timeout', 2);
// Create a GET request.
$request = $client->createRequest('GET', 'http://path_to_xml_feeds');
// Add a few query strings.
$query = $request->getQuery();
$query->set('key', 'value');
// Clients will only throw exceptions that are a subclass of GuzzleHttp\Exception\RequestException.
try {
$response = $client->send($request);
} catch (RequestException $e) {
// Do some stuff in case of the error.
}
// If successful HTTP query.
if ($response->getStatusCode() == 200) {
// We are expecting XML content. Yeah Guzzle can parse it!
try {
$xml = $response->xml();
} catch (ParseException $e) {
// Do some stuff in case of the error.
}
$xml = $response->xml();
// Do some stuff with the xml object.
}
This is it. This is the only code you need to call the web service and convert the response into a SimpleXMLElement object, which is easy to manipulate. Of course, Guzzle can do much more. To list a few, it can run requests in parallel, manages cookies, and nevertheless it also provides an events system. In fact, we are planning to use the event system to replace our (bad) blocking HTTP requests.
Block API
In Drupal 8, blocks are now plugins. Plugins are the object oriented paradigm of an info hook combine with a number of related implementation hooks, which put all together provide some reusable and specific functionality.
Defining a block is straight forward. First we need to create a class. For the IBP Catalog module, we called it IBPCatalogBlock and it's located undersrc/Plugin/Block/IBPCatalogBlock.php to follow the PSR-4 pattern. Blocks always implement the BlockPluginInterface interface. If you extend the abstract BlockBase class, you are only required to implement the build()method. Of course other methods can be overridden based on your requirements.
Next thing on the list is to define the specially formatted comments calledannotations. Annotations are meta information describing classes that can be read at runtime. People tend to love or hate them. What do you think ? In Drupal 8, block annotations enable your components to be discovered. They provide, in this case, useful metadata like an ID, a human label and a category.
Check our code on drupal.org for a full working example of our block implementation. Really, it's not that difficult, and it's kind of cool to have the whole implementation sitting in a single file/class.
/**
* Provides an 'IBP Catalog' block with the selected items from the feed.
*
* @Block(
* id = "ibpcatalog_block",
* admin_label = @Translation("ibpcatalog Block"),
* category = @Translation("Custom")
* )
*/
class IBPCatalogBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
// build your items list....
$items = ...
return array(
'#theme' => 'ibpcatalog',
'#items' => $items,
);
}
}
Caching API
As we are calling an external service, it's really important to cache the response. Not doing so will result in a sluggish website that will not only be a hassle for your visitors, but could also impact your search ranking. For those familiar with the Drupal 7 Cache API, picking up caching in Drupal 8 will not be a problem. Alongside an object oriented lifting, the caching APIoffers news features which make it more powerful and flexible:
-
Cache storage is separated into "bins", each containing various cache items. Common bins are "data", "bootstrap", "render"...
-
Cache tags makes cache invalidation better and smarter. Cache tags will improve your cache hit ratios and therefore your site performance.
-
New methods to permanently delete or invalidate the cache entries.
The example below is very easy. It uses the "data" bin and will cache the compiled data for one hour.
// Try to get block from the cache.
if ($cache = \Drupal::cache()->get($cid)) {
// Get cache.
$items = $cache->data;
}
else {
// Build cache and do you time consuming task.
$items = $this->getItems();
// Only store cache if valid.
if ($items) {
$expire = time() + 3600;
\Drupal::cache()->set($cid, $items, $expire);
}
}
Configuration API
Drupal 8 comes with an API to read and write configuration values. The simplest way to use this is to call the Drupal::config() static method.
/ Get module configuration.
$module_config = \Drupal::config('ibpcatalog.settings');
$this->key = $module_config->get('key');
Conclusion
The IBP Catalog is a very specific module. Brokers websites, which might use it, are definitely a small niche market. But the module implements a lot of new or inhanced features of Drupal 8. Feel free to dig into the code and contribute if you can. It's the best way to learn "the soon to be released" Drupal 8. You will find a practical example of how to create a config form, add a new admin page, play with the caching API and much more...