Insight | Oct 21, 2024
Fixing Drupal's 'Legacy' Problem
By Nathaniel Catchpole
There are parts of Drupal that contribute to the feeling that Drupal as a whole is ‘legacy’. This is primarily APIs and features that have become outdated because something better is available, but which have not been fully replaced. Expanding on meetings at Drupal devdays Burgas, Bulgaria and DrupalCon Barcelona 2024, I will show examples of the situation and then suggest ways to make things better.
We constantly renew and update different parts of Drupal core, but it is an uneven process. Some parts are left behind and adding something new doesn’t always compensate for that.
This is due to a recurring pattern:
- Drupal core adds a new API or feature that can replace some other functionality.
- The work to replace the old functionality is never completed. That leaves two, conflicting, inconsistent ways to do the same thing.
This problem extends across the site builder, PHP developer, front-end, and documentation experiences. It affects wide areas of Drupal core as well as contributed projects. But it’s also a symptom of making progress - in most cases, the new thing exists, we just need to get rid of the old thing.
Here are some examples to illustrate the problem, and how it manifests for different audiences.
PHP developers
The Entity module and API
The Entity API was added to core ten years ago and 92,000 modern Drupal sites install the contrib Entity module. Why? Because the work to complete editorial workflow, revision support, bundle fields etc. is not finished in core.
Legacy hooks instead of YAML
We have some very strange info or info-like hooks left in core, despite moving most of them to YAML ten years ago: hook_mail(), hook_views_data(), hook_schema().
Batch API
Batch API was added in 2007 and has barely changed. It is one of the harder APIs to use in core, still entirely procedural, with everything depending on arrays passed by reference with magic keys, as well as special return values. Even though I’ve been using it for fifteen years, I still have to check documentation or copy and paste other examples every time. We added the queue API in the meantime, which is easier to use, but doesn’t cover all the same use-cases as the batch API.
Site builders
Filter tips
WYSIWYG text fields link to a separate page with HTML instructions that looks like it's from a mid-2000s ‘how to write HTML’ guide. We could remove the link completely when a wysiwyg editor is configured.
'Submitted' information for content
Whereas nearly all entity rendering can be controlled via ‘manage display’ or layout builder, the ‘submitted’ variable for nodes is still hard-coded in the node template. You can switch it on and off from the UI, but it has its own section in content type settings away from the rest of the display configuration.
Menu item parents
When you edit a menu item and try to change the parent, you get a select list with every menu item on the entire site, and no autocomplete option.
Entity reference filters in Views
When you add an entity reference filter in views, the widget might be an entity reference autocomplete, or it might be a text box that you have to enter a number into. This is because a couple of entity types individually implement an autocomplete widget, while the rest use ‘numeric’.
File attachment field descriptions
Looking at these descriptions in the field UI, would you be confident about choosing which field to use to allow file attachments?
Themes
Template cruft
Most sites shouldn’t customize node.html.twig - instead using manage display, Layout Builder, custom field formatters, CSS etc. However if you open it up, there is 600 words of documentation, or variables that in most cases should never be used.
Configurable view modes were added in 2009, replacing node module’s hard-coded ‘teaser’ and ‘full’ view modes. Despite this, core still included references to those hard-coded view modes in node templates.
The ‘teaser’ node template variable was deprecated in Drupal 11.1. We’ll finally be able to remove it in Drupal 12, in 2026, seventeen years after it was made redundant. The full page template variable still needs to be deprecated.
hook_theme()
Lots of ‘info hooks’ have moved to YAML, or PHP attribute-based plugins like render elements, but defining templates still happens in hook_theme(), building a big array structure in PHP.
#type vs. #theme vs. SDCs
It’s not always clear when to define (or use) a template vs. a render element. For example when should you use #theme => 'table' vs. #type => 'table'? Now with single directory components we have a new way of providing templates with associated JavaScript and HTML, but these mostly have to be called from within theme templates defined in hook_theme(), there’s not yet a way to use them directly.
CSS component libraries
Asset libraries were added to Drupal 8.0, and as part of this, JavaScript was put into component-specific libraries so that it could be loaded on demand. This work never got done for CSS libraries, so every Drupal site still loads the CSS for tablesorting on every page.
Legacy browser support
Drupal has a browser support policy that allows support for legacy browsers to be dropped on a rolling basis, however actually removing workarounds for older browsers can take many, many years and we don’t have a good process for managing this.
JavaScript
API documentation
Unlike PHP, there is no official JavaScript API documentation generated from code.
backbone.js
backbone.js was added to core in 2012, it was used in the quckedit module and others which have since been deprecated and removed, but is still in contextual module, only one to go now though.
jQuery UI and jQuery
jQuery usage which has become unnecessary with ES6 is gradually being converted over to vanilla JavaScript. jQuery UI was added to core in 2009, and is still used for some key core functionality like autocomplete and dialogs. Native dialog support was added by Chrome in 2014, and became a full standard in 2022, the issue to add support for native dialogs to core has been open for eleven years. We also had to recently fork the unmaintained jquery.form library in order to update to jQuery 4, while the issue to remove the jQuery Form library from core has been open for four years.
Documentation
High-level documentation in PHPDoc
High level developer docs are split between api.drupal.org and sometimes multiple places in the handbook. No consistency exists between subsystems. Often unmaintained. PHPDoc is good for code docs but bad for prose.
Documentation duplication between the API and handbook
If you want an introduction to the cache API, you have a choice between the Drupal.org handbook or api.drupal.org.
Compare to Symfony or Laravel which have a much more consistent structure between components.
Local development environment setup
While progress is ongoing, a recent documentation audit discovered there are about fifteen different handbook pages for setting up a local development environment, some for operating systems that are no longer supported.
Why is it so hard to finish things?
Drupal 5, 6, 7 and 8 all contained significant and increasing amounts of changes, but they also took longer and longer to release each time. Each release cycle would start with an ‘anything goes’ period, but then ‘code freeze’ would start where refactoring was discouraged in favor of fixing release blockers only.
Then, by the time the next major release opened up, these issues had stalled and people’s attention had moved elsewhere. Many of these issues from the initial Drupal 8 development cycle are still pending, nine years after the release of Drupal 8.0.0.
Now, with minor releases every six months, it is possible to make significant changes to core at any time, and core refactoring is never frozen for long periods of time. However, because minor releases come with a backwards compatibility promise, and major releases come with a continuous upgrade path, making individual changes to Drupal core takes more effort and has to be done with more care. Removing old APIs or features means writing backwards compatibility layers, figuring out deprecations, sometimes extracting features into modules, then extracting those modules to contributed projects so they can be gracefully removed from core. What used to be simply deleting or rewriting some code back in 2012 can now entail significant work and understanding of core processes.
On the whole, the release cycle changes have been positive. They improve stability for modules and sites while allowing us to make continuous improvements to core. Still, figuring it out can be time-consuming. Having said this, as time goes on, we’ve gradually learned more and more about how to write good backwards compatibility layers and sunset features. The more we do, the more scaffolding for backwards compatibility layers there is, and the better the documentation gets for how to do it.
How can we fix this?
There are two approaches we need to take. It sounds silly, but we need to stop making things worse, and also make things better.
How to stop making things worse
Fix underlying blockers early in initiatives
Initiatives should aim to fix underlying blockers early on, so they don't end up either blocked or never addressing them or both. Thorny issues can take months or years to resolve, so the quicker work starts on them the better, in order to be able to release stable features faster.
Continue to improve features after their MVP is stable
Core initiatives often lack a clear roadmap for the phases after an initial minimum viable product. This can lead to an attempt to cram ‘should have’ issues into the first phase, and then once that milestone is reached, there isn’t a clear idea of what to continue working on, except for things that didn’t get done. Instead, we could try to divide work up by phases from the outset, but can always bring an issue forward if someone wants to work on it and it’s not blocked.
Deprecate old functionality when adding new features
When we add new features, we should identify things we can remove, and make this removal or deprecation a part of the completion steps for an initiative to be marked finished. Framework managers, subsystem, and topic maintainers can help with this by trying to review new experimental modules early on.
How to make things better
Many of the problems mentioned above, and many more, already have issues open in the Drupal core issue queue. The problem is not that things haven’t been identified, but that the issues are blocked, or don’t have a clear direction.
We should try an approach from the successful Bug Smash Initiative. That initiative has led to fixing some of the oldest bugs and triaging thousands more.
Pick a handful of legacy issues and focus on completing each one. And then rotate a new issue in if one is marked fixed, or if it turns out to be blocked.
I’ve started collecting candidates for issues to work on in this meta issue. If you’d like to help with this, join the #core-development channel on Drupal Slack.
Drop us a line
Have a project in mind?
Contacting Third and Grove may cause awesomeness. Side effects include a website too good to ignore. Proceed at your own risk.