LocalGov Drupal: Rewriting Waste Collection Data Provider Classes To Alter Data

LocalGov Drupal is a Drupal distribution that combines Drupal, some configuration, and a collection of modules with the aim of making it easier for councils to create websites. The functionality provided includes content pages, news pages, bus timetables, and waste collection systems. What's more, it's maintained by a vibrant community of people.

The LocalGov Drupal Waste Collection module is a module that allows local bin collection schedules to be displayed to users. This uses a combination of an address lookup and collection data to show users the bins collection schedule for that address for the next few months.

A plugin interface is used to allow different banks of data to be used in the bank end of the module, with CSV and Whitespace API integration coming with the module. Whitespace is a company used across the UK to manage waste collection systems and the Whitespace plugin interfaces with a SOAP API to pull bin collection data into the site.

I've been using the Waste Collection module for a little while now with a few different projects. Whilst the information that the module provides is good, I needed to alter this data in a recent project with Central Bedfordshire. This required the use of a hook to alter the Whitespace plugin class and output more customised waste collection schedules.

In this article I will go through the issue I needed to correct, and how I altered the data coming out of the Whitespace API without creating a new plugin.

The Problem

When you set up Whitespace integration using this module it pulls a set of service names, which is essentially a list of the types of collections that can happen. This will be things like "Refuse (black bin)" or "Recycling" and shows residents what sort of bin they need to put out for collection on that day.

The issue we had was that Central Bedfordshire had three different types of bin collection, but the food waste collection happened on the same day as the other two types. This meant that all of their collection listings were duplicated, so a single day would get one item for a certain bin type and another listing for food waste.

The LocalGov Drupal Waste Collection module output, showing food waste collection, refuse collection, and recycling collection schedules.

Due to the fact that this was a little confusing to look at we needed to remove the food waste listing from the display and rename the refuse and recycling bin collections to include the food waste bin. Thankfully, renaming the service type is easy through the module interface so we could just append the food waste collection to the end of the label for the other two collection types.

Altering how the data was rendered required some customisation of the module.

Using hook_data_provider_info_alter()

This hook is called when a plugin is loaded by Drupal and is used to allow modules to tweak the configuration of the loaded plugins. If we listen out for the "whitespace_data_provider" plugin we can inspect the array to see what we have to play with.

Array (
    [whitespace_data_provider] => Array (
            [id] => whitespace_data_provider
            [label] => Drupal\Core\StringTranslation\TranslatableMarkup Object
                (
                    [string:protected] => Whitespace Data Provider
                )
            [class] => Drupal\localgov_waste_collection_whitespace_provider\Plugin\DataProvider\WhitespaceDataProvider
            [provider] => localgov_waste_collection_whitespace_provider
        )
)

The class Drupal\localgov_waste_collection_whitespace_provider\Plugin\DataProvider\WhitespaceDataProvider is where all of the API integration and processing happens for the Whitespace module. I've looked through this class (and the module itself) quite a bit recently and it contains no hooks to allow the data to be altered after it is generated.

So, the solution here is to simply swap out this class for our own custom Whitespace data provider class, which will do what we need.

The following snippet is an implementation of the hook_data_provider_info_alter hook, which watches for the whitespace_data_provider plugin and changes the class definition to a class in the custom module we created.

namespace Drupal\my_module\Hook;

use Drupal\my_module\Plugin\DataProvider\CbcWhitespaceDataProvider;

class LocalGovWasteCollectionHooks {

  /**
   * Implements hook_data_provider_info_alter().
   */
  #[Hook('data_provider_info_alter')]
  public function dataProviderInfoAlter(array &$data_provider_info) {
    if (isset($data_provider_info['whitespace_data_provider'])) {
      $data_provider_info['whitespace_data_provider']['class'] = MyCustomWhitespaceDataProvider::class;
    }
  }

}

This change to the plugin definition is written to Drupal's cache mechanism so if you want to update this hook in the future then remember to flush the caches to update those changes.

Still Using Drupal 10?

If your site is still on Drupal 10 then you just need to add a couple of files to get this working. We'll do this in a future proof way so that when you do update to Drupal 11 it should just work in the same way as before.

First, you need to register the hook class as a service in your module's *.services.yml file.

services:
  my_module_localgov_waste_collection_hooks:
    class: \Drupal\cbc_module\Hook\LocalGovWasteCollectionHooks
    autowire: true

Then, create the same hook in the module's *.module file. All this needs to do is make a call to the hook method in the service, passing in the same information that this procedural hook received.

use Drupal\Core\Hook\Attribute\LegacyHook;

/**
 * Implements hook_data_provider_info_alter().
 */
#[LegacyHook]
function cbc_module_data_provider_info_alter(array &$data_provider_info) {
  \Drupal::service('cbc_module_localgov_waste_collection_hooks')->dataProviderInfoAlter($data_provider_info);
}

Once you've flushed the Drupal caches this code will work in the same way as the Drupal 11 version.

Overriding Whitespace

Out intent here is to look through the collections from the Whitespace API and remove any food waste items in the list. Rather than re-implementing the entire Whitespace API class again we can just extend the original WhitespaceDataProvider and override the methods that we need.

As it turns out, we only need to override one method here, the getCollections() method, and even then we still call the getCollections() method of the parent class to get data from the API. This prevents us from inadvertantly introducing bugs into how the Whitespace API integration works, but also means that we can keep our custom code nice and small.

This is the skeleton class that now sits between our side and the Whitespace API.

namespace Drupal\my_module\Plugin\DataProvider;

use Drupal\localgov_waste_collection_whitespace_provider\Plugin\DataProvider\WhitespaceDataProvider;

class MyCustomWhitespaceDataProvider extends WhitespaceDataProvider {

  /**
   * Remove certain items from the collection list.
   *
   * {@inheritDoc}
   *
   * @return array<string, array<string>>
   *   The collection data.
   */
  public function getCollections(string $uprn): array {
    $collections = parent::getCollections($uprn);

    // Do the thing.

    return $collections;
  }

}

The $collections array here is just a list of dates and the type of bin being collected. Each item looks like this:

Array (
    [date] => 02-07-2026
    [bin] => Food Waste Collection Service
    [holiday] => 
    [type] => Array (
            [name] => Food Waste Collection Service
            [label] => Food Waste Collection
            [colour] => 
        )
)

All we have to do then is loop through these items and if the bin value in the item is "Food Waste Collection Service" then we need to remove that item from the list. That label comes from Whitespace and is not the overridden label from the Drupal configuration, so we can be reasonably sure that this won't change.

Here's the final class that does this.

namespace Drupal\my_module\Plugin\DataProvider;

use Drupal\localgov_waste_collection_whitespace_provider\Plugin\DataProvider\WhitespaceDataProvider;

class MyCustomWhitespaceDataProvider extends WhitespaceDataProvider {

  /**
   * The Food Waste Collection Service label.
   */
  public const string FOOD_WASTE_SERVICE_LABEL = 'Food Waste Collection Service';

  /**
   * Remove certain items from the collection list.
   *
   * {@inheritDoc}
   *
   * @return array<string, array<string>>
   *   The collection data.
   */
  public function getCollections(string $uprn): array {
    $collections = parent::getCollections($uprn);
    if (isset($collections['dates']) && count($collections['dates']) > 0) {
      foreach ($collections['dates'] as $id => $collection) {
        if (isset($collections['dates'][$id]['bin']) && $collections['dates'][$id]['bin'] === static::FOOD_WASTE_SERVICE_LABEL) {
          unset($collections['dates'][$id]);
        }
      }
    }
    return $collections;
  }

}

I dislike having random strings in the code that don't describe what they are, so I tend to pull them out into class constants. This also means that if the label does change (however unlikely) then we can update it easily.

Conclusion

The result of this hook override is that our bin collection schedule does not contain any food waste collection items.

This is what the waste collection schedule looks like now.

The Waste collection schedule for August after the update. This shows just 4 items instead of the 8 items previously shown.

This is much easier to read, and allows Central Bedfordshire to operate their bin collection service without having to change anything. Altering output to suit the needs of the user, rather than altering how internal services operate to accommodate the website is always a good strategy.

The original decision to use plugins to handle the different data really allows this module to be used with any number of plugins. If those plugins don't quite fit the bill then we can easily override them without disrupting the underlying functionality too much.

Add new comment

The content of this field is kept private and will not be shown publicly.