Last updated 12 May 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

What we can not deprecate

Do not deprecate test classes. Any test that could be considered deprecated should be removed or modified to be relevant.

Tests which exercise deprecated code can be annotated with @group legacy in order to avoid deprecation notices during testing, but a follow-up issue should be filed in order to fix such tests to not exercise deprecated code.

How we deprecate

A deprecation consists of three parts:

  1. A @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. A @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();
}

Return values (especially array data structures)

Generally a return value of method should not changed. However if the method returns an array it might be permissible to add new values and deprecate existing values. In this instance, it is not possible to use @deprecated because the method is not being deprecated, or @trigger_error() because you cannot determine if the deprecated key is used. Therefore it is important that the return value is fully documented and the array keys are listed and those that are deprecated are clearly listed. The method should also have an @see to the relevant change record. For example:

   * @return array
   *   An array of typed data IDs keyed by corresponding relation URI. The keys
   *   are:
   *   - 'entity_type_id'
   *   - 'bundle'
   *   - 'field_name'
   *   - 'entity_type' (deprecated)
   *   The values for 'entity_type_id', 'bundle' and 'field_name' are strings.
   *   The 'entity_type' key exists for backwards compatibility and its value is
   *   the full entity type object. The 'entity_type' key will be removed before
   *   Drupal 9.0.
   *
   * @see https://www.drupal.org/node/2877608

(From \Drupal\hal\LinkManager\RelationLinkManager::getRelations())

Also because we want to be able to find the deprecated code to remove before Drupal 9 you need to add an @todo to #2716163: [meta] Remove all the deprecations. For example:

    // @todo https://www.drupal.org/node/2716163 Remove this in Drupal 9.0.
    foreach ($data as $relation_uri => $ids) {
      $data[$relation_uri]['entity_type'] = $this->entityManager->getDefinition($ids['entity_type_id']);
    }

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
<?php

namespace Drupal\datetime\Tests\Views;
	
@trigger_error('The '. __NAMESPACE__ . '\DateTimeHandlerTestBase is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\BrowserTestBase', E_USER_DEPRECATED);

/**
 * Base class for testing datetime handlers.
 *
 * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0.
 * Use \Drupal\Tests\BrowserTestBase.
 */
abstract class DateTimeHandlerTestBase extends HandlerTestBase {

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.