How to track and remove deprecated Drupal APIs from your codebase

Drupal 9 is scheduled to be released on June 3, 2020. What this means to you has been extensively covered in the Driesnote at DrupalCon Amsterdam 2019. Now, how do you go about actually getting the job done and removing deprecated APIs from contributed modules you’re using? There are several tips I’d like to share here. Nothing breathtaking, but rather a systematic approach to ensuring you’ll stay on top of things so your apps are ready on the day of the Drupal 9 release.

Before we get started, here are a few resources you might find useful:

Planning ahead

The challenge in front of you is not technical as most API changes involve only a few lines of code. Rather, your mission is to start planning ahead now. As of the writing of this blog post, you have about 7 months to be ready. This might seem like a lot, but there are several factors to take into account:

  • Consider the time it’s going to take for maintainers to look at a given patch, provide feedback, get the patch committed and a new stable release tagged. Multiply this by N patches in M contributed modules.
  • Consider how many cycles your development team will be able to allocate in upcoming sprints, when conflicting forces are at play and your immediate need is to successfully ship new projects for your customers or colleagues.

To get organized, I’d suggest you leverage your issue tracker of choice and write down bite-sized chunks of work you can easily slide into sprints. Here’s an example :

Sampl Jira EPIC to track deprecated API-related issues

Here, I’m using a Jira EPIC called ‘Prep for Drupal 9’ which is meant to track one issue per custom or contributed module I know is using deprecated APIs. Your strategy might be different whether you maintain only one app or many and you want to abstract out popular contrib modules in their own EPIC because they have the potential to impact most/all apps you’re responsible for. The reason why I insist on breaking things down into dedicated issues is not all contrib modules are created equal and will be ready at the same time. As we split up the work into chunks, we can then make decisions based on information at hand around the day of the Drupal 9 release. Those decisions can be formulated like so:

  • Is the module I need ready for Drupal 9?
  • Does the module I need have a stable release with deprecated APIs removed?
  • Can I afford to upgrade to Drupal 9 later, when this module is ready?
  • Can I afford to stop using this module temporarily/permanently?
  • Which strategy should I employ so I’m not limited by external circumstances? (more on that below)

There are times when you are blocked from removing APIs that are dependent on a minor version EOL or an upcoming minor release. This is the case of Facets and Google Analytics in the above example and it needs you to carefully think about the implications. As an example, if you maintain a contrib module and you remove an API that was deprecated in 8.8.x but your user base might still be running on a previous Drupal core minor version that is still being maintained by the Drupal Security Team, then you risk breaking their apps. As a responsible module maintainer, you should do the right thing, but only at the right time.

How to interact with the Drupal community

It’s easy to think about what you have to do, but it’s not so easy to determine how you’re going to do it. Let’s step back to understand what your next step should be when interacting with the community. Here’s a flowchart I came up with. I’ll explain the top-right composer.json patching circle in a moment.

Example Drupal contribution workflow to remove deprecated APIs

You can see above I’m not using the Upgrade Status module but a CLI alternative called Drupal Check. It’s a customized runner for PHPStan which allows you to conveniently check Drupal deprecation rules. It’s also the perfect companion for your CI/CD integration. Check it out.

Tracking progress

A typical way to track progress for individual issues is to mimic how you’d work in the public Drupal issue queue. Post comments to keep the team in the loop about your work, and often update the issue summary for those who can’t afford to read many comments. Do note that in the screenshot below there’s a caption indicating when the check was last performed, which is always convenient to know about.

Sample Jira ticket to track Drupal Check status

You should always keep in mind it's critical to track your work on drupal.org as this makes it possible for others to get involved. That being said, for your own tracker, another useful thing to do is to link to drupal.org issues so you always know what remains to be done for a module to be ready for Drupal 9 without leaving your workspace.

Drupal issues are being rendered as on the drupal.org website thanks to the Drupal Issue Chrome extension

Did you notice that the linked Jira issues are rendered like issues on drupal.org? This is thanks to the Drupal Issue Chrome extension which greatly improves the UX on non-drupal.org pages and doesn’t force you into clicking on the links to see if there was any status change to any of the linked issues.

Finally, here’s a typical comments section. Remember you’re not posting comments for yourself but for others (or your future self) to see the detailed history, should you have to come back to this at some point in the future. It’s like writing a good Git commit message, right?

Sample Jira comments to track progress on Drupal issues tracking deprecated APIs removal

It’s time to switch to a Composer-managed install

If you’re still not managing your site via Composer, do it now. I can’t stress this enough. There are multiple ways to help you switch to a Composer-managed install (composerize-drupal, Composerize, drupal composerize’s Drupal Console command...) and there’s even a core issue that is discussing ways to help you safely do it with a community-approved solution. Note that at this point the community seems to be leaning towards leveraging composerize-drupal.

If you’re still not convinced, here’s one more reason: when it comes to removing deprecated APIs, sometimes things will be outside your control and it’s not a great feeling to be blocked by others. Remember the above flowchart and the top-right composer.json patching circle? This is my opinionated way to think about putting you back in control: while you might file an issue, post or review a patch, beg the maintainer to commit it and hope for a new release to be tagged ASAP, sometimes expectations won’t be met and you’ll need Composer to help you out.

Composer is a fantastic asset. Out of the many great things it does, it can be extended to safely patch your application without hacking core. And we’re going to leverage this opportunity with deprecated APIs. Here’s an example where we’re updating composer.json to include several patches from drupal.org:

"patches": {
    "drupal/slack_receive": {
        "3053951 - Anchor tags stripped from markdown response in slack": "https://www.drupal.org/files/issues/2019-05-14/slack_receive-markdown-title-3053951-4.patch"
    },
    "drupal/extlink": {
        "3042607 - JavascriptTestBase is deprecated in favor of WebDriverTestBase": "https://www.drupal.org/files/issues/2019-07-24/extlink-fix-deprecation-3042607-4-d8.patch",
        "3029176 - Replace usages of the deprecated drupal_set_message() function": "https://www.drupal.org/files/issues/2019-01-29/extlink-Replace_usages_of_the_deprecated_drupal_set_message_function-3029176-2-D8.patch"
    },
    "drupal/autosave_form": {
        "3086690: entity_get_form_display() is deprecated and should be removed": "https://www.drupal.org/files/issues/2019-10-09/deprecated-3086690-2.patch",
        "3067893: Remove deprecated JavascriptTestBase in favor of WebDriverTestBase": "https://www.drupal.org/files/issues/2019-08-09/3067893-4.patch"
    },
    "drupal/google_analytics": {
        "3034176: Use mb_* functions instead of Unicode::* methods": "https://www.drupal.org/files/issues/2019-02-27/3034176-5.patch"
    },
    "drupal/crop": {
        "3042587: Remove deprecated function calls and raise the minimum version of core to 8.7": "https://www.drupal.org/files/issues/2019-10-17/crop_api-3042587-15.patch",
        "3042587: Follow-up to #3042587": "https://www.drupal.org/files/issues/2019-10-18/missing-file_unmanaged_copy-removal.patch"
    },
    "drupal/contact_storage": {
        "3081195: Remove calls to entity_get_display() and entity_get_form_display()": "https://www.drupal.org/files/issues/2019-10-12/2-contact-storage-drupal9-compatibility.patch"
    },
    "drupal/consumers": {
        "3042825: Drupal 9 Deprecated Code Report": "https://www.drupal.org/files/issues/2019-05-20/deprecated_code-3042825-2.patch"
    },
    "drupal/memcache": {
        "3042707: Drupal 9 Deprecated Code Report": "https://www.drupal.org/files/issues/2019-04-02/drupal_9_deprecated_code_report-3042707-2.patch"
    },
    "drupal/image_widget_crop": {
        "3042648: Drupal 9 Deprecated Code Report": "https://www.drupal.org/files/issues/2019-03-26/image_widget_crop-fixed_drupal_set_message-3042648-2.patch"
    }
},

In conclusion, you should be in a good position to track your work, contribute patches to drupal.org and leverage composer.json to apply patches today. For your convenience we’ve built a deprecation status tool in the Acquia Developer Center (Gábor even published a video about it). We hope you like it!