Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Feb 24 2021
Feb 24

Following yesterday's Entity Metadata Wrapper blog post and as I continued to work on this task, I noticed some duplication and found that I was repeating several of the same chaining steps in different methods in the same file. For example:

public function getFirstName(): string {
  return $this
    ->get('profile_user_basic') // Get the pupil's profile.
    ->get('field_first_name')
    ->value();
}

private function getTeacherFirstName(): string {
  $this
    ->get('profile_student') // Get the pupil's profile.
    ->get('field_class') // Get the pupil's class.
    ->get('field_teacher') // Get the class' teacher.
    ->get('profile_user_basic') // Get the teacher's profile.
    ->get('field_first_name')
    ->value();
}

In both cases, the last three lines are the same, where the same profile type is loaded, and the value is loaded from a field.

I wanted to find a way to remove this duplication whilst also making the code more readable. Ideally, this would mean adding a method like getFirstNameFromBasicProfile() that would group the last three steps.

Extending the EntityDrupalWrapper

I've done this before, where I've created a custom wrapper class with its own methods and extends EntityDrupalWrapper. This is how that might look:

final class PupilWrapper extends \EntityDrupalWrapper {

  public function __construct(\stdClass $data, $info = []) {
    parent::__construct('user', $data, $info);
  }

  public function getFirstName(): string {
    return $this->getFirstNameFromBasicProfile();
  }

  public function getTeacherFirstName(): string {
    return $this
      ->get('profile_student')
      ->get('field_class')
      ->get('field_teacher')
      ->getFirstNameFromBasicProfile();
  }

  private function getFirstNameFromBasicProfile(): string {
    return $this
      ->get('profile_user_basic')
      ->get('field_first_name')
      ->value();
  }

}

Whilst this has worked in previous situations, this time I had this error:

Error: Call to undefined method EntityDrupalWrapper::getFirstNameFromBasicProfile() in Drupal\my_module\EntityWrapper\PupilWrapper->getTeacherFirstName

This is because the get() method is returning an instance of EntityStructureWrapper (another class that extends EntityDrupalWrapper) which means that getFirstNameFromBasicProfile() is not accessible though it's in the same file.

I tried overridding the get() method but wasn't able to get this to work.

Decorating the EntityDrupalWrapper

Another option that I tried was to follow the Decorator design pattern, and add a new class that takes an EntityDrupalWrapper as an argument as uses it internally but doesn't extend it. Here's an example:

final class PupilWrapper {

  private $accountWrapper;

  public function __construct(\EntityMetadataWrapper $accountWrapper) {
    $this->accountWrapper = $accountWrapper;
  }

  public function getFirstName(): string {
    return $this->getFirstNameFromBasicProfile();
  }

  public function getTeacherFirstName(): string {
    return $this
      ->get('profile_student')
      ->get('field_class')
      ->get('field_teacher')
      ->getFirstNameFromBasicProfile();
  }

  private function getFirstNameFromBasicProfile(): string {
    return $this
      ->get('profile_user_basic')
      ->get('field_first_name')
      ->value();
  }

}

In this case, the constructor argument is an instance of EntityMetadataWrapper so that it could be either an EntityDrupalWrapper or EntityStructureWrapper.

Re-adding required wrapper methods

As the get() method is missing, this would cause an error:

Error: Call to undefined method Drupal\my_module\EntityWrapper\PupilWrapper::get() in Drupal\my_module\EntityWrapper\PupilWrapper->getFirstName()

However, we can re-add it, have it get the value from accountWrapper and return another instance of PupilWrapper so that getFirstNameFromBasicProfile() will be available.

public function get(string $property): self {
  return new self($this->accountWrapper->get($property));
}

The value() method is also required, but this can delegate to the decorated wrapper:

Error: Call to undefined method Drupal\my_module\EntityWrapper\PupilWrapper::value() in Drupal\my_module\EntityWrapper\PupilWrapper->getFirstName()

public function value(): string {
  return $this->accountWrapper->value();
}

Conclusion

This was the first time that I tried extending Drupal 7's entity metadata wrappers in this way, but it worked well, removes the duplication and cleans up the code further.

Feb 23 2021
Feb 23

Today I needed to load some Drupal user data via a profile2 profile. When looking into this, most resources that I found suggest using this approach and calling the profile2_load_by_user() function directly and passing in the user object:

$account = user_load(...);

$accountWrapper = new EntityDrupalWrapper('user', $account);
// or `$accountWrapper = entity_metadata_wrapper('user', $account);

$profile = profile2_load_by_user($account->value());
// or `$profile = profile2_load_by_user($account);`

$profileWrapper = new EntityDrupalWrapper('profile2', $profile);

$firstName = $profileWrapper->get('field_first_name')->value();

This though requires a few steps, and as I'm a fan of object-orientated code and Entity Metadata Wrappers, I wanted to find a cleaner solution.

This is my preferred method that uses method chaining. It returns the same value, is less code, and in my opinion, it's cleaner and easier to read.

$firstName = $accountWrapper
  ->get('profile_user_basic')
  ->get('field_first_name')
  ->value();
Jan 04 2021
Jan 04

Note: This post is written with a Drupal context, but applies to any PHP project.

This is a test that I wrote recently, which uses the camel case method name that is recommended by the Drupal and PSR-2 coding standards:

public function testThatPathAliasesAreNotTransferredToTheNewLanguageWhenOneIsAdded(): void {
  // ...
}

It has a long method name that describes the test that is being run. However, it's quite hard to read. Generally, I prefer to write tests like this, using the @test annotation (so that I can remove the test prefix) and snake case method names:

/** @test */
public function path_aliases_are_not_transferred_to_the_new_language_when_one_is_added(): void {
  // ...
}

This to me is a lot easier to read, particularly for long and descriptive test method names, and is commonly used within parts of the PHP community.

This approach, however, can result in some errors from PHPCS:

  • The open comment tag must be the only content on the line
  • Public method name "DefinedLanguageNodeTest::path_aliases_are_not_transferred_to_the_new_language_when_one_is_added" is not in lowerCamel format

We can avoid the errors by excluding the files when running PHPCS, or modifying rules within phpcs.xml (or phpcs.xml.dist) file to change the severity value for the rules. These approaches would mean either ignoring all PHPCS sniffs within the test files or ignoring some checks within all files, neither of which is an ideal approach.

Ignoring whole or partial files

We can tell PHPCS to ignore whole or partial files by adding comments - there's an example of this at the top of default.settings.php file:

// @codingStandardsIgnoreFile

The @codingStandards syntax, however, is deprecated and will be removed in PHP_CodeSniffer version 4.0. The new syntax to do this is:

// phpcs:ignoreFile

As well as phpcs:ignoreFile which ignores all of the sniffs in an entire file, there are also commands to disable and re-enable PHPCS at different points within the same file:

// Stop PHPCS checking.
// phpcs:disable

// Start PHPCS checking.
// phpcs:enable

Disabling specific rules in a file

As well as excluding a section of code from checks, with phpcs:ignore you can also specify a list of sniffs to ignore. For example:

// phpcs:disable Drupal.Commenting.DocComment, Drupal.NamingConventions.ValidFunctionName

By adding this to the top of the test class, these specific sniffs will be ignored so no errors will be reported, and any other sniffs will continue to work as normal.

If you're unsure what the names of the sniffs are that you want to ignore, add -s to the PHPCS command to have it include the sniff names in its output.

For more information on ignoring files, folders, part of files, and limiting results, see the Advanced Usage page for the PHP CodeSniffer project on GitHub.

You can also see this being used in some of the tests for this website.

Sep 05 2020
Sep 05

This week I gave a new talk on upgrading to Drupal 9 for the Drupal NYC meetup. Whilst preparing for that, I decided to upgrade my Dransible example project that I use for my Ansible and Ansistrano talk to Drupal 9 and document the process.

Whilst the steps taken are in the slides for that talk, here is the full list of steps that I took including the Composer commands.

Updating from Drupal 8.8 to 8.9

To begin with, let's update to the latest version of Drupal 8 so that we can do some testing and see all of the latest deprecation notices before moving to Drupal 9.

  1. Remove Drush temporarily using composer remove drush/drush as it will cause us being stuck on Drupal 8.9.0-beta2 rather than a newer, stable 8.9 version.
  2. Update ^8.8 to ^8.9 in composer.json for drupal/core-recommended, drupal/core-dev and drupal/core-composer-scaffold, and run composer update drupal/core-* --with-dependencies to update core to 8.9.5.
  3. Re-add Drush so that it's present for the deployment by running composer require drush/drush:^9.

Preparing for Drupal 9

  1. Add the Upgrade Status module by running composer require drupal/upgrade_status.
  2. Upgrade to Drush 10 by running composer require drush/drush:^10.
  3. Remove the Config Installer module by running composer remove drupal/config_installer. This is no longer needed since Drupal 8.6, and there will be no Drupal 9 version.
  4. Update the Admin Toolbar module to 2.3, a Drupal 9 compatible version, by running composer update drupal/admin_toolbar.

As I'd previously updated the Simple Message custom module to be Drupal 9 compatible (adding the core_version_requirement key to the info.yml file, and removing usages of deprecated code), no changes needed to be made to that.

Upgrading to Drupal 9

  1. Update ^8.9 to ^9.0 for the core packages in composer.json, and run composer update drupal/core-* --with-dependencies to update to 9.0.5.
  2. Re-add Drush by running composer require drush/drush. This will install Drush 10 by default.

Post upgrade

Although everything seemed to have updated OK locally, there were some errors when running a deployment to the Vagrant virtual machine that needed to be addressed, as well as some post-upgrade housekeeping steps to perform.

  1. Fix the deployment error by adding the Symfony Configuration component as a dependency by running composer require symfony/config:^4.
  2. Alias Drupal\Core\Messenger\MessengerInterface to messenger in simple_message.services.yml to fix the autowiring error.
  3. Add settings["config_sync_directory"] to settings file variables (this will be added automatically in the next version of the Drupal settings Ansible role).
  4. Remove the Upgrade Status module by running composer remove drupal/upgrade_status, as it's no longer needed.

And that's it! The Dransible demo project is upgraded, and if you see my Ansible deployments talk in the future, the demo site will be running on Drupal 9.

If you want to see the original pull request, it's at https://github.com/opdavies/dransible/pull/7.

May 20 2020
May 20

I recently finished porting this website from a static site generator to Drupal 8, meaning that this site has now been powered by three different major versions of Drupal (6, 7 and 8) as well as by two static site generators since it was first launched in early 2010.

The majority of the content was imported using migrations from JSON feeds that I created. This included:

  • Blog tags
  • Blog posts
  • Talks
  • Redirects

In some follow-up posts, I'll be looking at each migration separately, describing any issues and look at how it was used to import its respective content.

I'll update this post with the links to the follow-up posts, and they are also available from the blog series' page.

Apr 22 2020
Apr 22

Some time ago, I announced that I was planning on writing a book on automated testing and test driven development with Drupal. I created a landing page and set up a mailing list, but I wasn't sure at that point what I was going to cover or create as part of the book.

I'm going to write a book on automated testing in Drupal. Join the mailing list for updates, and I'm happy to take suggestions on what to cover. https://t.co/YXNpe6f8Ft #drupal

— Oliver Davies (@opdavies) May 15, 2018

Being a meetup and DrupalCamp conference organiser, after some thought I decided to build a website for an example conference, and that some of this code would then be included in the book as example content. This seemed to cover most of what I originally wanted, through features like a call for papers for potential speakers to propose sessions, allowing organisers to administer and moderate those proposals, automatically sending notification emails to submitters and displaying the accepted sessions.

I've started building it with Drupal 8.8 and it is now available on GitStore to purchase access to, including all future updates as I continue building the application - adding new features and upgrading to Drupal 9 once it is released. There are some other interesting things there too, such as using feature flags to enable or disable functionality, and using GitHub Actions to run the tests automatically.

The book itself I've added a page for on Leanpub, and I'll be continuing to add content to it in parallel to building the example codebase. Once there is enough content, I will release the first draft for purchase.

Any purchases that are made via Gitstore or Leanpub, an amount will be donated to the Drupal Association and the #DrupalCares campaign to help sustain the Association during COVID-19.

Oct 24 2018
Oct 24

Today I found another instance where I decided to use Illuminate Collections within my Drupal 8 code; whilst I was debugging an issue where a Drupal Commerce promotion was incorrectly being applied to an order.

No adjustments were showing in the Drupal UI for that order, so after some initial investigation and finding that $order->getAdjustments() was empty, I determined that I would need to get the adjustments from each order item within the order.

If the order were an array, this is how it would be structured in this situation:

$order = [
  'id' => 1,
  'items' => [
    [
      'id' => 1,
      'adjustments' => [
        ['name' => 'Adjustment 1'],
        ['name' => 'Adjustment 2'],
        ['name' => 'Adjustment 3'],
      ]
    ],
    [
      'id' => 2,
      'adjustments' => [
        ['name' => 'Adjustment 4'],
      ]
    ],
    [
      'id' => 3,
      'adjustments' => [
        ['name' => 'Adjustment 5'],
        ['name' => 'Adjustment 6'],
      ]
    ],
  ],
];

Getting the order items

I started by using $order->getItems() to load the order’s items, converted them into a Collection, and used the Collection’s pipe() method and the dump() function provided by the Devel module to output the order items.

collect($order->getItems())
  ->pipe(function (Collection $collection) {
    dump($collection);
  });

Get the order item adjustments

Now we have a Collection of order items, for each item we need to get it’s adjustments. We can do this with map(), then call getAdjustments() on the order item.

This would return a Collection of arrays, with each array containing it’s own adjustments, so we can use flatten() to collapse all the adjustments into one single-dimensional array.

collect($order->getItems())
  ->map(function (OrderItem $order_item) {
    return $order_item->getAdjustments();
  })
  ->flatten(1);

There are a couple of refactors that we can do here though:

  • Use flatMap() to combine the flatten() and map() methods.
  • Use higher order messages to delegate straight to the getAdjustments() method on the order, rather than having to create a closure and call the method within it.
collect($order->getItems())
  ->flatMap->getAdjustments();

Filtering

In this scenario, each order item had three adjustments - the correct promotion, the incorrect one and the standard VAT addition. I wasn’t concerned about the VAT adjustment for debugging, so I used filter() to remove it based on the result of the adjustment’s getSourceId() method.

collect($order->getItems())
  ->flatMap->getAdjustments()
  ->filter(function (Adjustment $adjustment) {
    return $adjustment->getSourceId() != 'vat';
  });

Conclusion

Now I have just the relevant adjustments, I want to be able to load each one to load it and check it’s conditions. To do this, I need just the source IDs.

Again, I can use a higher order message to directly call getSourceId() on the adjustment and return it’s value to map().

collect($order->getItems())
  ->flatMap->getAdjustments()
  ->filter(function (Adjustment $adjustment) {
    return $adjustment->getSourceId() != 'vat';
  })
  ->map->getSourceId();

This returns a Collection containing just the relevant promotion IDs being applied to the order that I can use for debugging.

Now just to find out why the incorrect promotion was applying!

Aug 21 2018
Aug 21

I’ve been experimenting with moving some code to Drupal 8, and I’m quite intrigued by a different way that I’ve tried to structure it - using event subscribers, building on some of the takeaways from Drupal Dev Days.

Here is how this module is currently structured:

Note that there is no opdavies_blog.module file, and rather than calling actions from within a hook like opdavies_blog_entity_update(), each action becomes it’s own event subscriber class.

This means that there are no long hook_entity_update functions, and instead there are descriptive, readable event subscriber class names, simpler action code that is responsibile only for performing one task, and you’re able to inject and autowire dependencies into the event subscriber classes as services - making it easier and cleaner to use dependency injection, and simpler write tests to mock dependencies when needed.

The additional events are provided by the Hook Event Dispatcher module.

Code

opdavies_blog.services.yml:

services:
  Drupal\opdavies_blog\EventSubscriber\PostToMedium:
    autowire: true
    tags:
      - { name: event_subscriber }

  Drupal\opdavies_blog\EventSubscriber\SendTweet:
    autowire: true
    tags:
      - { name: event_subscriber }

Adding autowire: true is not required for the event subscriber to work. I’m using it to automatically inject any dependencies into the class rather than specifying them separately as arguments.

src/EventSubscriber/SendTweet.php:

namespace Drupal\opdavies_blog\EventSubscriber;

use Drupal\hook_event_dispatcher\Event\Entity\EntityUpdateEvent;
use Drupal\hook_event_dispatcher\HookEventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class SendTweet implements EventSubscriberInterface {

  ...

  public static function getSubscribedEvents() {
    return [
      HookEventDispatcherInterface::ENTITY_UPDATE => 'sendTweet',
    ];
  }

  public function sendTweet(EntityUpdateEvent $event) {
    // Perform checks and send the tweet.
  }

}

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web