logo

パレット - カラフルに🎨

Palette — ビジュアルページビルダー、デザインの専門知識は不要です。

ライブデモ パレットをダウンロード

Scroll

9.8.1. hook_form_alter() 既存のフォームに submit と validate を追加する。

27/09/2019, by mikhail

前のレッスンの 1 つで、フックとは何かを学びました。このレッスンでは、hook_form_alter() フックを実際に扱い、既存のフォームに機能を追加します。

コード例は github で見られます:
https://github.com/levmyshkin/drupalbook8

このレッスンでは、フックを実際に見始めます。後でフックに戻り、他のいくつかの例を見ます。とりあえず、hook_form_alter() から始めましょう。

モジュールにフックを追加するには、MODULENAME.module という名前のファイルを作成する必要があります。ですから、drupalbook.module という名前のファイルを作成します。これは、私たちのフックと補助関数を保存する PHP ファイルになります。それ以外については、src フォルダーに別々のファイルとクラスを使うほうが良いです。とりあえず、ファイルには開始タグだけを追加できます。

drupalbook

それでは、hook_form_alter() を追加しましょう。PhpStorm を使っている場合は、フックの名前を書き始めると、PhpStorm がフックの 1 つを選ぶよう提案します。この方法でフックを選ぶと、PhpStorm が自動的に引数を関数に代入するので、どの引数を追加する必要があるかを覚えたりヘルプで調べたりする必要はありません:

fubction

フックを追加したいとき、hook という単語をあなたのモジュールの名前に置き換える必要があり、そうすると drupal がフックの場所にあなたのコードを自動的に挿入します。その結果、この関数ができるはずです:

/**
 * Implements hook_form_alter().
 */
function drupalbook_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {

}

前のレッスンで、$form、$form_state に何が保存されるかをすでに検討しました。$form_id には、適切なメソッドで定義したフォーム id を保存する必要があります:

9.8. Drupal でのフォームの操作。プログラムで構成フォームを追加する。

それでは、前のレッスンで作った API キーを追加するフォームを拡張しましょう。そのために、フォーム id を定義する必要があります。それはこのメソッドで見られます:

function getFormId() {
  return 'drupalbook_admin_settings';
}

あるいは、DOM インスペクターを開いて HTML の id 属性を見ます:

settings

ここでは、-(ハイフン)を _(アンダースコア)に置き換える必要があります。しかし注意してください。form タグ属性の id は、AJAX リクエストの後にインクリメントされることがあります。例えば、drupalbook-admin-settings-0、drupalbook-admin-settings-1、drupalbook-admin-settings-2 など。私たちが必要なのは末尾に数字のない部分で、これは getFormId() メソッドで指定されるフォーム id から形成される部分です。

次に、私たちのコードの実行を drupalbook_admin_settings フォームだけに限定する必要があります。というのも、hook_form_alter() にあるコードは、Drupal Form API を通じて生成される絶対にすべてのフォームに対して実行されるからです:

($form_id == 'drupalbook_admin_settings') {
  // Further code here.
}

if の中に、キー API フォーム用のコードを書けます。空のフィールドに何を書くべきかを示すために、テキストフィールドにプレースホルダーを追加しましょう:

($form_id == 'drupalbook_admin_settings') {
  $form['drupalbook_api_key']['#attributes']['placeholder'] = 'API key';
  $form['drupalbook_api_client_id']['#attributes']['placeholder'] = 'API client ID';
}

hook_form_alter() を適用するには、キャッシュをクリアします。

api key

#attributes は textfield の可能なキーとしてドキュメントに記載されていませんが:

https://api.drupal.org/api/drupal/core%2

それでも、このキーはタグの属性を指定するために使えます:class、placeholder、さまざまな rel 属性。より明確な表は Drupal 7 Form API 用のものです:

https://api.drupal.org/api/drupal/developer%21topics%21forms_api_reference.html/7.x

ここで textfield フィールド用の #attributes が見つかります。

属性値は、setAttributes メソッドでフォーム要素を生成(レンダリング)する際に Form API で設定されます:

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%

これは API ドキュメントフォームには記載されていませんが、それが機能することを自分で確認できます。

フォームフィールドに設定できる他の値は、基底の FormElement クラスで見られます:
https://api.drupal.org/api/drupal/core%21lib%2

Drupal Form API は不明瞭で分かりにくく見えます。実際そうで、私もその中のいくつかのことをまだ理解していません。しかし、この API を使うのは十分にシンプルで明確です。手元に動作するサンプルがあればなおさらです。ですから、質問があってフォームであれこれをどうするか分からない場合は、インターネットで探せば 100% 動作する例が見つかります。すべてのフォームは Drupal の Cache、Render、Theming のすべての API を通り、フォーム内のどのフィールドも hook_form_alter() を通じて変更できることを知っておく必要があります。そして、それをどうするかは Google を通じてすでに調べられます。また、Form API に出会う回数が多く、それを使った例を解析する回数が多いほど、この Form API はあなたにとってより簡単で明確になります。そして、それがこれほど大きいことを心配しないでください。今必要な部分だけを使えばいいのです。

Validate
それでは、Form API を通じて validate 関数をどう使うかを見てみましょう。Key API フィールドが google という単語で始まる、例えば google-KEY123a3sa のように、チェックしましょう。最初にフォームのコードを書いていれば、validateForm() メソッドを挿入し、その中ですべてのチェックを行えました:

/**
 * {@inheritdoc}
 */
public function validateForm(array &$form, FormStateInterface $form_state) {

}

しかし、私たちが扱うフォームはしばしば contrib モジュールにあり、そのようなフォームのコードはカスタムモジュールに書くほうが良いです。ですから、フォームに新しい検証関数を追加しましょう:

['#validate'][] = 'drupalbook_settings_validate';

#validate 配列には、すべてのコールバックと検証関数を保存します。コールバックは、名前による関数の呼び出しです。つまり、['callback_function1', 'callback_function2'] 配列を追加し、次にこれらの名前を配列から取り、drupal がこれらの関数を呼び出します。この場合、drupal はフォームをチェックするためにこれらの関数を呼び出します。そして今、drupalbook.module ファイルに drupalbook_settings_validate 関数を作成する必要があります。この関数には $form、$form_state というパラメーターがあります:

/**
 * Custom validation callback.
 */
function drupalbook_settings_validate(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {

}

そして今、API Key フィールド自体のチェックを追加しましょう:

/**
 * Custom validation callback.
 */
function drupalbook_settings_validate(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  if (strpos($form_state->getValue('drupalbook_api_key'), 'google') === FALSE) {
    $form_state->setErrorByName('drupalbook_api_key', t('API Key must start from "google".'));
  }
}

厳密比較演算子 === を使います。というのも、google が文字列の先頭にある場合、strpos() は 0 を返し、PHP では (0 == FALSE) が TRUE を返すからです。PHP では ''、0、NULL、FALSE はすべて空の値であり、単純比較 == では等しいのです。

これで、設定フォームを保存するたびにチェックが実行され、チェックが通らない場合は drupal がエラーを生成し、設定は保存されません:

settings form

Submit
すべての validate 関数が実行され、エラーが発生しなかった後、drupal は submit 関数を呼び出します。これらはフォームからデータを送信した後に機能します。前のレッスンで submitForm() メソッドをすでに見ました。そこで、データを構成に保存しました。しかし、submit で他のアクション、例えばデータの変更や、一部のデータを他のエンティティに保存することもできます。API Key の使い方に関する追加のメッセージを表示する、もう 1 つの関数を作りましょう。hook_form_alter() に関数名を追加します:

['#submit'][] = 'drupalbook_settings_submit';

次に、$form、$form_state もパラメーターに渡す drupalbook_settings_submit() 関数で、メッセージを表示します。

/**
 * Custom submit callback.
 */
function drupalbook_settings_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  \Drupal::messenger()->addStatus(
    t(htmlentities('Insert API key in your  tag:  .'))
  );
}

htmlentites() 関数を使うのは、「<」、「/」、「>」タグを出力するために使うすべての特殊文字が drupal によって削除されないようにするためです。すべての HTML テキストは t() 関数から切り取られます。これは、テキストに貼り付けられないようにするためです。例えば、そのようなコードに別のサイトへの javascript リダイレクトが含まれている場合、メッセージ表示時にリダイレクトされてしまいます。だからこそ、タグを表示するために htmlentities 関数を使います。

検証関数が失敗した場合はメッセージは表示されず、フォームにエラーがない場合にのみメッセージが表示されます。

drupalbook form

以前は、メッセージを出力するために drupal で drupal_set_message 関数が使われていました:

(t('An error occurred and processing did not complete.'));

しかし今では、皆どこでも統一して OOP の使用に導こうとしています。

以下は、drupalbook.module ファイルの現在のコード全体です:

/**
 * Implements hook_form_alter().
 */
function drupalbook_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  if ($form_id == 'drupalbook_admin_settings') {
    $form['drupalbook_api_key']['#attributes']['placeholder'] = 'API key';
    $form['drupalbook_api_client_id']['#attributes']['placeholder'] = 'API client ID';

    $form['#validate'][] = 'drupalbook_settings_validate';
    $form['#submit'][] = 'drupalbook_settings_submit';
  }
}

/**
 * Custom validation callback.
 */
function drupalbook_settings_validate(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  if (strpos($form_state->getValue('drupalbook_api_key'), 'google') === FALSE) {
    $form_state->setErrorByName('drupalbook_api_key', t('API Key must start from "google".'));
  }
}

/**
 * Custom submit callback.
 */
function drupalbook_settings_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  // drupal_set_message is deprecated
  // drupal_set_message(t('An error occurred and processing did not complete.'));

  \Drupal::messenger()->addStatus(
    t(htmlentities('Insert API key in your  tag:  .'))
  );
}

これでフォーム API のレッスンは終わりますが、Form API の学習が終わるわけではありません。validate、submit、さまざまなフィールドの関数に何度も出会います。また、あるレッスンで、Form API を通じて AJAX と form_states をどう使うかも理解します。

コード例は github で見られます:
https://github.com/levmyshkin/drupalbook8