Skip to main content

Insight | Apr 16, 2018

How to Keep your Drupal 8 site on Cloudflare Up to Date

By Ed Hornig

Or how to automatically purge Cloudflare caches on Drupal 8 when your site is updated.

       

Cloudflare is a free global CDN that can dramatically increase the speed and security of your website. Drupal 8 can be resource-heavy, so serving it behind a caching layer is a very good idea.

You can use the Cloudflare Drupal module and the free-tier Cloudflare service to quickly set up a caching system that will automatically purge the Cloudflare cache on a node url when the node is updated. But what about the other pages a node appears on? For example, a taxonomy page that lists all the nodes of a given taxonomy term. Drupal 8 internal caching handles this problem elegantly with “cache tags.” Any page that node 5 appears on will include the cache tag node:5, so Drupal knows that when node 5 is updated, all these pages need to have their caches cleared.

Unfortunately, only the Enterprise-tier Cloudflare supports purging by cache tags, so for most people this solution will not work. If you have Enterprise Cloudflare, read no further. Otherwise, the rest of this article will show you how to create a custom queuer plugin that you can use to queue relevant urls for purging on Cloudflare.

This solution is an alternative to the URLs Queuer module, which creates an unacceptable database strain for most sites.

We’ll create a custom module called my_purge and we’ll create the queuer plugin first at my_purge/src/Plugin/Purge/Queuer/MyQueuerPlugin.php:

<?php

namespace Drupal\my_purge\Plugin\Purge\Queuer;

use Drupal\purge\Plugin\Purge\Queuer\QueuerInterface;

use Drupal\purge\Plugin\Purge\Queuer\QueuerBase;

/**

* Queues URLs to be purged.

*

* @PurgeQueuer(

*   id = "my_queuer",

*   label = @Translation("My queuer"),

*   description = @Translation("Queues URLs to be purged."),

*   enable_by_default = true,

* )

*/

class MyQueuerPlugin extends QueuerBase implements QueuerInterface {}

Note that the id is “my_queuer.” Now we’ll create the queuer class. All it does is load the my_queuer plugin that we just created and initialize the invalidation factory and purge queue services. my_purge/src/Plugin/Purge/Queuer/MyQueuer.php:

<?php

namespace Drupal\my_purge\Plugin\Purge\Queuer;

class MyQueuer {

 public $purgeInvalidationFactory;

 public $purgeQueue;

 public $queuer;

 public function initialize() {

   if (is_null($this->queuer)) {

     $this->queuer = \Drupal::service('purge.queuers')->get('my_queuer');

     if ($this->queuer !== FALSE) {

       $this->purgeInvalidationFactory = \Drupal::service('purge.invalidation.factory');

       $this->purgeQueue = \Drupal::service('purge.queue');

     }

   }

   return $this->queuer !== FALSE;

 }

}

Now we can write code in my_purge.module using the MyQueuer class to to invalidate any urls we want in response to a node update. To cover new node creation and updates on existing nodes, we’ll call the my_purge_clear_associated_urls() function within hook_entity_insert() and hook_entity_update().

<?php

use Drupal\Core\Entity\EntityInterface;

use Drupal\my_purge\Plugin\Purge\Queuer\MyQueuer;

use Drupal\Core\Url;

use Drupal\Core\Cache\Cache;

/**

* Implements hook_entity_insert().

*/

function my_purge_entity_insert(EntityInterface $entity){

 my_purge_clear_associated_urls($entity);

}

/**

* Implements hook_entity_update().

*/

function heritage_purge_entity_update(EntityInterface $entity){

 my_purge_clear_associated_urls($entity);

}

In our example, whenever a node updates we want to clear the url for the node, the homepage url, and the urls for any taxonomy terms referenced in field_category of the node.

/**

* Clears urls associated with a node.

*

* @param \Drupal\Core\Entity\EntityInterface $entity

*    The node being updated.

*/

function heritage_purge_clear_associated_urls($entity) {

 // Clearing the drupal cache is necessary sometimes.

 Cache::invalidateTags(array('homepage_tag'));

 $queuer = new MyQueuer();

 $queuer->initialize();

 $urls = [];

 $options = ['absolute' => true];

 // Homepage

 $urls[] = Url::fromUri('internal:/', $options)->toString();

 // Node page

 $urls[] = Url::fromRoute('entity.node.canonical', ['node' => $entity->id()], $options)->toString();

 // Taxonomy term pages

 if ($entity->hasField('field_category')) {

   $categories = $entity->get('field_category')->getValue();

   foreach ($categories as $category) {

     $tid = $category['target_id'];

     $term_url = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $tid], $options);

     $urls[] = $term_url->toString();

   }

 }

 $invalidations = [];

 foreach($urls as $url) {

   $invalidation_type = 'url';

   $invalidations[] = $queuer->purgeInvalidationFactory->get($invalidation_type, $url);

   $queuer->purgeQueue->add($queuer->queuer, $invalidations);

 }

}

You can see that toward the end, we have an array of urls to purge. Alternatively, we could use paths instead and set the $invalidate_type to ‘path.’ Each url gets added to the queue, which can be configured to execute immediately, or during cron runs.

You will notice that we clear the Drupal cache for the homepage by using a cache tag with Cache::invalidateTags(array('homepage_tag')); That’s because  the Drupal cache is the first layer of caching, and if it doesn’t clear, then clearing the Cloudflare cache will have no effect.

We’re finished with a custom module that uses only 4 files (the .info file is not shown). Now, if everything has gone according to plan, whenever you create a new node or update an existing node, the changes will update immediately, and you’ll still get the benefit of having lightning-fast Cloudflare caching.

 

 

Drop us a line

Have a project in mind?

Contacting Third and Grove may cause awesomeness. Side effects include a website too good to ignore. Proceed at your own risk.

Reduced motion disabled