Last updated March 1, 2017.

Why we deprecate

In Drupal 8, minor updates can introduce new APIs and features. When a new API is added, the old API will be deprecated in favor of the new one. We cannot remove the old API in a minor release because Drupal 8 makes a backwards compatibility promise, but it will usually be removed in the next major version (Drupal 9).

Contributed project developers, as well as those maintaining custom integrations, should follow the deprecations when possible and use the latest APIs available. This means that when Drupal 9 is released they will have to make fewer changes to be compatible

What we can deprecate

How we deprecate

A deprecation consists of three parts:

  1. An @deprecated PHPdoc tag that indicates when the code was deprecated, when it will be removed, and what to use instead, usually with a link to the change record for the change.
  2. An @trigger_error('...', E_USER_DEPRECATED) at runtime to notify developers that deprecated code is being used. The @ suppression should be used in most cases so that we can customize the error handling and avoid flooding logs on production. In some cases, we will omit the @ if it is important to notify developers of a behavior or BC break (e.g. for a critical issue).
  3. A unit test proving the deprecation notice will be triggered when the deprecated code is called.

The PHPdoc text and the trigger_error() message should follow the same format:

@deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. Use \Drupal\Foo\Bar::baz() instead. 
@see http://drupal.org/node/the-change-notice-nid.

@trigger_error('baz() is deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. Use \Drupal\Foo\Bar::baz() instead. See http://drupal.org/node/the-change-notice-nid.', E_USER_DEPRECATED);

Any additional details about specific values should be included after the link to the change record.

Methods

Add @trigger_error('...', E_USER_DEPRECATED) at the top of the method. Add @deprecated to the docblock for the method. For example:

/**
 * Checks if a string is safe to output.
 *
 * @param string|\Drupal\Component\Render\MarkupInterface $string
 *   The content to be checked.
 * @param string $strategy
 *   (optional) This value is ignored.
 *
 * @return bool
 *   TRUE if the string has been marked secure, FALSE otherwise.
 *
 * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
 *   Instead, you should just check if a variable is an instance of
 *   \Drupal\Component\Render\MarkupInterface.
 *
 * @see https://www.drupal.org/node/2549395
 */
public static function isSafe($string, $strategy = 'html') {
  @trigger_error('SafeMarkup::isSafe() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Instead, you should just check if a variable is an instance of \Drupal\Component\Render\MarkupInterface. See https://www.drupal.org/node/2549395.', E_USER_DEPRECATED);
  return $string instanceof MarkupInterface;
}

Method parameters

Add @trigger_error('...', E_USER_DEPRECATED) at the start of the code block that handles the parameter. Add an (deprecated) phpdoc parameter and detail how it is deprecated. Add an @see to the related change record to the method. For example:

    /**
     * Sets a service.
     *
     * @param string $id
     *   The service identifier.
     * @param object $service
     *   The service instance.
     * @param string $scope
     *   (deprecated) The scope of the service. The $scope parameter is deprecated since version 8.0.0 and will be removed in 9.0.0. 
     *
     * @see https://www.drupal.org/node/2549395
     */
  public function set($id, $service, $scope = ContainerInterface::SCOPE_CONTAINER) {
    if (!in_array($scope, array('container', 'request')) || ('request' === $scope && 'request' !== $id)) {
      @trigger_error('The concept of container scopes is deprecated since version 8.0.0 and will be removed in 9.0.0. Omit the third parameter. See https://www.drupal.org/node/2549395.', E_USER_DEPRECATED);
    }
    $this->services[$id] = $service;
  }

Procedural functions

Add @trigger_error('...', E_USER_DEPRECATED) at the top of the function. Add @deprecated to the docblock for the function. For example:

/**
 * Deletes old cached CSS files.
 *
 * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
 *   Use \Drupal\Core\Asset\AssetCollectionOptimizerInterface::deleteAll().
 *
 * @see https://www.drupal.org/node/2317841
 */
function drupal_clear_css_cache() {
  @trigger_error('drupal_clear_css_cache() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Asset\AssetCollectionOptimizerInterface::deleteAll(). See https://www.drupal.org/node/2317841.', E_USER_DEPRECATED);
  \Drupal::service('asset.css.collection_optimizer')->deleteAll();
}

Services

Add a deprecation key and message to the service definition.

  router.matcher.final_matcher:
    class: Drupal\Core\Routing\UrlMatcher
    arguments: ['@path.current']
    deprecated: The "%service_id%" service is deprecated. You should use the 'router.no_access_checks' service instead. See https://www.drupal.org/node/2317841.

Further reading: https://symfony.com/blog/new-in-symfony-2-8-deprecated-service-definitions

Code paths & behavior

For example when we have a code path handling a deprecated key in a YAML file.
Add @trigger_error('...', E_USER_DEPRECATED) at the start of the code block that handles the backwards compatibility layer. Add an @deprecated phpdoc annotation to the method or function docblock that contains the code path. For example:

// @todo

Classes

Add @trigger_error('...', E_USER_DEPRECATED) under the namespace declaration. Add an @deprecated phpdoc annotation to the class docblock. For example:

// @todo

Files containing procedural functions

Add @trigger_error('...', E_USER_DEPRECATED) after the @file docblock. Add an @deprecated phpdoc annotation to the file docblock. For example:

/**
 * @file
 * Miscellaneous functions.
 *
 * @deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0.
 *   See each function in the file for individual deprecation notices with
 *   upgrade instructions.
 *
 * @see https://www.drupal.org/node/12345678
 */
@trigger_error(__FILE__ . ' is deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. See individual methods in the file for individual deprecation notices with upgrade instructions. See https://www.drupal.org/node/12345678.', E_USER_DEPRECATED);

Unintended behavior and security issues

For cases where using the previous API could result in unintended behavior or security issues, we may deliberately break backwards compatibility. Omit error suppression in such cases, so that developers can find and fix code that may no longer work after the BC break. For example:

elseif (strpos($string, $key) !== FALSE) {
  trigger_error("Fallthrough for unrecognized placeholders to %variable has been removed. See https://www.drupal.org/node/2605274. Invalid placeholder ($key) in string: $string', E_USER_DEPRECATED);
}

In the code above from #2807705: FormattableMarkup::placeholderFormat() can result in unsafe replacements we removed the ability to replace placeholders that started with an alpha character. The replacement was done without sanitization and opened possible security issues, so rather than maintain backwards compatibility, we stopped doing the replacement altogether. Therefore, we trigger an error without suppression so contributed projects, custom code, and real sites are alerted immediately to the problem and could fix any errant placeholders.