Drupal 10.2の開発者向けアップデート

[Drupal Advent Calendar 2023](https://qiita.com/advent-calendar/2023/drupal) の6日目です。

前日の投稿ではウミ菊池さんから[非エンジニア視点での Drupal 10.2 変更点ピックアップ](https://www.studio-umi.jp/blog/7/793)が紹介されていましたので、本記事ではDrupal 10.2に含まれる開発者向けのアップデート(の中から私が理解できたもの)をいくつか紹介しようと思います。執筆時点の最新バージョンである[Drupal 10.2.0-rc1](https://www.drupal.org/project/drupal/releases/10.2.0-rc1のリリースノート)を参照しています。

## 新機能

### プラグインタイプで、Annotationsの代わりにAttributesが利用できるように

Drupalでは、ブロックやViewsで利用されているプラグインシステムでDoctrine Annotationsが活用されています。PHP 8から言語ネイティブでAttributesがサポートされたことで、Drupal 10.2からAttributesを使用したプラグイン開発ができるようになりました。

Doctrine AnnotationsはDrupal 10中に非推奨となり、Drupal 11で削除される計画です。

AnnotatedClassDiscoveryで独自プラグインタイプを作成している方は、[Attributesを使用した実装方法](https://www.drupal.org/node/3395582)に移行する必要があります。

AnnotatedClassDiscoveryが利用されたプラグインタイプを用いてプラグインを作成している方は、まずプラグインタイプ側でAttributesがサポートされていることを確認し、[Attributesを使用した実装方法](https://www.drupal.org/node/3395575)に移行する必要があります。

Drupal 10.2の時点では、コアではBlockとActionのみがAttributesに対応しています。

#### コード例

ブロックプラグインタイプを使用してプラグインを作成した例を示します。

##### Drupal 10.1以前

```php:web/modules/custom/marucha/src/Plugin/Block/MaruchaBlock.php
<?php

namespace Drupal\marucha\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides marucha block.
 *
 * @Block(
 *   id = "marucha_block",
 *   admin_label = @Translation("Marucha Block"),
 * )
 */
class MaruchaBlock extends BlockBase {

  public function build() {
    return [
      '#markup' => 'This is marucha block.',
    ];
  }

}

```

##### Drupal 10.2以降

```php:web/modules/custom/marucha/src/Plugin/Block/MaruchaBlock.php
<?php

namespace Drupal\marucha\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Block\Attribute\Block;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
 * Provides marucha block.
 */
#[Block(
  id: 'marucha_block',
  admin_label: new TranslatableMarkup('Marucha Block')
)]
class MaruchaBlock extends BlockBase {

  public function build() {
    return [
      '#markup' => 'This is marucha block.',
    ];
  }

}

```

##### 差分

https://github.com/hmaruyama/drupal-10-2-updates/commit/049d4d27309d01b90cd4c7e56deca3e774c866fe

#### 参照

* https://www.drupal.org/node/3395582
* https://www.drupal.org/docs/drupal-apis/plugin-api/attribute-based-plugins
* https://speakerdeck.com/acquiajp/drupal-moziyurukai-fa-ru-men-jiang-zuo-di-7hui-puraguin

### イベントサブスクライバでSymfonyのautoconfigurationが利用可能に

イベントをサブスクライブする場合、モジュールの *.service.yml ファイルにサブスクライバを登録します。Drupal 10.2からは、service.ymlの `_defaults` キー内に `autoconfigure: true` を指定することで個別にタグをつける必要がなくなりました。

#### コード例

##### Drupal 10.1以前

```yml:web/modules/custom/marucha/marucha.services.yml
services:
  marucha.config_subscriber:
    class: '\Drupal\marucha\EventSubscriber\ConfigSubscriber'
    tags:
      - { name: 'event_subscriber' }
```

##### Drupal 10.2以降

```yml:web/modules/custom/marucha/marucha.services.yml
services:
  _defaults:
    autoconfigure: true
  marucha.config_subscriber:
    class: '\Drupal\marucha\EventSubscriber\ConfigSubscriber'
```

##### 差分

https://github.com/hmaruyama/drupal-10-2-updates/commit/9ace9df40e4e4c04e5ba281b2449267b0f692720

#### 参照

* https://www.drupal.org/node/3357408
* https://speakerdeck.com/acquiajp/drupal-moziyurukai-fa-ru-men-jiang-zuo-di-6hui-ibento

### ControllerBaseクラスでDIする際にcreate()メソッドが不要に

コントローラー、フォーム、ブロックなどのクラスでサービスを注入する場合、create()ファクトリメソッドをオーバーライドすることでサービスを読み込むことができます。

Drupal 10.2ではControllerBaseクラスに[AutowireTrait](https://www.drupal.org/node/3396179)が実装され、サービスを読み込むためであればcreate()メソッドの実装は不要になりました。[^1]

[^1]: フォームやブロックは・・・？

#### コード例

##### Drupal 10.1以前

```php:web/modules/custom/marucha/src/Controller/MaruchaController.php
<?php

namespace Drupal\marucha\Controller;

use Drupal\Component\Datetime\Time;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

class MaruchaController extends ControllerBase {

  protected $time;

  public function __construct(Time $time) {
    $this->time = $time;
  }

  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('datetime.time')
    );
  }

# 省略
```

##### Drupal 10.2以降

```php:web/modules/custom/marucha/src/Controller/MaruchaController.php
<?php

namespace Drupal\marucha\Controller;

use Drupal\Component\Datetime\Time;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class MaruchaController extends ControllerBase {

  protected $time;

  public function __construct(
    #[Autowire(service: 'datetime.time')]
    Time $time
  ) {
    $this->time = $time;
  }

# 省略
```

##### 差分

https://github.com/hmaruyama/drupal-10-2-updates/commit/c7577f5e726ae39897051ef25abca7e95de12581

#### 参照

* https://www.drupal.org/node/3395716
* https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/dependency-injection-for-a-form
* https://speakerdeck.com/acquiajp/drupal-moziyurukai-fa-ru-men-jiang-zuo-di-5hui-sabisu

### #config_targetプロパティがConfigFormBaseクラスに追加

以下のように、スキーマファイルで `constraints` キーを使用することで各構成値に制約を設定できます。

```yml:web/modules/custom/marucha/config/schema/marucha.schema.yml
marucha.settings:
  type: config_object
  label: 'Marucha settings'
  mapping:
      name:
        type: label
        label: 'Name'
        constraints:
          NotBlank:
            message: "入力してください！(スキーマファイル側で制御)"
```

しかし、フォームではこの制約の定義を読み込むことは出来ず、validateForm()メソッドで同じようなロジックを記述する必要がありました。

Drupal 10.2から、ConfigFormBaseクラスに `#config_target` プロパティが実装され、これを使用することでスキーマファイルに定義された制約で検証を行うことができるようになりました。

#### コード例

##### Drupal 10.1以前

```php:web/modules/custom/marucha/src/Form/MaruchaConfigForm.php
<?php

namespace Drupal\marucha\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

class MaruchaConfigForm extends ConfigFormBase {

  public function getFormId() {
    return 'marucha_config_form';
  }

  protected function getEditableConfigNames() {
    return ['marucha.settings'];
  }

  public function buildForm(array $form, FormStateInterface $form_state) {
    $config = $this->config('marucha.settings');
    $form['name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Name'),
      '#default_value' => $config->get('name'),
    ];
    return parent::buildForm($form, $form_state);
  }

  public function validateForm(array &$form, FormStateInterface $form_state) {
    if (!$form_state->getValue('name')) {
      $form_state->setErrorByName('name', '入力してください！(validateFormメソッドで制御)');
    }
  }

# 省略
```

![Screenshot 2023-12-09 at 17.57.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/2674b755-9b26-da6b-9235-0d1db45fbc02.png)

##### Drupal 10.2以降

```php:web/modules/custom/marucha/src/Form/MaruchaConfigForm.php
<?php

namespace Drupal\marucha\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

class MaruchaConfigForm extends ConfigFormBase {

  public function getFormId() {
    return 'marucha_config_form';
  }

  protected function getEditableConfigNames() {
    return ['marucha.settings'];
  }

  public function buildForm(array $form, FormStateInterface $form_state) {
    $config = $this->config('marucha.settings');
    $form['name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Name'),
      '#default_value' => $config->get('name'),
      '#config_target' => 'marucha.settings:name',
    ];
    return parent::buildForm($form, $form_state);
  }

# 省略
```

![Screenshot 2023-12-09 at 17.58.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/4b1ac5df-a09a-8e7e-f3cd-9fa87eb984b2.png)

##### 差分

https://github.com/hmaruyama/drupal-10-2-updates/commit/d3911861565008b327388b714eff4804030a3c25

#### 参照

* https://www.drupal.org/node/3373502
* https://youtu.be/Dnxp3aBPGhs?si=ltNuSiohmaODXfn8&t=296
* https://speakerdeck.com/acquiajp/drupal-moziyurukai-fa-ru-men-jiang-zuo-di-9hui-gou-cheng

## まとめ

私が触ったことがあるAPIを中心に新機能を紹介しました。コード例はリポジトリに公開しました。

https://github.com/hmaruyama/drupal-10-2-updates