Converting PHP class annotations to PHP attributes
For years, Drupal relied on the doctrine/annotations library to add metadata to plugin classes. This approach works through docblock comments. The /** ... */ blocks that appear above class definitions. While convenient, this method has several inherent limitations and will be gradually replaced by PHP attributes.
In this blog post, i will describe the changes based on the well known migrate_plus module.
The release of Drupal's migrate_plus module version 6.0.9 introduced a significant breaking change that will impact developers maintaining custom migration plugins: the module now requires plugins to use PHP Attributes instead of the older Doctrine annotation syntax. Unfortunately the change was introduced without ensuring backward compatibility and leads to no longer working migrate plugins.
Lets land this. Its very disruptive. But it will always be disruptive. And much of the RTBC queue is cleaned out right now, so this is about as good as it gets.
Source: d.o/3541220
This transition is not unique to migrate_plus and reflects a broader movement across the Drupal ecosystem toward modernising plugin definitions using native PHP 8.1+ language features.
Understanding the shift: Why PHP attributes matter
Before diving into the a step-by-step guide on how to convert your plugins, it's important to understand why this breaking change exists and what problem it solves.
The Problems with Doctrine Annotations
- Performance overhead. Annotations are interpreted at runtime. The extra parsing step adds computational overhead during plugin discovery, which Drupal mitigates through caching but cannot completely eliminate. For large sites with many plugins, this can become noticeable.
- Error-prone syntax. Because annotations exist in comments, they're vulnerable to subtle formatting errors. For example, if a docblock tag like @see appears after an annotation, the parser becomes confused and fails to recognize the annotation entirely. These errors are difficult to debug because they occur at runtime, not during static analysis.
- Maintenance burden. The doctrine/annotations library is no longer actively maintained and has been deprecated by its maintainers. This means the Drupal community will eventually need to support the parsing library independently or risk it becoming a liability as PHP evolves.
- Limited IDE integration. Since annotations live in comments rather than code, IDEs cannot natively provide autocomplete, type hints, or navigation features for them.
PHP Attributes: A Better Solution
PHP 8.0 introduced native attributes. This describes a language feature designed specifically to solve the metadata problem. While the initial PHP 8.0 implementation lacked support for nested attributes (essential for Drupal's complex plugin metadata), PHP 8.1 added that capability, making attributes viable for Drupal's needs.
Attributes provide several advantages over annotations:
- Compile-time processing. PHP processes attributes during compilation, not at runtime. This eliminates the parsing overhead entirely and results in faster plugin discovery.
- Type safety and validation. Since attributes are part of the PHP language itself, they undergo the same validation as regular code. Invalid syntax is caught immediately, not discovered later at runtime.
- Superior IDE support. Modern IDEs recognize attributes as native language constructs and provide full autocomplete, type hints, and code navigation.
- Clear semantics. Using #[AttributeName] syntax makes metadata clearly distinct from documentation comments. The code is more readable and maintainable.
- Future-proof. As PHP continues to evolve, attributes will be part of that evolution. Drupal won't be dependent on a third-party library.
Step-by-Step conversion guide for migrate_plus
As an example for the conversion, we take a simple migrate_plus plugin and show, what needs to be changed.
Step 1: Locate custom migrate_plus plugins
The migrate_plus plugin supports the following plugin types.
your_module/
├── src/
│ └── Plugin/
│ └── migrate_plus/
│ ├── data_parser/ (data parser plugins)
│ └── data_fetcher/ (data_fetcher plugins)
│ └── authentication/ (authentication plugins)Step 2: Add the required use Statement
Let's take a data_parser plugin for our example. Import the attribute class at the top of your file. For source plugins, add:
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\migrate_plus\Attribute\DataParser;Step 3: Convert the annotation to an attribute
Remove the docblock annotation and replace it with an attribute declaration.
Before
/**
* Data parser plugin description.
*
* @DataParser(
* id = "my_data_parser_plugin_id",
* title = @Translation("My custom data parser plugin")
* )
*/
class MyDataParserPlugin implements DataParserPluginInterface {
// Plugin code.
}After
/**
* Data parser plugin description.
*/
#[DataParser(
id: 'my_data_parser_plugin_id',
title: new TranslatableMarkup('My custom data parser plugin')
)]
class MyDataParserPlugin implements DataParserPluginInterface {
// Plugin code.
}Key changes:
- Move the attribute outside the docblock comment
- Use id: instead of id =
- Change "string" to 'string' (or keep double quotes—either works)
Step 4: Test and verify
After converting your plugins, rebuild Drupal's cache:
drush cache:rebuildAfterwards we can verify, if our migration plugin is working:
drush migrate:statusThis shouldn't throw any errors and the migration should run without problems.
Important notes for migrate_plus 6.0.9
The migrate_plus module version 6.0.9 specifically addressed the plugin discovery layer to support attributes (d.o/3541220).
- Backward compatibility is not given. Unlike Drupal core's gradual transition (where annotations and attributes coexist), migrate_plus 6.0.9 requires plugins to use attributes. Old annotation-based plugins will not be discovered and will cause migration failures.
- Related plugins must be updated. If your migration relies on custom source, process, or other plugin types, all of them must be converted. A migration cannot mix annotation-based and attribute-based plugins.
- The breaking change affects contrib and custom modules. Any contributed or custom module providing migration plugins will need updates. If you use a third-party migration plugin module, check its compatibility with migrate_plus 6.0.9 or later.
Additional ressources: