Dec 20 2016
Dec 20

Over the past couple of years I have become more active in issues on drupal.org, in which time I have honed a selection of commands that I regularly use in the creation and application of patches. Here is a run-down of my most useful commands.

This examples in this article will focus on patches for Drupal core, but most of it (if not all) is equally relevant for contrib modules too.

I strongly recommend you read and understand the Git Instructions on drupal.org which covers many of the basics (e.g. how to set up the repo). This article with overlap on certain points, but will include a few alternatives too.

I am not claiming this is necessarily the best solution, rather the best I have come across so far - I am always learning.

Obtain latest code

If you are intending to create a patch for submission to drupal.org then you should make sure you have the latest version of code and are working on the correct branch:


git pull --rebase git checkout 8.3.x

Most of my current patches are for the 8.3.x branch, but replace this with the name of which ever branch you are working on.

Create a feature branch

I usually have multiple issues open at the same time ("Needs review", RTBC, etc.) and, because it is rare for me to fix an issue with a single commit, I create a feature branch for each issue:


git checkout -b feature/2669978-migrate_d7_menu_links

These feature branches will only ever be local so you can use any naming convention you like, but I prefer feature/[issue_id]-[description].

Feature branches can also make it easier to create interdiffs (see below) so I normally make a commit to a feature branch for every patch I submit to drupal.org.

Apply existing patch

There are times when you may want to make changes/improvements to an existing patch, in which case you will need to apply that patch to your code base. Drupal.org recommends the use of


git apply -v migrate_d7_menu_links-2669978-43.patch

but that requires you to download the patch to your working directory (and remember to delete it later) so I prefer the following:


curl 'https://www.drupal.org/files/issues/migrate_d7_menu_links-2669978-43.patch' | git apply -v

which works in exactly the same manner, but uses the online version of the patch.

Patch failed to apply

Sometimes git apply will not work, often when a re-roll is required. In this situation I use:


curl 'https://www.drupal.org/files/issues/migrate_d7_menu_links-2669978-43.patch' | patch -p1

which will apply as much of the patch as possible and create .rej files for those hunks that failed to apply making it easier to fix.

Create a patch

Once you have made the relevant code changes to fix your current issue (and fully tested it, of course!) it is time to create the patch. I usually use the simplest option, as recommended by drupal.org:


git diff > [description]-[issue_id]-[comment_number].patch

but here are a couple of alternatives worth remembering. If you do not want to include every change you have made to your codebase then just stage the changes you require and run:


git diff --cached > [description]-[issue_id]-[comment_number].patch

Or if this is not the first commit since branching from core (e.g. a subsequent patch), often on a feature branch, then use:


git diff [old_sha] [new_sha] > [description]-[issue_id]-[comment_number].patch

This command can also be used if you have already committed your changes.

Create an interdiff

To quote from drupal.org's Creating an interdiff:

An interdiff is a text file in patch format that describes the changes between two versions of a patch.

If you have changed an existing patch then it is recommended that you create an interdiff to make life easier for code reviewers.

Normally I use:


git diff [previous_patch_sha] > interdiff-[issue_id]-[old_comment_number]-[new_comment_number].txt

This explains why I prefer to make a commit to my feature branch for every patch (see above).

If this is the only change since the previous patch then you can instead use:


git diff > interdiff-[issue_id]-[old_comment_number]-[new_comment_number].txt

There are some cases, most notably when re-rolling a patch that has failed to apply, when the previous options are not possible. In this case I recommend using the interdiff command that is included with patchutils:


interdiff old.patch new.patch > interdiff-[issue_id]-[old_comment_number]-[new_comment_number].txt

If you have any better/alternative commands that you prefer to use then tell me about them in the comments below.

Oct 11 2016
Oct 11

In Drupal 8 admin paths are now defined as part of route definitions so are no longer defined in hook_admin_paths().

In many cases, uniting routing with admin path definition makes things easier - simply add _admin_route: TRUE in a .routing.yml file, but not when it comes to Views. This is because Views routes are generated dynamically, leading to the question: How can you set _admin_route: TRUE when there is no .routing.yml file?

The solution I have come up with is to use a RouteSubscriber (after inspiration from Drupal\node\EventSubscriber\NodeAdminRouteSubscriber). In alterRoutes() is a loop through all the routes setting the _admin_route option to TRUE for a pre-defined array of routes.

The simplest way to create a RouteSubscriber is to use Drupal Console: drupal generate:routesubscriber. If you don't want to use Drupal Console then here is the code you need:

Register your RouteSubscriber in /modules/custom/my_module/my_module.services.yml as follows:

my_module.route_subscriber:
  class: Drupal\my_module\Routing\RouteSubscriber
  tags:
    - { name: event_subscriber }

Create your RouteSubscriber in /modules/custom/my_module/src/Routing/RouteSubscriber.php as follows:

<?php

namespace Drupal\my_module\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Class RouteSubscriber.
 *
 * @package Drupal\my_module\Routing
 * Listens to the dynamic route events.
 */
class RouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    $admin_routes = ['view.my_example_view.page_1'];
    foreach ($collection->all() as $name => $route) {
      if (in_array($name, $admin_routes)) {
        $route->setOption('_admin_route', TRUE);
      }
    }
  }
}

I am not claiming this is the only solution, just the best I have come up with so far. If you have any better suggestions then please comment below.

Oct 04 2016
Oct 04

This article will explain how to formulate the route name for a view because there are very few sources for the information online.

The short version

"view.$view_id.$display_id"

The long(er) version

There are many reasons why you might require the route name for a view, but currently it is not very well documented. Fortunately, during my research I came across the drupal.org issue Document views routes on which this article is based. I owe many thanks to the contributors of that issue, particularly andypost who provided details of where the route name is generated (see the For Reference section below).

As explained in the short version (above), the route name for any view is "view.$view_id.$display_id" - the string "view" followed by the View ID followed by the Display ID, each separated by a full stop ".".

The View ID is the machine name of the view, likely to be a machine readable version of the name you gave the view. E.g. If you originally gave your view the name "My example view" the the View ID will (probably) be "my_example_view". You can also find the View ID by going to the edit page for your view - the View ID will be at the end of the URL,
e.g. example.com/admin/structure/views/view/my_example_view.

The Display ID determines which variation of the view we require (multiple pages etc. can be created from the same view). Very often the Display ID will be "page_1" because this is the default value given to the first page created for a view. In the UI you can check in a number of ways, including:

  1. At the top of the view edit page, click on the relevant display name - the Display ID will be at the end of the URL,
    e.g. example.com/admin/structure/views/view/my_example_view/edit/page_1.
  2. On the view edit page, click to reveal the ADVANCED section and, in the OTHER section, you will find the Machine Name - this is the Display ID you require.

For Reference

For those who are interested, the route name for each view is defined in \Drupal\views\Plugin\views\display\PathPluginBase::collectRoutes:

public function collectRoutes(RouteCollection $collection) {
  $view_id = $this->view->storage->id();
  $display_id = $this->display['id'];

  $route = $this->getRoute($view_id, $display_id);

  if (!($route_name = $this->getOption('route_name'))) {
    $route_name = "view.$view_id.$display_id";
  }
  $collection->add($route_name, $route);
}

Aug 16 2016
Aug 16

If you are trying to get to grips with Dependency Injection in Drupal 8 then here is a walk-through of how I applied it in one of my Drupal 8 test projects.

I have a project I have been using to investigate Drupal 8 since alpha10 which has been invaluable in my learning process. But as a result, some of my code is over 2 years old and written when I barely had a grasp of Drupal 8 concepts.

In the past week a very old @todo jumped out to me:

// @todo Find a way to get a service within a controller.

I realised that the solution to the problem is dependency injection. These are the steps I took in making the change to my controller.

Starting Point

Don't judge me, but here is how I had been accessing services within my controller:

$foo_bar = \Drupal::service('foo.bar');
$data = $foo_bar->get_data();

From a procedural point of view this was the sensible way to do it, but this was before I grasped dependency injection. Rather than accessing the static service it should be a part of the controller and accessed like this:

$data = $this->fooBar->getData();

Addtional Functions

The service is injected into the controller within the constructor. Until now I had no need of a constructor so added the following:

public function __construct(FooBar $foo_bar) {
  $this->fooBar = $foo_bar;
}

This needs to be called (with the correct parameter(s)) from the create() function, so I also added:

public static function create(ContainerInterface $container) {
  return new static(
    $container->get('foo.bar')
  );
}

First Gotcha

Don't forget to add the required use statements for any classes you reference, e.g.:

use Drupal\MyCustomModule\FooBar;
use Symfony\Component\DependencyInjection\ContainerInterface;

I highly recommend using PhpStorm as your IDE; it can add a use statement simply by pressing Alt + Enter.

Second Gotcha

Now I must admit, this one caught me out (and not just on my first attempt); for the controller to be called correctly it must either:

  • implement ContainerInjectionInterface
  • extend ControllerBase (because that class implements ContainerInjectionInterface)

e.g.

class MyCustomController implements ContainerInjectionInterface {

There are pros and cons to both approaches; extending the ControllerBase provides access to some handy functions including currentUser(), config() and state() amongst others. The downside is that these additions add complexity that make it harder to unit test. The ControllerBase page is a good point of reference when making this decision.

That's all folks!

And there you have it; I can now call the FooBar service as a property of MyCustomController rather than statically.

Aug 09 2016
Aug 09

For me this is the biggest unanswered question hanging over my development of Drupal 8 websites:

How should I add config to a Drupal 8 site?

Before I go any further I should warn you that despite providing plenty of options, this article will not provide a definitive answer. After much investigation, thought and discussion I suspect different solutions will apply in different situations - I wish there was a silver bullet; a solution that is best for every situation, but (as yet) there isn't.

The problem

Imagine you are developing an existing Drupal 8 installation and you want to add, for example, a pre-existing view saved as a config file. How should you add that to your site? What about if you had 2 config files, or 10, or even 100 - how would you do it then?

The solutions(?)

The rest of this article will highlight some possible methods by which you could import config file(s).

/config/install directory

Config files can be added to a site as part of enabling a module by placing them in the /config/install directory of the module. This functionality is described in more depth in the drupal.org article Include default configuration in your Drupal 8 module.

If the migration of config is a one-time-only affair then this is a great option and the workflow is equally simple for any number of config files. BUT the development of a site is more often an iterative process - what happens when you need to add just one more config file? Simply adding this additional file to the /config/install directory of a currently enabled module will have no effect. In theory you could uninstall the module and then re-enable it, but this is not always an option if the module is a dependency of other modules. Besides this workflow is becoming cumbersome and is clearly not the intended functionality of the /config/install directory.

Configuration Synchronization

The apparent obvious solution to adding config would be the Configuration Synchronization user interface (Admin menu: Configuration > Development > Configuration Synchronization) and in some cases it is. The interface provides two options: import a single item of config or a full archive (for more details read Managing configuration in Drupal 8 on drupal.org).

Single item

The user interface for importing a single item of config is straightforward and ideal for that particular use case, but not if the number of config items increases. This workflow would not be suitable if there were, for example, ten or more items of config that required importing.

Full archive

Unfortunately, the alternative to "Single Item" is not "Multiple Items" it is "Full Archive" instead. In this case the workflow is as easy for one item of config as it would be for 100, but the steps can be complicated because this is not the prime intention of the functionality.

To add config via the Full Archive configuration synchronization the steps are as follows:

  1. Export the complete current configuration (/admin/config/development/configuration/full/export)

  2. Add your additional config file(s) - there are two options for doing this:

    a. Continue in the UI by:

    • Unzipping the config export
    • Adding the additional config file(s)
    • Zipping the new complete config
    • Uploading the new complete config zip (/admin/config/development/configuration/full/import)

    b. Or Use the file system instead of the UI by:

    • Locating the (not zipped) export of the complete config in the sync directory (by default /sites/default/files/config/sync)
    • Adding the additional config file(s)
    • Running the synchronization (/admin/config/development/configuration)

Update hook

In some cases the simplest method would be to manually convert the config file(s) into code that can be run in an update hook. This workflow would be more prone to errors due to transcribing the config, but would be ideal for simple config such as a Role, e.g.

$role = Role::create([
  'id' => 1,
  'label' => 'role_example',
  'weight' => 0,
  'is_admin' => FALSE,
  'permissions' => [],
]);

Contrib modules

There are already a number of contrib modules that aim to help with configuration management. A couple worth investigating are:

There are plenty of other options too and I would recommend a thorough search of drupal.org to find the best for your requirements.

Core issues

At least a couple of issues have been raised against core regarding the management of config:

Both of these (and possible others too) are worth keeping track of.

Custom code

Another option is to write your own solution code. If you select this option, let me know how it goes!

The debate continues

That is the best I have come up with so far, but this is by no means an exhaustive list of solutions - if you have a suggestion or, even better, a single, general solution then please comment below.

My quest for a Grand Unifying Theory continues...

Aug 02 2016
Aug 02

Class Aliasing is the simple, but very useful solution to the problem of needing two classes (from different namespaces) with the same name.

The problem

I recently found myself in a similar situation and my thanks go to Ben Doherty (benjy) for introducing me to the solution.

While creating the Drupal 7 version of the Link field MigrateCckField plugin, I realised that a lot of code could be reused from the existing Drupal 6 version. Both the existing Drupal 6 class and the new Drupal 7 class extending it would need to be called LinkField, but how could this be achieved? Obviously this would not work:

use Drupal\link\Plugin\migrate\cckfield\d6\Linkfield;

class LinkField extends LinkField {
}

The solution

To make this snippet work, the existing LinkField class needs to be known by a different name - it requires an alias. The convention demands that the alias be formed by prefixing the class name (LinkField) with the next higher portion of the namespace (d6). In this case the alias becomes D6LinkField (after the necessary capitalisation).

Thus the correct form of the previous snippet is:

use Drupal\link\Plugin\migrate\cckfield\d6\Linkfield as D6LinkField;

class LinkField extends D6LinkField {
}

Very simple, but ever so useful.

Further reading

For additional clarification, I recommend you review the straightforward example of Class Aliasing in the Namespace article on drupal.org.

Just for fun

drupal.org clearly states:

"Aliasing should only be done to avoid name collisions."

But that didn't stop us having some fun in the ComputerMinds office, how about these possible uses of Class Aliasing:

use Drupal as YourMum;

or

use SomeLongDrupallyClass as Bob;

or

use Drupal as ☃;

Jun 21 2016
Jun 21

This article will talk you through the steps to follow to write a simple PHPUnit test for Drupal 8.

I have been doing a lot of work on Drupal 8 migrations for the past few months so that will be the focus of the test.

Step 1: Create a Fixture

To quote the PHPUnit manual:

One of the most time-consuming parts of writing tests is writing the code to set the world up in a known state and then return it to its original state when the test is complete. This known state is called the fixture of the test.

In the case of testing a Drupal migration, the fixture is a database agnostic[1] representation of the data source to be migrated. According to the convention set out in core, the file should be created in the following location: modules/[module_name]/tests/fixtures and the file name should reference the version of Drupal core to which it applies, e.g. drupal7.php. It mainly consists of createTable() and insert() commands that form the dataset.

The db-tools.php command line application can be used to automate the creation of a fixture file. This was used in core to create the fixture files for the migrate_drupal module. To find out more, I recommend reading the drupal.org article entitled Generating database fixtures for D8 Migrate tests.

Alternatively you can write the fixture file manually - if doing so, core/modules/migrate_drupal/tests/fixtures/drupal7.php is a good point of reference.

Here is an (admittedly convoluted) example of a fixture file:

<?php
/**
 * @file
 * A database agnostic dump for testing purposes.
 */

use Drupal\Core\Database\Database;

$connection = Database::getConnection();

$connection->schema()->createTable('example', array(
  'fields' => array(
    'id' => array(
      'type' => 'serial',
      'not null' => TRUE,
    ),
    'name' => array(
      'type' => 'varchar',
      'not null' => TRUE,
      'length' => '255',
      'default' => '',
    ),
    'weight' => array(
      'type' => 'int',
      'not null' => TRUE,
      'default' => '0',
    ),
  ),
  'primary key' => array(
    'id',
  ),
  'mysql_character_set' => 'utf8',
));

$connection->insert('example')
->fields(array(
  'id',
  'name',
  'weight',
))
->values(array(
  'id' => '1',
  'name' => 'General discussion',
  'weight' => '2',
))
->values(array(
  'id' => '2',
  'name' => 'Term1',
  'weight' => '0',
))
->execute();

The snippet above creates one table (example) containing 3 fields (id, name, weight) and then inserts two records into it. Depending on the complexity of the test, a fixture file could include any number of tables each containing enough rows to cater for each facet that needs to be tested.

Step 2: Create the test file

This is the file that will contain the method to set up and run the test(s).

The file structure of test files is important, our example would be created in modules/[module_name]/tests/src/Kernel/Migrate/d7. It is located in the Kernel directory because it is a functional test - this is explained in more details in the drupal.org Automated tests article. It is then organised into the Migrate directory (obviously because it is a migration test). For more details on the location of test files read the drupal.org article on PHPUnit file structure, namespace, and required metadata.

The file name (and contained class) should end in "Test" and for migration tests should begin with "Migrate" (according to Drupal core convention). In our example the file will be called MigrateExampleTest.php and will initially contain the following:

<?php

namespace Drupal\Tests\example\Kernel\Migrate\d7;

use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;

/**
 * Tests 'example' migration.
 *
 * @group example
 */
class MigrateExampleTest extends MigrateDrupalTestBase {

  static $modules = ['example'];

  /**
   * [email protected]}
   */
  protected function setUp() {
    parent::setUp();
  }

  /**
   * Test 'example' migration from Drupal 7 to 8.
   */
  public function testExample() {

  }
}

The class name should match the file name, in this case MigrateExampleTest. A straightforward test class should extend MigrateDrupalTestBase, but a more complicated test class could extend MigrateDrupal7TestBase which provides access to the migrate_drupal fixture including a well-populated database.

The test class must specify a @group that matches the originating module short name. $modules is an array of modules that need to be enabled for this test (including this migration module). The setUp() and testExample() methods will be covered in detail in the following sections.

Step 3: Write a setUp() method

The setUp() method contains the code required to prepare and run the test(s). Here is the straightforward class for our example migration:

protected function setUp() {
  parent::setUp();
  $this->loadFixture( __DIR__ . '/../../../../tests/fixtures/drupal7.php');

  $this->executeMigrations(['example']);
}

You are likely to want to call the setUp() method of the parent class very early, if not the first line of the method. This will run various vital bootstrapping commands, such as setting up the database connection. You should then load the fixture (as created in Step 1).

You may also need to call any/all of the following methods, but they will not be covered within this basic tutorial:

  • $this->installEntitySchema()
  • $this->installSchema()
  • $this->installConfig()

Finally call $this->executeMigrations() with an array of migration IDs. Our version only executes the migration with ID example, but this array can contain any number of migration IDs.

Step 4: Write the test method(s)

Our simple class only contains a single test method, testExample(), but there is no limit on the number of test methods. Test methods must be public and named test*. They contain a number of assertion methods, e.g. $this->assertSame(), to assert that an actual value matches an expected value, see Appendix A of the PHPUnit manual for more details. Here is our test method:

/**
 * Test 'example' migration from Drupal 7 to 8.
 */
public function testExample() {
  $example = Example::load(1);
  $this->assertTrue($example instanceof Example);
  $this->assertEquals('General discussion', $example->name);

  $example2 = Example::load(2);
  $this->assertTrue($example2 instanceof Example);
  $this->assertEquals(0, $example->weight);
}

Step 5: Test your tests

And with that you have written a PHPUnit test for Drupal! But that is not all there is to it. Don't just leave it there, don't just blindly upload this to drupal.org as part of a module, run the tests locally first. I found the articles on drupal.org useful, if you are new to running PHPUnit tests then I highly recommend you start there.

Glossary

[1] Database agnostic - able to work with various systems, rather than being customised for a single system.

Jun 21 2016
Jun 21

This article will talk you through the steps to follow to write a simple PHPUnit functional (Kernel) test for Drupal 8.

I have been doing a lot of work on Drupal 8 migrations for the past few months so that will be the focus of the test.

Step 1: Create a Fixture

To quote the PHPUnit manual:

One of the most time-consuming parts of writing tests is writing the code to set the world up in a known state and then return it to its original state when the test is complete. This known state is called the fixture of the test.

In the case of testing a Drupal migration, the fixture is a database agnostic[1] representation of the data source to be migrated. According to the convention set out in core, the file should be created in the following location: modules/[module_name]/tests/fixtures and the file name should reference the version of Drupal core to which it applies, e.g. drupal7.php. It mainly consists of createTable() and insert() commands that form the dataset.

The db-tools.php command line application can be used to automate the creation of a fixture file. This was used in core to create the fixture files for the migrate_drupal module. To find out more, I recommend reading the drupal.org article entitled Generating database fixtures for D8 Migrate tests.

Alternatively you can write the fixture file manually - if doing so, core/modules/migrate_drupal/tests/fixtures/drupal7.php is a good point of reference.

Here is an (admittedly convoluted) example of a fixture file:

<?php
/**
 * @file
 * A database agnostic dump for testing purposes.
 */

use Drupal\Core\Database\Database;

$connection = Database::getConnection();

$connection->schema()->createTable('example', array(
  'fields' => array(
    'id' => array(
      'type' => 'serial',
      'not null' => TRUE,
    ),
    'name' => array(
      'type' => 'varchar',
      'not null' => TRUE,
      'length' => '255',
      'default' => '',
    ),
    'weight' => array(
      'type' => 'int',
      'not null' => TRUE,
      'default' => '0',
    ),
  ),
  'primary key' => array(
    'id',
  ),
  'mysql_character_set' => 'utf8',
));

$connection->insert('example')
->fields(array(
  'id',
  'name',
  'weight',
))
->values(array(
  'id' => '1',
  'name' => 'General discussion',
  'weight' => '2',
))
->values(array(
  'id' => '2',
  'name' => 'Term1',
  'weight' => '0',
))
->execute();

The snippet above creates one table (example) containing 3 fields (id, name, weight) and then inserts two records into it. Depending on the complexity of the test, a fixture file could include any number of tables each containing enough rows to cater for each facet that needs to be tested.

Step 2: Create the test file

This is the file that will contain the method to set up and run the test(s).

The file structure of test files is important, our example would be created in modules/[module_name]/tests/src/Kernel/Migrate/d7. It is located in the Kernel directory because it is a functional test - this is explained in more details in the drupal.org Automated tests article. It is then organised into the Migrate directory (obviously because it is a migration test). For more details on the location of test files read the drupal.org article on PHPUnit file structure, namespace, and required metadata.

The file name (and contained class) should end in "Test" and for migration tests should begin with "Migrate" (according to Drupal core convention). In our example the file will be called MigrateExampleTest.php and will initially contain the following:

<?php

namespace Drupal\Tests\example\Kernel\Migrate\d7;

use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;

/**
 * Tests 'example' migration.
 *
 * @group example
 */
class MigrateExampleTest extends MigrateDrupalTestBase {

  static $modules = ['example'];

  /**
   * [email protected]}
   */
  protected function setUp() {
    parent::setUp();
  }

  /**
   * Test 'example' migration from Drupal 7 to 8.
   */
  public function testExample() {

  }
}

The class name should match the file name, in this case MigrateExampleTest. A straightforward test class should extend MigrateDrupalTestBase, but a more complicated test class could extend MigrateDrupal7TestBase which provides access to the migrate_drupal fixture including a well-populated database.

The test class must specify a @group that matches the originating module short name. $modules is an array of modules that need to be enabled for this test (including this migration module). The setUp() and testExample() methods will be covered in detail in the following sections.

Step 3: Write a setUp() method

The setUp() method contains the code required to prepare and run the test(s). Here is the straightforward class for our example migration:

protected function setUp() {
  parent::setUp();
  $this->loadFixture( __DIR__ . '/../../../../tests/fixtures/drupal7.php');

  $this->executeMigrations(['example']);
}

You are likely to want to call the setUp() method of the parent class very early, if not the first line of the method. This will run various vital bootstrapping commands, such as setting up the database connection. You should then load the fixture (as created in Step 1).

You may also need to call any/all of the following methods, but they will not be covered within this basic tutorial:

  • $this->installEntitySchema()
  • $this->installSchema()
  • $this->installConfig()

Finally call $this->executeMigrations() with an array of migration IDs. Our version only executes the migration with ID example, but this array can contain any number of migration IDs.

Step 4: Write the test method(s)

Our simple class only contains a single test method, testExample(), but there is no limit on the number of test methods. Test methods must be public and named test*. They contain a number of assertion methods, e.g. $this->assertSame(), to assert that an actual value matches an expected value, see Appendix A of the PHPUnit manual for more details. Here is our test method:

/**
 * Test 'example' migration from Drupal 7 to 8.
 */
public function testExample() {
  $example = Example::load(1);
  $this->assertTrue($example instanceof Example);
  $this->assertEquals('General discussion', $example->name);

  $example2 = Example::load(2);
  $this->assertTrue($example2 instanceof Example);
  $this->assertEquals(0, $example->weight);
}

Step 5: Test your tests

And with that you have written a PHPUnit test for Drupal! But that is not all there is to it. Don't just leave it there, don't just blindly upload this to drupal.org as part of a module, run the tests locally first. I found the articles on drupal.org useful, if you are new to running PHPUnit tests then I highly recommend you start there.

Glossary

[1] Database agnostic - able to work with various systems, rather than being customised for a single system.

Jun 14 2016
Jun 14

In Drupal 7 if you wanted to tweak the functionality of an existing function then an alter hook was your best friend, but in Drupal 8 it's "all change!"

With the introduction of Symfony, your new BFF is an Event Subscriber. Alter hooks still exist, but it is recommended that we all move towards Events to fall in line with Symfony.

If you are interested in the comparison between alter hooks and events then I recommend this article from PreviousNext.

Introduction

In just 4 simple steps, this article will talk you through the process of writing a custom Event Subscriber linked to an existing event (N.B. writing an Event Dispatcher will not be covered here).

A lot of my Drupal 8 work so far has revolved around migrations so my code snippets will focus on subscribing to migration events.

Step 1: Create Subscriber file

Following the precedent set by core, create a meaningfully titled .php file in modules/custom/my_module/src/EventSubscriber. In my case the file is MigrationSubscriber.php.

In this new file create a class (with the same name as the file) which implements EventSubscriberInterface, e.g.:

<?php

/**
 * @file
 * Contains \Drupal\my_module\EventSubscriber\MigrationSubscriber.
 */

namespace Drupal\my_module\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Code to run in conjunction with migrations.
 */
class MigrationSubscriber implements EventSubscriberInterface {

  /**
   * [email protected]}
   */
  public static function getSubscribedEvents() {

  }

}

Remember to include the @file doxygen, namespace and a short, but descriptive comment before the class.

The getSubscribedEvents() is a requirement of EventSubscriberInterface.

Step 2: List subscribed events

The getSubscribedEvents() method needs to return an array of method names keyed by the event names, e.g.

public static function getSubscribedEvents() {
  $events[MigrateEvents::PRE_IMPORT] = 'onMigrationPreImport';
  $events[MigrateEvents::POST_IMPORT] = 'onMigrationPostImport';
  return $events;
}

In this example when the MigrateEvents::PRE_IMPORT (which is the string constant 'migrate.pre_import') event is dispatched the onMigrationPreImport()will run, etc.

If you are using the MigrateEvents constants (PRE_IMPORT, POST_IMPORT, etc.) then you will need to add the necessary use statement at the top of the file, outside the class:

use Drupal\migrate\Event\MigrateEvents;

Further details about alternative return values of the getSubscribedEvents() method can be found in the doxygen of the EventSubscriberInterface.

Step 3: Add a function for each subscribed event

Of course, now we have assigned methods to the event names we must write those methods, e.g.:

/**
 * Code to run before a migration has been imported.
 */
public function onMigrationPreImport(MigrateImportEvent $event) {

}

/**
 * Code to run after a migration has been imported.
 */
public function onMigrationPostImport(MigrateImportEvent $event) {

}

To find the variable type of the parameter(s) of these methods look for the code that dispatches the event, e.g. the following code dispatches the PRE_IMPORT event:

$this->getEventDispatcher()->dispatch(MigrateEvents::PRE_IMPORT, new MigrateImportEvent($this->migration));

Step 4: Register your new service

Your new service will need to be registered with the module. This is done by adding an entry to the module's .services.yml file which can be found at the root of the module. If you are creating a custom module then it is quite likely that this file does not yet exist, if not create my_module.services.yml (where my_module should be replaced with the name of your module). Your new entry should look something like this:

services:
  my_module.migration_subscriber:
    class: Drupal\my_module\EventSubscriber\MigrationSubscriber
    tags:
      - { name: event_subscriber }

Make sure your service has a meaningful (and unique) id, in our case it is migration_subscriber. Note that the service is tagged as an event_subscriber.

And that is all there is to it! As ever, remember to clear caches having written the code. Next time the appropriate event is dispatched your custom method will be run.

Feb 11 2014
Feb 11

This is part 3 in my series of articles about creating a custom field. I recommend reading Part 1: Field type and Part 2: Field widget first, if you have not done so already.

After creating the field type and field widget it is now time to complete the set by creating the field formatter.

a) Create the file

The field type must be located as follows:
<module_name>/lib/Drupal/<module_name>/Plugin/field/formatter/<field_formatter_name>.php
N.B. The field formatter name should be in CamelCase.

b) Add Contains, namespace and use

In the newly created field type file add a brief comment to explain what it consists of:

/**
* @file
* Contains \Drupal\<module_name>\Plugin\field\formatter\<field_formatter_name>.
*/

N.B. The "Contains..." line should match the location and name of this file.

Then add the namespace as follows:

namespace Drupal\<module_name>\Plugin\field\formatter;

N.B. Again I must emphasise that it is vital for the namespace to match the location of the file otherwise it will not work.

Then add the following uses:

use Drupal\field\Plugin\Type\Formatter\FormatterBase;

This provides the class that the field widget will extend.

use Drupal\Core\Entity\Field\FieldItemListInterface;

This provides a variable type required within the field formatter class.

c) Add formatter details annotation

The annotation should appear as follows:

/**
* Plugin implementation of the '<field_formatter_id>' formatter.
*
* @FieldFormatter(
* id = "<field_formatter_id>",
* label = @Translation("<field_formatter_label>"),
* field_types = {
* "<field_type_id>"
* }
* )
*/

N.B. All text represented by a <placeholder> should be appropriately replaced according to requirements. The field_type_id must match the id of a field type and the field_formatter_id should match the default formatter specified in the field type (see Part 1 of this article).

d) Add field formatter class

Create the field formatter class as follows:

class <field_formatter_name> extends FormatterBase {

}

N.B. The <field_formatter_name> must match the name of this file (case-sensitive).

The field formatter class needs to contain the viewElements() function that defines how the field will be output:

  /**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items) {
$elements = array();

foreach ($items as $delta => $item) {
$elements[$delta] = array(
'#theme' => 'person_default',
'#forename' => check_plain($item->forename),
'#surname' => check_plain($item->surname),
'#age' => check_plain($item->age),
);
}
return $elements;
}

When writing a viewElements() function for a field with multiple columns I recommend using a custom theme (e.g. person_default) to avoid including markup within the code.

Here is a simple example, similar to that discussed above.

Once you have created a field type, a field widget and field formatter you now have a custom field type!

After you have saved the files (and cleared caches, of course!) you will find:

  • the field type in the Field Type drop-down when adding a new field, under the Manage Fields tab.
  • the field widget in the Widget drop-down under the Manage Form Display tab.
  • the field formatter Format drop-down under the Manage Display tab.
Feb 04 2014
Feb 04

This is part 2 in my series of articles about creating a custom field. I recommend reading Part 1: Field type first, if you have not done so already.

After creating the field type it is now time to create the field widget.

a) Create the file

The field widget must be located as follows:
<module_name>/lib/Drupal/<module_name>/Plugin/field/widget/<field_widget_name>.php
N.B. The field widget name should be in CamelCase.

b) Add Contains, namespace and use

In the newly created field type file add a brief comment to explain what it consists of:

/**
* @file
* Contains \Drupal\<module_name>\Plugin\field\widget\<field_widget_name>.
*/

N.B. The "Contains..." line should match the location and name of this file.

Then add the namespace as follows:

namespace Drupal\<module_name>\Plugin\field\widget;

N.B. I cannot emphasise enough: it is vital that the namespace matches the location of the file otherwise it will not work.

Then add the following uses:

use Drupal\Core\Entity\Field\FieldItemListInterface;

This provides a variable type required within the field widget class.

use Drupal\field\Plugin\Type\Widget\WidgetBase;

This provides the class that the field widget will extend.

c) Add widget details annotation

The annotation should appear as follows:

/**
* Plugin implementation of the '<field_widget_id>' widget.
*
* @FieldWidget(
* id = "<field_widget_id>",
* label = @Translation("<field_widget_label>"),
* field_types = {
* "<field_type_id>"
* }
* )
*/

N.B. All text represented by a <placeholder> should be appropriately replaced according to requirements. The field_type_id must match the id of a field type and the field_widget_id should match the default widget specified in the field type (see Part 1 of this article).

d) Add field widget class

Create the field widget class as follows:

class <field_widget_name> extends WidgetBase {

}

N.B. The <field_widget_name> must match the name of this file (case-sensitive).

The field widget class needs to contain the formElement() function that defines how the field will appear on data input forms:

 /**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) {

$element['forename'] = array(
'#title' => t('Forename'),
'#type' => 'textfield',
'#default_value' => isset($items[$delta]->forename) ? $items[$delta]->forename : NULL,
);
$element['surname'] = array(
'#title' => t('Surname'),
'#type' => 'textfield',
'#default_value' => isset($items[$delta]->surname) ? $items[$delta]->surname : NULL,
);
$element['age'] = array(
'#title' => t('Age'),
'#type' => 'number',
'#default_value' => isset($items[$delta]->age) ? $items[$delta]->age : NULL,
);
return $element;
}

The above example includes element types of textfield and number, other element types include:

  • radios
  • checkboxes
  • email
  • url

I intend to delve into other element types in a future article.

And there we have it: a complete (basic) field widget. Here is a simple example, similar to that described above.

Click Part 3: Field formatter to continue creating a custom field.

Jan 31 2014
Jan 31

Having written articles on how to create a Drupal 8 field type, field widget and field formatter I thought that now is the time to explain why you might want to create a custom field type. More specifically, why I have created a custom field.

To provide some context: my pet Drupal 8 project is to produce a companion site for players of the Fantasy Football (soccer) game Fantasy Premier League (N.B. this is not my site :) ). For the uninitiated, Fantasy Football, as described by Wikipedia, is "a game in which participants assemble an imaginary team of real life footballers and score points based on those players' actual statistical performance or their perceived contribution on the field of play". My site is called FPL Assist and, without going into too much detail, provides advice on the best players to pick. As you will soon see if you click through to the site, FPL Assist is a work in progress (so is not yet up to the usual high standards of the work we produce for clients!).

Having read an article about Pantheon's free Drupal 8 sandboxes I decided this would be the ideal place to host my site.

ComputerMinds aims to grant employees time to help improve skills and learn - some of the development time for this project has been part of this scheme.

I wanted to write weekly articles that would include a table of the expected best players that week. Each row in that table would consist of:

  • player name (e.g. Wayne Rooney)
  • team (e.g. Manchester United, Chelsea, etc.)
  • position (e.g. defender, striker, etc.)
  • predicted points

I decided the best solution would be a custom field consisting of four columns, one for each of the above. Each field would represent a row in the table.

Having followed the steps described in my previous articles I now had a custom field type, field widget and field formatter.

Under Manage Fields I created a field of my custom type:

And checked for the correct auto-population of the default widget (Manage Form Display) and formatter (Manage Display).

The widget looked like this:

With the addition of some basic theming the formatter looked like this:

After testing this initial solution I decided to improve it by changing some of the columns in the widget from basic textfields to more user-friendly types:

  • Name: textfield with autocomplete
  • Team: select (drop-down)
  • Position: select (drop-down)

This resulted in a widget that looks as follows:

If you want to see the widget in action then here are the FPL Assist articles to which I referred.

Jan 28 2014
Jan 28

I have been experimenting with the Alpha release of Drupal 8 and so I'm sharing some of my experiences so that you can avoid the pitfalls I have encountered.

First I would like to give credit to the two articles I used during the exercise:

Hopefully this article will provide a third point-of-view to make your task easier.

a) Create the file

In D8 the location of files is very important. The field type must be located as follows:
<module_name>/lib/Drupal/<module_name>/Plugin/field/field_type/<field_type_name>.php
N.B. The field type name should be in CamelCase.

b) Add Contains, namespace and use

In the newly created field type file add a brief comment to explain what it consists of:

/**
* @file
* Contains \Drupal\<module_name>\Plugin\field\field_type\<field_type_name>.
*/

N.B. The "Contains..." line should match the location and name of this file.

Then add the namespace as follows:

namespace Drupal\<module_name>\Plugin\field\field_type;

N.B. It is vital that the namespace matches the location of the file otherwise it will not work.

Then add the following uses:

use Drupal\field\Plugin\Type\FieldType\ConfigFieldItemBase;

This provides the class that the field item will extend.

use Drupal\field\FieldInterface;

This provides a variable type required within the field item class.

c) Add field details annotation

Annotations are an important part of Drupal 8 and must not be treated as simple comments! :o) The annotation should appear as follows:

/**
* Plugin implementation of the '<field_type_name>' field type.
*
* @FieldType(
*   id = "<field_type_id>",
*   label = @Translation("<field_type_label>"),
*   description = @Translation("<field_type_description>"),
*   default_widget = "<field_type_default_widget>",
*   default_formatter = "<field_type_default_formatter>"
* )
*/

N.B. All text represented by a <placeholder> should be appropriately replaced according to requirements. The default_widget and default_formatter must match the ids of a widget and a formatter.

d) Add field item class

Create the field item class as follows:

class <field_type_name> extends ConfigFieldItemBase {}

N.B. The <field_type_name> must match the name of this file (case-sensitive).

The class should contain the following:

i. schema()

The schema() function defines the sub-field(s) that make up the field item. Here is an example:

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldInterface $field) {
    return array(
      'columns' => array(
        'forename' => array(
          'type' => 'varchar',
          'length' => 256,
          'not null' => TRUE,
        ),
        'surname' => array(
          'type' => 'varchar',
          'length' => 256,
          'not null' => TRUE,
        ),
        'age' => array(
          'type' => 'int',
          'not null' => TRUE,
        ),
      ),
    );
  }

ii. isEmpty()

The isEmpty() function defines what constitutes an empty field item, e.g.

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    $value = $this->get('forename')->getValue();
    return $value === NULL || $value === '';
  }

iii. getPropertyDefinitions()

The getPropertyDefinitions() function defines the data types of the fields, e.g.

  /**
   * {@inheritdoc}
   */
  static $propertyDefinitions;
  /**
   * {@inheritdoc}
   */
  public function getPropertyDefinitions() {
    if (!isset(static::$propertyDefinitions)) {
      static::$propertyDefinitions['forename'] = array(
        'type' => 'string',
        'label' => t('Forename'),
      );
      static::$propertyDefinitions['surname'] = array(
        'type' => 'string',
        'label' => t('Surname'),
      );
      static::$propertyDefinitions['age'] = array(
        'type' => 'integer',
        'label' => t('Age'),
      );
    }
    return static::$propertyDefinitions;
  }

Here is a simple example, similar to that described above.

Jan 28 2014
Jan 28

I have been experimenting with the Alpha release of Drupal 8 and so I'm sharing some of my experiences so that you can avoid the pitfalls I have encountered.

First I would like to give credit to the two articles I used during the exercise:

Hopefully this article will provide a third point-of-view to make your task easier.

a) Create the file

In D8 the location of files is very important. The field type must be located as follows:
<module_name>/lib/Drupal/<module_name>/Plugin/field/field_type/<field_type_name>.php
N.B. The field type name should be in CamelCase.

b) Add Contains, namespace and use

In the newly created field type file add a brief comment to explain what it consists of:

/**
* @file
* Contains \Drupal\<module_name>\Plugin\field\field_type\<field_type_name>.
*/

N.B. The "Contains..." line should match the location and name of this file.

Then add the namespace as follows:

namespace Drupal\<module_name>\Plugin\field\field_type;

N.B. It is vital that the namespace matches the location of the file otherwise it will not work.

Then add the following uses:

use Drupal\field\Plugin\Type\FieldType\ConfigFieldItemBase;

This provides the class that the field item will extend.

use Drupal\field\FieldInterface;

This provides a variable type required within the field item class.

c) Add field details annotation

Annotations are an important part of Drupal 8 and must not be treated as simple comments! :o) The annotation should appear as follows:

/**
* Plugin implementation of the '<field_type_name>' field type.
*
* @FieldType(
*   id = "<field_type_id>",
*   label = @Translation("<field_type_label>"),
*   description = @Translation("<field_type_description>"),
*   default_widget = "<field_type_default_widget>",
*   default_formatter = "<field_type_default_formatter>"
* )
*/

N.B. All text represented by a <placeholder> should be appropriately replaced according to requirements. The default_widget and default_formatter must match the ids of a widget and a formatter (see Part 2 of this article).

d) Add field item class

Create the field item class as follows:

class <field_type_name> extends ConfigFieldItemBase {}

N.B. The <field_type_name> must match the name of this file (case-sensitive).

The class should contain the following:

i. schema()

The schema() function defines the sub-field(s) that make up the field item. Here is an example:

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldInterface $field) {
    return array(
      'columns' => array(
        'forename' => array(
          'type' => 'varchar',
          'length' => 256,
          'not null' => TRUE,
        ),
        'surname' => array(
          'type' => 'varchar',
          'length' => 256,
          'not null' => TRUE,
        ),
        'age' => array(
          'type' => 'int',
          'not null' => TRUE,
        ),
      ),
    );
  }

ii. isEmpty()

The isEmpty() function defines what constitutes an empty field item, e.g.

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    $value = $this->get('forename')->getValue();
    return $value === NULL || $value === '';
  }

iii. getPropertyDefinitions()

The getPropertyDefinitions() function defines the data types of the fields, e.g.

  /**
   * {@inheritdoc}
   */
  static $propertyDefinitions;
  /**
   * {@inheritdoc}
   */
  public function getPropertyDefinitions() {
    if (!isset(static::$propertyDefinitions)) {
      static::$propertyDefinitions['forename'] = array(
        'type' => 'string',
        'label' => t('Forename'),
      );
      static::$propertyDefinitions['surname'] = array(
        'type' => 'string',
        'label' => t('Surname'),
      );
      static::$propertyDefinitions['age'] = array(
        'type' => 'integer',
        'label' => t('Age'),
      );
    }
    return static::$propertyDefinitions;
  }

Here is a simple example, similar to that described above.

Continue to Part 2: Field widget to continue creating a custom field.

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