Skip to main content

Blog

Cross-posted from opensource.com.

Install and configure the tools

Since it is good practice to use Composer to manage a Drupal site's dependencies, use it to install the tools for BDD tests: Behat, Mink, and the Behat Drupal Extension. The Behat Drupal Extension lists Behat and Mink among its dependencies, so you can get all of the tools by installing the Behat Drupal Extension package:

composer require drupal/drupal-extension --dev

Mink allows you to write tests in a human-readable format. For example:

Given I am registered user,
When I visit the homepage,
Then I should see a personalized news feed

Because these tests are supposed to emulate user interaction, you can assume they will be executed within a web browser. That is where Mink comes into play. There are various browser emulators, such as Goutte and Selenium, and they all behave differently and have very different APIs. Mink allows you to write a test once and execute it in different browser emulators. In layman's terms, Mink allows you to control a browser programmatically to emulate a user's action.

Now that you have the tools installed, you should have a behat command available. If you run it:

./vendor/bin/behat

you should get an error, like:

FeatureContext context class not found and can not be used

Start by initializing Behat:

./vendor/bin/behat --init

This will create two folders and one file, which we will revisit later; for now, running behat without the extra parameters should not yield an error. Instead, you should see an output similar to this:

No scenarios
No steps
0m0.00s (7.70Mb)

Now you are ready to write your first test, for example, to verify that website visitors can leave a message using the site-wide contact form.

Writing test scenarios

By default, Behat will look for files in the features folder that's created when the project is initialized. The file inside that folder should have the .feature extension. Let's tests the site-wide contact form. Create a file contact-form.feature in the features folder with the following content:

Feature: Contact form
  In order to send a message to the site administrators
  As a visitor
  I should be able to use the site-wide contact form

  Scenario: A visitor can use the site-wide contact form
    Given I am at "contact/feedback"
    When I fill in "name" with "John Doe"
    And I fill in "mail" with "john@doe.com"
    And I fill in "subject" with "Hello world"
    And I fill in "message" with "Lorem Ipsum"
    And I press "Send message"
    Then I should see the text "Your message has been sent."

Behat tests are written in Gherkin, a human-readable format that follows the Context–Action–Outcome pattern. It consists of several special keywords that, when parsed, will execute commands to emulate a user's interaction with the website.

The sentences that start with the keywords Given, When, and Then indicate the Context, Action, and Outcome, respectively. They are called Steps and they should be written from the perspective of the user performing the action. Behat will read them and execute the corresponding Step Definitions. (More on this later.)

This example instructs the browser to visit a page under the "contact/feedback" link, fill in some field values, press a button, and check whether a message is present on the page to verify that the action worked. Run the test; your output should look similar to this:

1 scenario (1 undefined)
7 steps (7 undefined)
0m0.01s (8.01Mb)

 >> default suite has undefined steps. Please choose the context to generate snippets:

  [0] None
  [1] FeatureContext
 >

Type 0 at the prompt to select the None option. This verifies that Behat found the test and tried to execute it, but it is complaining about undefined steps. These are the Step Definitions, PHP code that will execute the tasks required to fulfill the step. You can check which steps definitions are available by running:

./vendor/bin/behat -dl

Currently there are no step definitions, so you shouldn't see any output. You could write your own, but for now, you can use some provided by the Mink extension and the Behat Drupal Extension. Create a behat.yml file at the same level as the Features folder—not inside it—with the following contents:

default:
  suites:
    default:
      contexts:
       - FeatureContext
        - Drupal\DrupalExtension\Context\DrupalContext
        - Drupal\DrupalExtension\Context\MinkContext
        - Drupal\DrupalExtension\Context\MessageContext
        - Drupal\DrupalExtension\Context\DrushContext
  extensions:
    Behat\MinkExtension:
      goutte: ~

Steps definitions are provided through Contexts. When you initialized Behat, it created a FeatureContext without any step definitions. In the example above, we are updating the configuration file to include this empty context along with others provided by the Drupal Behat Extension. Running ./vendor/bin/behat -dl again produces a list of 120+ steps you can use; here is a trimmed version of the output:

default | Given I am an anonymous user
default | When I visit :path
default | When I click :link
default | Then I (should )see the text :text

Now you can perform lots of actions. Run the tests again with ./vendor/bin/behat. The test should fail with an error similar to:

  Scenario: A visitor can use the site-wide contact form        # features/contact-form.feature:8
        And I am at "contact/feedback"                          # Drupal\DrupalExtension\Context\MinkContext::assertAtPath()
        When I fill in "name" with "John Doe"                   # Drupal\DrupalExtension\Context\MinkContext::fillField()
        And I fill in "mail" with "john@doe.com"                # Drupal\DrupalExtension\Context\MinkContext::fillField()
        And I fill in "subject" with "Hello world"              # Drupal\DrupalExtension\Context\MinkContext::fillField()
        Form field with id|name|label|value|placeholder "subject" not found. (Behat\Mink\Exception\ElementNotFoundException)

        And I fill in "message" with "Lorem Ipsum"              # Drupal\DrupalExtension\Context\MinkContext::fillField()

        And I press "Send message"                              # Drupal\DrupalExtension\Context\MinkContext::pressButton()

        Then I should see the text "Your message has been sent." # Drupal\DrupalExtension\Context\MinkContext::assertTextVisible()

 --- Failed scenarios:

        features/contact-form.feature:8 1 scenario (1 failed) 7 steps (3 passed, 1 failed, 3 skipped) 0m0.10s (12.84Mb)

The output shows that the first three steps—visiting the contact page and filling in the name and subject fields—worked. But the test fails when the user tries to enter the subject, then it skips the rest of the steps. These steps require you to use the name attribute of the HTML tag that renders the form field.

When I created the test, I purposely used the proper values for the name and address fields so they would pass.  When in doubt, use your browser's developer tools to inspect the source code and find the proper values you should use. By doing this, I found I should use subject[0][value] for the subject and message[0][value] for the message. When I update my test to use those values and run it again, it should pass with flying colors and produce an output similar to:

 

1 scenario (1 passed)
7 steps (7 passed)
0m0.29s (12.88Mb)

Success! The test passes! In case you are wondering, I'm using the Goutte browser. It is a command line browser, and the driver to use it with Behat is installed as a dependency of the Behat Drupal Extension package.

Other things to note

As mentioned above, BDD tests should be written from the perspective of the user performing the action. Users don't think in terms of HTML name attributes. That is why writing tests using subject[0][value] and message[0][value] is both cryptic and not very user friendly. You can improve this by creating custom steps at features/bootstrap/FeatureContext.php, which was generated when Behat initialized.

Also, if you run the test several times, you will find that it starts failing. This is because Drupal, by default, imposes a limit of five submissions per hour. Each time you run the test, it's like a real user is performing the action. Once the limit is reached, you'll get an error on the Drupal interface. The test fails because the expected success message is missing.

This illustrates the importance of debugging your tests. There are some steps that can help with this, like Then print last drush output and Then I break. Better yet is using a real debugger, like Xdebug. You can also install other packages that provide more step definitions specifically for debugging purposes, like Behatch and Nuvole's extension,. For example, you can configure Behat to take a screenshot of the state of the browser when a test fails (if this capability is provided by the driver you're using).

Regarding drivers and browser emulators, Goutte doesn't support JavaScript. If a feature depends on JavaScript, you can test it by using the Selenium2Driver in combination with Geckodriver and Firefox. Every driver and browser has different features and capabilities. For example, the Goutte driver provides access to the response's HTTP status code, but the Selenium2Driver doesn't. (You can read more about drivers in Mink and Behat.) For Behat to pickup a javascript enabled driver/browser you need to annotate the scenario using the @javascript tag. Example:

Feature:
 (feature description)

  @javascript
  Scenario: An editor can select the author of a node from an autocomplete field
    (list of steps)

Another tag that is useful for Drupal sites is @api. This instructs the Behat Drupal Extension to use a driver that can perform operations specific to Drupal; for example, creating users and nodes for your tests. Although you could follow the registration process to create a user and assign roles, it is easier to simply use a step like Given I am logged in as a user with the "Authenticated user" role. For this to work, you need to specify whether you want to use the Drupal or Drush driver. Make sure to update your behat.yml file accordingly. For example, to use the Drupal driver:

default:
  extensions:
    Drupal\DrupalExtension:
      blackbox: ~
      api_driver: drupal
      drupal:
        drupal_root: ./relative/path/to/drupal

I hope this introduction to BDD testing in Drupal serves you well. If you have questions, feel free to add a comment below, send me an email at mauricio@agaric.com (or through the Agaric contact form) or a tweet at @dinarcon.

 

Image credit: "Anti-Muslim Ban Protests" by Mrs. Gemstone is licensed under CC BY-SA 2.0.

Stay in touch

Find It Cambridge is an online resource that empowers families, youth, and those who support them to easily find activities, services, and resources in Cambridge, Massachusetts. It serves as a one-stop-shop website for those who live and work in Cambridge.

Agaric led development and functionality-focused consulting on this project from its inception in 2016.  In 2020, we upgraded the site and made the program locator and event finder platform that powers it available to all communities.

The Challenge

Building an event calendar and program directory central to people’s lives is challenging. City governments are notorious for silos and redundancy. The City of Cambridge was determined to do differently.

This started with thorough user research led by the city. Over 250 interviews and 1,250 surveys were completed by Cambridge residents and representatives from the city, schools, and community-based organizations. Taking the time to survey and interview everyday residents ensured we could confidently build a truly helpful site.

From that research we learned that the site needed:

  • Powerful, but intuitive search for Cambridge residents
  • Friendly authoring experience for service providers
  • Phone number with a human on the other end

The Approach

To make the research findings a reality we combined forces with Terravoz, a digital research and development agency, and Todd Linkner, a designer and front-end developer who defined Find It Cambridge’s brand identity and developed an accompanying style guide.

A Search for Locals

There are hundreds of events, programs and organizations in Cambridge. To find exactly what one is looking for a sophisticated filtering system is a must. We chose Apache Solr, leader of the pack when it comes to advanced filtering.

One particularly interesting facet came out of Cambridge’s unique geography. Despite spanning a relatively small area, Cambridge’s neighborhood boundaries are infamously creative. Even longtime residents don’t necessarily know where one neighborhood ends and another starts. So, while filtering by neighborhood is helpful, we decided a visual aid was in order.

Todd Linkner created a custom SVG image file representing Cambridge’s neighborhoods. We then took that SVG file and wrote a custom module that associates each neighborhood map section to a Drupal vocabulary term. The result is a clickable map filter aiding site visitors in quickly finding programs and activities in their area.

Screenshot showing how a clickable map filters results. A clickable map allows residents to filter by neighborhood.

Supporting Service Providers

For a knowledge hub like Find It Cambridge to thrive, it needed buy in from service providers. Getting their input during the research phase set that relationship off on the right foot. The resounding feedback was that the site needed to be easy for them to use.

This proved to be a challenge because while ease of use was critical, it was also essential that events and programs have rich metadata. The more data we ask of users, the more complex interfaces become.

To address this we leveraged Drupal’s customizable dashboard and the Field Groups module.

Customized Dashboard

By default, the first page a user sees when logging into a Drupal site is an underwhelming user profile page.

We customized a dashboard with the key actions providers take on the site: creating new content, updating past content and answering questions about the site.

Screenshot of customized content manager dashboard. A customized dashboard provides quick access to the most common tasks and information for service providers.

While there is a Drupal Dashboard module, we opted to build this ourselves for maximum flexibility and control. Doing so allowed us to break information out into several working tabs. A custom administrative page for internal documentation pages and other Find It Cambridge information turns control over the “Have Questions?” section of the dashboard over to site administrators, rather than being hardcoded.

Delete Without Worries

With dozens of service providers managing content on the site mistakes are bound to happen. The worst scenario is accidentally deleting a node. In Drupal when a node is deleted it is gone forever. To protect against these we used the Killfile module to “soft delete” nodes, allowing for their recovery if needed.

Helping Managers Help Providers

Another key piece to getting relevant, timely information added to the site is helping the Find It Cambridge team remind and support service providers to use the site and update their information. To that end we put together a statistics page listing organizations in alphabetical order, along with the number of programs and events they have. This allows the team to quickly spot duplicate entries and other incorrect data.

We also implemented a notification system. Any time a service provider adds or updates content the Find It team receives an email. This helps managers stay on top of the ever changing content of the site.

Results

Since Find It Cambridge launched, 333 organizations have created accounts and contributed to the directory. Residents now have a single site they can refer to stay connected with events and access programs. The effort has also fostered increased collaboration across city departments and services.

Connecting community is an ongoing process and we continue to improve the site to better connect residents.

In 2020, we completely overhauled the site and built the program locator and event finder that powers FindItCambridge as software and as a platform available to all cities, towns, and regions to adopt.

Drutopia Features

Actions

An action is a call for concrete work from supporters. It can stand on its own or be part of a campaign. It can have a goal and due date set. For example, a call for 50 people to call a prison by June 13th demanding an investigation into prisoner abuse. An action can also be turned into a fundraiser, allowing people to donate directly to the site through Stripe. No longer will groups be dependent on proprietary platforms such as GoFundMe and IndieGoGo.

Articles

Drutopia provides an article content type and related configuration, including article types (such as "Victories" or "Press releases") and topics which span all kinds of content on the site (such as "Tenant organizing" or "Environmental racism").

Blog Posts

Drutopia provides the blog content type and related configuration, ideal for giving individuals or groups more informal voices distinct from the organization as a whole.

Campaigns

A campaign page is a central place to explain an issue, publish updates about its activity, list out demands, post calls to action, and raise funds.

Groups

  • Groups broadcast to supporters their activity and ways to support.
  • Each group has a page within the website. A group can launch a campaign, call for an action and publish a news article.
  • All of these pieces of content are displayed on a group's page, creating a mini-site.

Profile Pages

Drutopia provides a People content type for showing visitors information about staff, volunteers, contributors, and more.

The Drutopia Initiative

The Drutopia initiative lays out a technique for configuring a Drupal distribution, for the commons.  It is a recognition and a reflection in software architecture that liberation requires organizing and collective action. We ask you not to sit back and let Drutopia work its magic, but rather to stand up and, with us, build a cooperative of grassroots organizations that work together to build tools that we can own together.

Core values of the Drutopia initiative:

  • Be inclusive regarding gender, gender identity, sexual orientation, ethnicity, ability, age, religion, geography, and class.
  • Commit to protection of personal information and privacy and freedom from surveillance
  • Value collaboration and cooperation above competition
  • Prioritize human needs over private profit
  • Foster non-hierarchical structures and collective decision-making

Drutopia encourages:

  • Users and adopters to pool their resources and take a lead role in prioritizing and crowdfunding new development
  • Designers, site builders, and developers to focus on rolling their work into shared solutions available to everyone, heightening the ability of groups of all stripes and sizes to benefit.

Strategy - Shared Configuration Management

Drutopia focuses on a specific use case for websites — grass-roots organizing — and pioneers an elegant model for reducing the costs of maintaining an individual Drupal site by providing a shared base configuration for a collection of websites that are run by various organizations with similar needs that contribute to the development cooperatively.

Learn more specifics about the cooperative development strategy behind Drutopia by reading the Drutopia White Paper.

Drutopia as a LibreSaaS or Platform Cooperative

We believe that the platforms that influence and affect so much of our lives (think Facebook and Google) should be owned by the people who use them. In the same line of thinking, the potential of the Drutopia configuration framework is truly unleashed as fully hosted, member-owned platforms. Members of the platform cooperatives will drive forward the vision of the project from the perspective of those most affected– shaping the development of new features. The platforms themselves can span multiple participating hosts that endorse and follow the hosted Drutopia standards.

Contribute to the Drutopia Initiative

Drutopia is a fully functional content management system apart from all these goals, and there are a few ways different individuals and organizations can contribute to this initiative:

1) Drutopia as a Drupal distribution that can be self-hosted by anyone, but the software project will be democratically governed by members of the Drutopia cooperative. For as little as $10 a year you can become a member who votes in leadership team elections, suggests features for the development roadmap, and is part of a community that is building tools for the grassroots.

2) Non-profits and other organizations that pay Chocolate Lilly or Agaric to build a Drutopia website for their organizing will be contributing to our development efforts, and helping us to move forward in attaining the goals of the Drutopia initiative.

3) Individuals and groups that recognize needs for specialized Drupal distributions should talk to us. We encourage you to reach out and bring together others that share the same needs, so that we can collectivize the development and maintenance of more Drutopia distributions. 

Cooperative Webhosting

Staying true to the core values of Drutopia, we hosting your site through, and include membership in, democratically governed May First Movement Technology cooperative.  May First is dedicated to supporting organizers and activists by providing tools and services that protect their data and privacy from governments and corporations.

It is common for a Drupal site to list multiple items. It could be several authors of a single article, the days a recreation center is open, or the flavors an ice cream parlor serves. Clean, structured data is a strong point of Drupal, but the display of that structured content is limited out of the box. That is why DevCollaborative partnered with the Agaric Tech Collective to complete a stable release of In Other Words, a Drupal module that gives site builders the power to configure precise and natural ways to display lists of items.

Hear from us

Somos Agaric, una cooperativa de trabajadores. Construimos sitios web y herramientas en línea que respetan tu libertad. Brindamos capacitación y consulta para alcanzar sus metas.

Descubra cómo Agaric puede ayudarlo a ganar poder sobre su tecnología en línea.

Keegan is a Web Developer, focused mostly on building sites using Drupal and/or front-end JavaScript frameworks (like React.js), and is also a Drupal core contributor.

Keegan's longest-lived hobbies are being in nature, sonic-doodling, and studying the history and philosophy of science and technology. Having a degree in Environmental Science and Technology, Keegan learned early that there is no deficiency in the capabilities of modern technology to create a more equitable and sustainable global infrastructure, but a lack of funding. Hence, Keegan believes that the future of societal health depends on collective efforts to provision and employ information and communications infrastructure that is owned and controlled by the people who use it—not by the full-fledged military contractors that big tech comprises—so that love, democracy, community, and eco-friendly infrastructure can thrive.

Keegan is inspired, as a worker-owner of a tech coop, to have recognized the practical benefits of Libre software and of the cooperative model toward the respective protection and cultivation of attentional liberation, and is always beyond delighted to hear whenever someone else has made the same or similar discoveries. Most of all, Keegan is humbled to be immersed in a positive work-culture that emphasizes action-oriented ethical practices, and to be surrounded day-to-day by brilliant friends and mentors.

A large lower case letter i surrounded by tiny letters, that would spell out "capitalism" if they weren't crushed and falling over from the first i.

In the previous entry, we wrote our first Drupal migration. In that example, we copied verbatim values from the source to the destination. More often than not, the data needs to be transformed in some way or another to match the format expected by the destination or to meet business requirements. Today we will learn more about process plugins and how they work as part of the Drupal migration pipeline.

Syntax for process plugin definition and chaining

Syntactic sugar

The Migrate API offers a lot of syntactic sugar to make it easier to write migration definition files. Field mappings in the process section are an example of this. Each of them requires a process plugin to be defined. If none is manually set, then the get plugin is assumed. The following two code snippets are equivalent in functionality.

process:
  title: creative_title
process:
  title:
    plugin: get
    source: creative_title

The get process plugin simply copies a value from the source to the destination without making any changes. Because this is a common operation, get is considered the default. There are many process plugins provided by Drupal core and contributed modules. Their configuration can be generalized as follows:

process:
  destination_field:
    plugin: plugin_name
    config_1: value_1
    config_2: value_2
    config_3: value_3

The process plugin is configured within an extra level of indentation under the destination field. The plugin key is required and determines which plugin to use. Then, a list of configuration options follows. Refer to the documentation of each plugin to know what options are available. Some configuration options will be required while others will be optional. For example, the concat plugin requires a source, but the delimiter is optional. An example of its use appears later in this entry.

Providing default values

Sometimes, the destination requires a property or field to be set, but that information is not present in the source. Imagine you are migrating nodes. As we have mentioned, it is recommended to write one migration file per content type. If you know in advance that for a particular migration you will always create nodes of type Basic page, then it would be redundant to have a column in the source with the same value for every row. The data might not be needed. Or it might not exist. In any case, the default_value plugin can be used to provide a value when the data is not available in the source.

source: ...
process:
  type:
    plugin: default_value
    default_value: page
destination:
  plugin: 'entity:node'

The above example sets the type property for all nodes in this migration to page, which is the machine name of the Basic page content type. Do not confuse the name of the plugin with the name of its configuration property as they happen to be the same: default_value. Also note that because a (content) type is manually set in the process section, the default_bundle key in the destination section is no longer required. You can see the latter being used in the example of writing your Drupal migration blog post.

Concatenating values

Consider the following migration request: you have a source listing people with first and last name in separate columns. Both are capitalized. The two values need to be put together (concatenated) and used as the title of nodes of type Basic page. The character casing needs to be changed so that only the first letter of each word is capitalized. If there is a need to display them in all caps, CSS can be used for presentation. For example: FELIX DELATTRE would be transformed to Felix Delattre.

Tip: Question business requirements when they might produce undesired results. For instance, if you were to implement this feature as requested DAMIEN MCKENNA would be transformed to Damien Mckenna. That is not the correct capitalization for the last name McKenna. If automatic transformation is not possible or feasible for all variations of the source data, take notes and perform manual updates after the initial migration. Evaluate as many use cases as possible and bring them to the client’s attention.

To implement this feature, let’s create a new module ud_migrations_process_intro, create a migrations folder, and write a migration definition file called udm_process_intro.yml inside it. Follow the instructions in this entry to find the proper location and folder structure or download the sample module from https://github.com/dinarcon/ud_migrations It is the one named UD Process Plugins Introduction and machine name udm_process_intro. For this example, we assume a Drupal installation using the standard installation profile which comes with the Basic Page content type. Let’s see how to handle the concatenation of first an last name.

id: udm_process_intro
label: 'UD Process Plugins Introduction'
source:
  plugin: embedded_data
  data_rows:
    -
      unique_id: 1
      first_name: 'FELIX'
      last_name: 'DELATTRE'
    -
      unique_id: 2
      first_name: 'BENJAMIN'
      last_name: 'MELANÇON'
    -
      unique_id: 3
      first_name: 'STEFAN'
      last_name: 'FREUDENBERG'
  ids:
    unique_id:
      type: integer
process:
  type:
    plugin: default_value
    default_value: page
  title:
    plugin: concat
    source:
      - first_name
      - last_name
    delimiter: ' '
destination:
  plugin: 'entity:node'

The concat plugin can be used to glue together an arbitrary number of strings. Its source property contains an array of all the values that you want put together. The delimiter is an optional parameter that defines a string to add between the elements as they are concatenated. If not set, there will be no separation between the elements in the concatenated result. This plugin has an important limitation. You cannot use strings literals as part of what you want to concatenate. For example, joining the string Hello with the value of the first_name column. All the values to concatenate need to be columns in the source or fields already available in the process pipeline. We will talk about the latter in a future blog post.

To execute the above migration, you need to enable the ud_migrations_process_intro module. Assuming you have Migrate Run installed, open a terminal, switch directories to your Drupal docroot, and execute the following command: drush migrate:import udm_process_intro Refer to this entry if the migration fails. If it works, you will see three basic pages whose title contains the names of some of my Drupal mentors. #DrupalThanks

Chaining process plugins

Good progress so far, but the feature has not been fully implemented. You still need to change the capitalization so that only the first letter of each word in the resulting title is uppercase. Thankfully, the Migrate API allows chaining of process plugins. This works similarly to unix pipelines in that the output of one process plugin becomes the input of the next one in the chain. When the last plugin in the chain completes its transformation, the return value is assigned to the destination field. Let’s see this in action:

id: udm_process_intro
label: 'UD Process Plugins Introduction'
source: ...
process:
  type: ...
  title:
    -
      plugin: concat
      source:
        - first_name
        - last_name
      delimiter: ' '
    -
      plugin: callback
      callable: mb_strtolower
    -
      plugin: callback
      callable: ucwords
destination: ...

The callback process plugin pass a value to a PHP function and returns its result. The function to call is specified in the callable configuration option. Note that this plugin expects a source option containing a column from the source or value of the process pipeline. That value is sent as the first argument to the function. Because we are using the callback plugin as part of a chain, the source is assumed to be the last output of the previous plugin. Hence, there is no need to define a source. So, we concatenate the columns, make them all lowercase, and then capitalize each word.

Relying on direct PHP function calls should be a last resort. Better alternatives include writing your own process plugins which encapsulates your business logic separate of the migration definition. The callback plugin comes with its own limitation. For example, you cannot pass extra parameters to the callable function. It will receive the specified value as its first argument and nothing else. In the above example, we could combine the calls to mb_strtolower() and ucwords() into a single call to mb_convert_case($source, MB_CASE_TITLE) if passing extra parameters were allowed.

Tip: You should have a good understanding of your source and destination formats. In this example, one of the values to want to transform is MELANÇON. Because of the cedilla (ç) using strtolower() is not adequate in this case since it would leave that character uppercase (melanÇon). Multibyte string functions (mb_*) are required for proper transformation. ucwords() is not one of them and would present similar issues if the first letter of the words are special characters. Attention should be given to the character encoding of the tables in your destination database.

Technical note: mb_strtolower is a function provided by the mbstring PHP extension. It does not come enabled by default or you might not have it installed altogether. In those cases, the function would not be available when Drupal tries to call it. The following error is produced when trying to call a function that is not available: The "callable" must be a valid function or method. For Drupal and this particular function that error would never be triggered, even if the extension is missing. That is because Drupal core depends on some Symfony packages which in turn depend on the symfony/polyfill-mbstring package. The latter provides a polyfill) for mb_* functions that has been leveraged since version 8.6.x of Drupal.

What did you learn in today’s blog post? Did you know that syntactic sugar allows you to write shorter plugin definitions? Were you aware of process plugin chaining to perform multiple transformations over the same data? Had you considered character encoding on the source and destination when planning your migrations? Are you making your best effort to avoid the callback process plugin? Please share your answers in the comments. Also, I would be grateful if you shared this blog post with your colleagues.

Next: Migrating data into Drupal subfields

This blog post series, cross-posted at UnderstandDrupal.com as well as here on Agaric.coop, is made possible thanks to these generous sponsors. Contact Understand Drupal if your organization would like to support this documentation project, whether is the migration series or other topics.

Learning Objectives

  • Understand the different approaches to upgrading your site to Drupal 11 using the Migrate API.
  • Revise site architecture and map configuration from the previous site to the new one
  • Use the Migrate Drupal UI module to understand module requirements for running upgrades.
  • Use the Migrate Upgrade module to generate migration files.
  • Cherry-pick content migrations for getting a content type migrated to Drupal 11.
  • Modify a migration to convert a content type to a user entity.
  • Modify a migration to convert a content type to a paragraph entities.
  • Migrate images to media entities.
  • Learn about writing a custom process plugin for providing a migrate path for modules that do not include one already.
  • Tips and recommendations upgrade projects.

Prerequisites

This is an advanced course that requires familiarity with the Drupal migration concepts. Our Drupal 11 content migrations training will give you all the background knowledge that you need. Alternatively, you can read the 31 days of migrations series in our blog or watch this video for an overview of the Migrate API.

Setup instructions

Having a Drupal 7 and Drupal 11 local installation is required to take this course. We offer this DDEV-based repository configured with the two Drupal installations used in the training. Alternatively, you can use a tool like Lando or Docksal. You will have to be able to restore a MySQL database dump containing the Drupal 7 database. Drupal 11 site needs to be able to connect to the Drupal 7 database. Drush needs to be installed in order to run migrations from the command line.

This training will be provided over Zoom. You can ask questions via text chat or audio. Sharing your screen, but you might want to do it to get assistance on a specific issue. Sharing your camera is optional.

What to expect

Rocket launch

Prior to the training

Attendees will receive detailed instructions on how to setup their development environment. In addition, they will be able to join a support video call days before the training event to make the the local development environment is ready. This prevents losing time fixing problems with environment set up during the training.

On the days of the training

  • The training totals 7 hours of instruction, which we usually split into 2 sessions.
  • A team of developers available to answer questions and help with training-related issues.

After the training

  • Attendees will receive copies of the training recordings.
  • Attendees will receive a free copy of the 31 days of migrations book.
  • Attendees will receive a certificate of completion.

Micky Metts speaking at event

Micky speaks at events and hosts workshops online and around the world on topics including Free Software, Personal Digital Security, Drupal and Platform Cooperatives. She is also a host of a monthly cyber security webinar and a weekly series, Show and Tell with Agaric and friends.

We are a worker-owned cooperative specializing in building tools and websites that respect your freedom. We also provide training and consultation to meet your goals. Everything we do is part of our purpose to help all people gain the most power possible over their own lives.

Sign up to be notified when Agaric gives a migration training: