[Drupal]カスタムフォームのテンプレートをカスタマイズする方法

## やりたいこと
Form APIで作成したカスタムフォームのテンプレートをカスタマイズしたい

## やり方
### フォームを作成
まずは通常通りフォームとルートを作成する
[Introduction to Form API](https://www.drupal.org/docs/drupal-apis/form-api/introduction-to-form-api)

- `my_module/src/Form/MyForm.php`

```php
<?php

namespace Drupal\my_module\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Implements an example form.
 */
class ExampleForm extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'example_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['phone_number'] = [
      '#type' => 'tel',
      '#title' => $this->t('Your phone number'),
    ];
    $form['actions']['#type'] = 'actions';
    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Save'),
      '#button_type' => 'primary',
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    if (strlen($form_state->getValue('phone_number')) < 3) {
      $form_state->setErrorByName('phone_number', $this->t('The phone number is too short. Please enter a full phone number.'));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->messenger()->addStatus($this->t('Your phone number is @number', ['@number' => $form_state->getValue('phone_number')]));
  }

}
```

- `my_module/my_module.routing.yml`

```
example.form:
  path: '/example-form'
  defaults:
    _title: 'Example form'
    _form: '\Drupal\my_module\Form\ExampleForm'
  requirements:
    _permission: 'access content'
```

この時点でフォームにはデフォルトのテンプレートが適用されている
![スクリーンショット 2021-12-01 19.24.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/bc627618-25d6-9cf6-0fd3-6b22aaa9ba03.png)

![スクリーンショット 2021-12-01 19.26.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/32c63fcc-c7f0-01e9-a8f8-bd5607352581.png)

### hook_themeにテーマの情報を登録
`my_module/my_module.module`に以下のように`hook_theme`を書く。

```php
/**
 * Implements hook_theme().
 */
function my_module_theme($existing, $type, $theme, $path) {
  return [
    'example_form' => [  //テーマ名（任意）、テンプレート名になる
      'render element' => 'form', // MyFormのbuildForm()で渡されてくるレンダリング配列のTwigの中での変数名(任意).
      // 'template' => 'another-template-name', (テーマ名とテンプレート名が異なる場合はこちらで指定)
    ],
  ];
}
```

### テンプレートの作成
`my_module/templates/example-form.html.twig`を作成（テンプレート名は`hook_theme`で指定したテーマ名と同じだが、ケバブケースにするように注意）
内容はとりあえずtestだけにしておく

```
test
```

### フォームでテーマを指定
`my_module/src/Form/MyForm.php`の`buildForm()`内のどこかに以下の一行を追加

```
$form['#theme'] = 'example_form'; 
```

### フォームを確認
フォームにアクセスして`test`が表示されていればOK

![スクリーンショット 2021-12-01 19.29.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/5545e143-0219-69bc-d333-35bc4ec2a241.png)


### フォームをカスタマイズ
`hook_theme`で設定したように、フォームの内容は`form`という変数に入っているので、テンプレートを以下のようにすると完全なフォームが表示される。

```
{{ form }}
```
![スクリーンショット 2021-12-01 19.32.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/d2d4c57d-06d7-7c09-99b4-a1a9d7cecacc.png)

以下のように書くと特定のフォーム要素だけが取り出せる。

```twig
<div class="background-grey">{{ form.phone_number }}</div>
```
![スクリーンショット 2021-12-01 20.09.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/114f4880-70cb-be3b-f0ee-671e034260d1.png)


ただし全部のフォーム要素を並べても、フォームとしては機能しない。（送信ボタンを押しても何も起こらない）

```twig
<div class="background-grey">{{ form.phone_number }}</div>
{{ form.actions.submit }}
```

Twigの`without`を使うとうまく機能するようになる。

```twig
<div class="background-grey">{{ form.phone_number }}</div>
{{ form|without('phone_number') }}
```
![スクリーンショット 2021-12-01 20.10.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/f184ed8a-4a17-46a6-da7d-cb6ea72e2877.png)