Skip to main content

Retrofit: Running legacy Drupal 7 code on your Drupal 10 site

Published on

Check out the Retrofit for Drupal project website for more information: https://retrofit-drupal.com/

I have had an idea for a few years. It is the greatest yet worst idea I’ve had for four years. Some 400,000 Drupal 7 sites report to Drupal.org through the Update module. Drupal 7's end-of-life is coming on January 5 2025, and has been a continual topic of discussion. 

There are many reasons: contributed modules not ported to Symfony-based Drupal, data migrations, and business logic in custom code. There are more reasons. But I want to focus on what I believe is one of the biggest blockers for organizations looking to migrate off of Drupal 7 and onto newer versions of Drupal. Development teams are tasked with maintaining systems and implementing new features. It is a constant balance on continually needing to deliver to stakeholders while fixing bugs. 

Upgrading from Drupal 7 is a heavy lift. The way we define things – routes, blocks, user permissions – was changed. Even if the custom code isn't dramatic, the number of lines that need to be touched is. Design paradigms changed. We adopted Symfony, Guzzle, and other packages. While I believe this was for the best, we never provided a backward compatibility layer to bridge Drupal 7 code to Symfony-based Drupal.

One quick aside before I go on.

  • I like to use the word legacy, to refer to Drupal 7 and any deprecated code between major versions to simplify things.
  • I like to use Drupal without a version for the current version of Drupal that is Symfony-based (aka Symfonic Drupal, aka 8/9/10/+)

What if we could run Drupal 7 modules in Drupal 10?

If sites still use Drupal 7 due to the monumental effort to rewrite their legacy code, why aren't we making this easier? There is the Module Upgrader project. It performs in-place code rewrites for known upgrade paths. There is one problem: it is modifying all of the code all at once. That requires heavy auditing of the changed code to ensure it was upgraded correctly. This tool is great for converting smaller code bases or obscure contributed modules that were never ported.

What if, instead, a backward compatibility layer was available? What if you could copy and paste your Drupal 7 modules into Drupal 10? What if your code just worked, or you only need to refactor 30% of it versus 98%?

That seems like a game-changer.

Retrofit for Drupal

I would like to introduce Retrofit. Retrofit provides compatibility layers to run legacy Drupal code. It is a Composer package that needs to be added to your codebase — no extra modules are required.

composer require mglaman/retrofit-drupal

Here is a quick overview of what Retrofit can currently provide:

  • Convert hook_menu into routes, menu links, local tasks, and actions.
  • Convert hook_permissions definitions.
  • Global $user replacement
  • Form building, validation, and submission via drupal_get_form routes.
  • Convert hook_block to block plugin definitions

The alters for the various APIs have not been implemented.

There are a lot of hard problems to figure out:

  • Field values were accessed via arrays in Drupal 7, and the new Field API does not implement \ArrayAccess.
  • Database tables have changed so that queries will break. For instance SELECT nid FROM {node} n WHERE n.type = :type needs to be SELECT nid FROM {node_field_data} n WHERE n.type = :type.
  • Items once defined in code but are now configuration entities – note types, view modes, etc.

Even if those problems cannot be solved, the current coverage and support reduce a lot of boilerplate refactoring that would be required.

How does it work?

The retrofit package registers a service provider so that it may hook into Drupal.

$GLOBALS['conf']['container_service_providers']['retrofit'] = Provider::class;

From there, it integrates into Drupal by registering event subscribers and decorating existing services. It performs a little hack to have Drupal consider its own namespace for plugin discovery:

$namespaces = $container->getParameter('container.namespaces');
$namespaces['Retrofit\Drupal'] = __DIR__;
$container->setParameter('container.namespaces', $namespaces);

This allows plugins like blocks and field formatters to be defined via a deriver from the legacy Drupal 7 hooks. Menu links and permissions decorate and extend the existing service, but I'd also like to move those into a deriver-based approach.

Check out the demo video!