(追記20151014: 本記事は執筆時点の Drupal 8 のバージョン(開発版)にもとづく古い解説となっています)

本日はインストール編管理画面編カスタムモジュール作成編と続けてきた Drupal 8 入門シリーズの続きとして「 Drupal 8 のウェブフォーム作成」のお話ができればと思います。

ウェブフォームというものは、CMS ・ウェブアプリケーションにおいてデータ処理の中心的役割を担う存在です。 近年は JavaScript を活用することで、必ずしもウェブフォームを使わなくても通信とデータベースとのやりとりができるようになりましたが、それでも、データ処理の中心的存在は依然かわらずフォームといえるかと思います。

今回は、そんなフォームを Drupal 8 で扱う方法をご紹介したいと思います。 以下、まずは想定読者と前提について見ていきましょう。

想定読者

今回想定している読者層は「 Drupal 7 でフォーム開発の経験がある方」となります。 今回は「 Drupal 8 ならではのフォームのポイント」に焦点を絞り、「フォームとはなんぞや」「 Drupal におけるフォームの基本的な考え方」といったベーシックな部分は理解されていることを前提に、特に Drupal 7 から 8 にかけて変わったところを主に取り上げたいと思います。

前提

上述のとおり「 Drupal 7 でフォーム開発の経験がある方」が対象であるという点のほかに次のような前提を置かせていただきました。

  • Drupal 8 のインストール・セットアップはすでに済んでいる
  • フォームのコードの入れ物となるカスタムモジュールも作成できている

このあたりは上掲の記事である程度触れていますので、まだの方はご参考にしてみてください。

Drupal 8 のフォームの概要

まずは、 Drupal 7 のフォームと比べたときの Drupal 8 のフォームの特徴をざっくりと見ていきましょう。

ポイント 1: フォームは「フォームクラス」で作成する

Drupal 8 ではフォームは「フォームクラス」という形で作成します。

Drupal 7 のフォームは hook_form() hook_form_validate() hook_form_submit() という 3 つの関数を別個に実装する形で作成しましたが、 Drupal 8 ではフォーム周りにオブジェクト指向のスタイルが本格的に導入され、 Drupal\Core\Form\FormBase というベースクラスを継承する形でフォームクラスを作成するスタイルとなりました。

class SampleForm extends FormBase {
  // ...
}

関数(メソッド)の中身は Drupal 7 とほぼほぼ同じですが、よりわかりやすくコンパクトに記述できるようになっています。

ポイント 2: drupal_get_form() の代わりに getForm() を使う

Drupal 開発者にはおなじみの drupal_get_form() が Drupal 8 では削除される形になります。 代わりに getForm() というメソッドがフォーム生成を担うインタフェースとして導入されました。

\Drupal::formBuilder()->getForm('\Drupal\umi\Form\SampleForm')

ちなみに Drupal 8 の正式版リリース前の開発版では drupal_get_form() がまだ存在しています。

ポイント 3: フォームをページに表示するには「 _form 」プロパティを使う

Drupal 8 でフォームをページに表示するには、ルーティングで「 _form 」プロパティにフォームクラスを設定します。

Drupal 7 でフォームをページに表示するには hook_menu() の中で drupal_get_form() にフォームを渡していましたが、 Drupal 8 では routing.yml ファイルの中で _form プロパティを使ってフォームを指定します。

defaults:
  _content: '\Drupal\umi\Controller\UmiHelloController::form'

こちらも基本的な考え方は Drupal 7 から変わっていませんが、コードがよりシンプルにわかりやすく記述できるようになりました。

ポイント 4: hook_form_alter() まわりは D7 といっしょ

その他フォーム関連でよく使うのは、既存のフォームを改編する hook_form_alter() hook_form_FORM_ID_alter() あたりでしょう。 このあたりの関数は Drupal 8 になっても Drupal 7 からの変更はありません。

Drupal 8 のフォームの主なポイントどころは以上でしょうか。 つづいて、実際にかんたんなフォームをひとつ作って流れを見ていきましょう。

フォーム作成の流れ

1. モジュールを作成・インストール

まずはフォームのコードを記述する入れ物となるモジュールを作成します。 今回は umi というモジュールがあらかじめ作成されているものとします。 実際の作成方法はカスタムモジュール作成編をご参照ください。

2. フォームクラスを定義

ここから実際のフォーム作成に入ります。 まずはフォームクラスを記述するファイルを作成します。

umi モジュールの下に lib/Drupal/umi/Form とディレクトリを 4 つ作成し、その中に SampleForm.php という PHP ファイルを作成しましょう。

ファイルをエディタで開いたら、次の内容を記入します。 少し長めですが、そのまま記入して保存してください。

<?php

// 名前空間の定義
namespace Drupal\umi\Form;

// 必要なクラスの読み込み
use Drupal\Core\Form\FormBase;

// フォームクラスの定義
// ベースクラスである FormBase を継承して定義する
class SampleForm extends FormBase {
  // フォーム ID を返す
  public function getFormId() {
    return 'sample_form';
  }

  // フォームを生成する
  // 内容は D7 の hook_form() と同じ
  public function buildForm(array $form, array &$form_state) {
    $form['name'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('name')
    );

    $form['phone_number'] = array(
      '#type' => 'tel',
      '#title' => $this->t('Phone number')
    );

    $form['actions']['#type'] = 'actions';
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => $this->t('Save'),
    );

    return $form;
  }

  // バリデーション処理を行う
  // 内容は D7 の hook_form_validate() と同じ
  public function validateForm(array &$form, array &$form_state) {
    // バリデーションエラーがある場合は $this->setFormError で戻す
    if (strlen($form_state['values']['phone_number']) < 3) {
      $this->setFormError('phone_number',
        $form_state, t('The phone number is too short.')
      );
    }
  }

  // submit された情報を処理する
  // 内容は D7 の hook_form_submit() と同じ
  public function submitForm(array &$form, array &$form_state) {
    drupal_set_message(
      $this->t('Your name: @name, phone number: @number.', array(
          '@name' => $form_state['values']['name'],
          '@number' => $form_state['values']['phone_number'],
      ))
    );
  }
}

FormBase というベースクラスを継承したクラスの中に、 getFormId buildForm validateForm submitForm という 4 つの public メソッドを定義しました。 getFormId については新しい機能となりますが、後者 3 つについては名前からも想像がつくとおり、以下の形で Drupal 7 の関数と対応しています。

  • buildForm: D7 の hook_form
  • validateForm: D7 の hook_form_validate
  • submitForm: D7 の hook_form_submit

以上で、フォームの本体の作成は完了です。 メソッド内に出てくるインスタンスメソッド tsetFormError は、それぞれ Drupal 7 の t 関数、 form_set_error 関数に対応しています。

フォーム本体の作成はこれで完了です。

3. ルーティング設定を追加

フォーム本体が作成できたので、あとはこれをページに表示させるためのルーティング設定を行います。

フォームクラスを作成したモジュール(今回の場合は umi モジュール)のディレクトリの直下にある .routing.yml ファイル(今回の場合は umi.routing.yml )をエディタで開きます。 モジュールを作成したばかりでまだ存在しない場合は新規に作成しましょう。

ファイルが開けたら、中身に次の内容を記述して保存します。

umi.sample_form:
  path: 'sample-form'
  defaults:
    _form: '\Drupal\umi\Form\SampleForm'
  requirements:
    _access: 'TRUE'

YAML ファイルにおいてはインデントも階層構造を表す重要な情報となりますので、インデントを変更しないようにご注意ください。

ルーティング設定の全般的なお話についてはコントリビュートモジュール作成編をご覧いただければと思いますが、ざっくりとした意味合いの解説を以下に載せておきます。

  • path: このフォームを表示させたい URL パス。 sample-form と指定してある場合は http://ドメイン/sample-form というパスのことを意味します。
  • _form: 描画したいフォームのクラス。 _content の代わりに直接クラスを指定する形で使う。
  • _access: アクセス制御を行う関数。 D7 でいう access callback 。

4. ページを確認

フォームを作成し、ルーティング設定を行えば、あとはページに反映されていることを確認するだけです。

ルーティングに関わる部分を変更したので、一度キャッシュクリアをしておきましょう。 管理画面に管理者としてログインし、環境設定 → パフォーマンスのページを開きます。 パスでいうとこちら /admin/config/development/performance のページにアクセスします。

インタフェースが日本語に設定してある場合は「すべてのキャッシュをクリアー」というボタンがメイン部分に表示されているかと思うのでこれをクリックしましょう。 するとキャッシュがクリアになり、新たに追加したパスが Drupal に認識されるようになります。

以下のような画面が表示されればフォームの作成は無事完了!です。

Drupal 8 フォーム開発 01 フォーム画面

Drupal 8 フォーム開発 02 バリデーションエラーメッセージ

Drupal 8 フォーム開発 03 バリデーションエラー画面

Drupal 8 フォーム開発 04 サブミット後画面

以上です。 かんたんにではありますが、 Drupal 8 のフォーム機能をご紹介してみました。 いかがだったでしょうか?

今回はデータベースに関わる部分には触れませんでしたが、 submitForm メソッド内で受け取ったデータをデータベースに渡すような処理を追加すればそのままデータベース操作ができるようなフォームになります。

基本的な考え方は Drupal 7 と同じままですが、オブジェクト指向スタイルが本格的に導入され、よりシンプルで洗練された書き方ができるようになりました。 最初は勝手がちがうところに少し戸惑いそうですが、慣れてこればよりスムーズ・快適に開発ができるようになりそうです。