How and why we deprecated code on the way to Drupal 9

Last updated on
18 August 2020

Instead of working on Drupal 9 in its own git branch from the start, we built most of Drupal 9 in Drupal 8. This had various benefits:

  1. All new (Drupal-9-ready) code was deployed on Drupal 8 sites before Drupal 9 was even released.
  2. We worked out the issues in the new code in Drupal 8.
  3. Feedback was provided based on this new code so it can be improved in Drupal 8.
  4. This kept us from refactoring too much like we did with previous Drupal versions, so we will not end up with an entirely reworked API.

The best user benefit is that the path of Drupal 8 to 9 is not a sudden jump. Instead, it is several smaller steps that are much easier to adopt.

The same strategy will be used to build Drupal 10 in Drupal 9.

Why do we deprecate?

There is always something to improve in Drupal. Let's take file.inc functions in Drupal 8 for example. These functions have been around for a long time but are not a good fit in Drupal 8. Most were deprecated in Drupal 8.7.0 and they will be removed in Drupal 9. This is what the before/after pair looks for one of them:

// Before 8.7.0 only this was possible.
file_unmanaged_copy($source, $destination);

// After 8.7.0 this is the new way. The prior code still works but is deprecated.
\Drupal::service('file_system')->copy($source, $destination);

The new solution is better for various reasons, including:

  1. The file system service implements the FileSystemInterface and thus makes it easy to find all the related functionality, no need to hunt around in global function names following a naming convention.
  2. The service can be swapped for a different file system implementation, such as for logging file operations.
  3. The service can be mocked in tests, allowing file system operations to be avoided while still testing the logic.
  4. The code matches Drupal 8's code style elsewhere.
  5. The code does not need to be loaded at all times, can be autoloaded when needed.

The old solution remains in Drupal 8 for backwards compatibility. That means that modules, sites and custom code using the file_unmanaged_copy() function will continue working even though a replacement solution is already available. The old solution is marked as deprecated, which means it will eventually be removed from Drupal altogether.

How do we deprecate?

Taking the example above, we use the @deprecated annotation on the function implementation. This documents when we deprecated the function, when it will be removed and what to use instead. We also add a @see annotation explaining where to read more. In the implementation of the function, a trigger_error() call is added to trigger a fail in tests that look for deprecation errors. Then, a backwards compatible implementation of the old solution is added, which is based on the new API. This is how a backwards compatible function implementation might look:

/**
 * Copies a file to a new location without database changes or hook invocation.
 *
 * [...]
 *
 * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
 *   Use \Drupal\Core\File\FileSystemInterface::copy().
 *
 * @see file_copy()
 * @see https://www.drupal.org/node/3006851
 */
function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
  @trigger_error('file_unmanaged_copy() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \\Drupal\\Core\\File\\FileSystemInterface::copy(). See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);

  // ... backwards compatible implementation ...
}

Finally, while not evident in this code example, a test should be added to confirm that the deprecation works properly.

While most deprecations are simple, some cases are less straightforward. Some will not be able to trigger_error() (such as global or class constants), and some will not be able to have a @deprecated annotation (such as an else {} clause in a condition). There are also some deprecations that were implemented before the current deprecation policy was adopted, and possibly missing the @deprecated annotation or trigger_error().

The important piece is that we have a way to identify "future changes" while maintaining backwards compatible implementations. This means Drupal 8 modules, custom code, etc. continue working in updated versions, but it is clear what needs to be changed in order to work with Drupal 9.

A more thorough explanation can be found in the Drupal core deprecation policy.

Which deprecated APIs were removed?

We removed nearly all deprecated APIs to the Drupal 9.0.0 release, and changed the remaining small number of deprecations to be deprecated for Drupal 10 instead. See a list of all things marked for deprecation in 8.9.x on api.drupal.org, including deprecations in dependencies such as Twig and Symfony. (All deprecations present in the 8.9.x branch have been removed from Drupal 9.0.0.)

When was the full list of deprecations for Drupal 9 available?

The full deprecation list was finalized in Drupal 8.8.0, which was released on December 4, 2019.

Help improve this page

Page status: No known problems

You can: