前回の記事で、カスタムテーブルの一覧表示を行いましたが、今回は、カスタムテーブルに対して、レコードの追加、編集、削除および表示を行います。
実行イメージ
今回作成したカスタムモジュールのサンプルは、こちらのリンクからご確認できます。
twigテンプレートを使用した表示
社員情報をtwigテンプレートを使用して表示します。
employee.routing.ymlに以下を記述します。(今回作成するカスタムモジュールのモジュール名は「employee」のため、ファイル名「employee.routing.yml」とします)
employee.view:
path: '/employee/view/{employee_id}'
defaults:
_controller: '\Drupal\employee\Controller\EmployeeViewController::view'
_title: 'Employee情報の表示'
requirements:
_permission: 'access content'
employee_id: \d{5}
Controllerに社員IDを引数として渡すために、pathに{employee_id}を記述しています。
引数の社員IDは、数字5桁として制限したいので、requirementsに、正規表現でemployee_idについて、「\d{5}」と記述します。
今回はtwigにデータを渡したいので、employee.moduleに以下のように記述します。変数名は、'employee'とします。
<?php
/**
* Implements hook_theme().
*/
function employee_theme($existing, $type, $theme, $path) {
return [
'employee_template' => [
'variables' => ['employee' => NULL],
],
];
}
twigテンプレートファイルは、モジュールの中にtemplatesディレクトリを作成して、その中にファイル名employee-template.html.twigとして作成します。このファイル名は、上記のemployee.modueの中で記述した'employee_template'のアンダースコアをハイフン「-」としたものとなります。
今回、以下のようなtwigテンプレートemployee-template.html.twigを作成します。
<p>社員情報を表示します。</p>
<div class="employee">
<table class="table table-hover">
<tbody>
<tr>
<td colspan="4" class="kana">{{ employee.kana }}</td>
</tr>
<tr>
<td colspan="4" class="name">{{ employee.name }}</td>
</tr>
<tr>
<th scope="row">社員ID</th>
<td colspan="3">{{ employee.employee_id }}</td>
</tr>
<tr>
<th scope="row">部門コード</th>
<td>{{ employee.dept_code }}</td>
<th scope="row">職位</th>
<td>{{ employee.position }}</td>
</tr>
<tr>
<th scope="row">電話番号</th>
<td colspan="3">{{ employee.telephone }}</td>
</tr>
<tr>
<th scope="row">メールアドレス</th>
<td colspan="3">{{ employee.mail }}</td>
</tr>
</tbody>
</table>
</div>
<a href="/ja/employee/list" class="link-btn">一覧へ戻る</a>
Controllerクラスとして、以下のようなファイルを作成し、employeeテーブルから社員IDが引数で渡された$employee_idに等しいデータを検索します。fetchAssoc()により、結果を連想配列として取得できるので、取得した結果をそのままtwigテンプレートへ渡します。
<?php
namespace Drupal\employee\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Database;
/**
* EmployeeViewControllerクラス
*/
class EmployeeViewController extends ControllerBase {
/**
* employeeデータの表示
*
* @return array
* Return markup array.
*/
public function view($employee_id) {
// employeeテーブルから社員IDが$employee_idに等しいデータをselect
$conn = Database::getConnection();
$query = $conn->select('employee', 'emp');
$query->condition('employee_id', $employee_id)->fields('emp');
// 連想配列として取得
$employee = $query->execute()->fetchAssoc();
// 'employee-template.html.twig'に変数$employeeを渡す
return [
'#theme' => 'employee_template',
'#employee' => $employee,
];
}
}
上記employee.routing.ymlに、「path: '/employee/view/{employee_id}'」と指定してあるので、社員ID「00001」の表示のために、/employee/view/00001にアクセスすると、以下のような画面が表示されます。
レコード追加用のフォームの作成
次に、社員のレコード追加のためのフォームを作成します。フォームの作成のために、DrupalのFormBaseクラスを継承したフォームクラスを作成します。フォームクラスでは、以下の3つのメソッドの実装が必須です。
- getFormId()
- buildForm()
- submitForm()
入力チェック等のバリデーションを行う場合は、以下のメソッドを実装します。
- validateForm()
以下のようにファイルEmployeeAddForm.phpを作成します。
<?php
/**
* @file
* Contains \Drupal\employee\Form\EmployeeAddForm.
*/
namespace Drupal\employee\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Url;
class EmployeeAddForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'employee_add_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['employee_id'] = array(
'#type' => 'textfield',
'#title' => $this->t('社員ID'),
'#required' => TRUE,
);
$form['dept_code'] = array(
'#type' => 'textfield',
'#title' => $this->t('部門コード'),
'#required' => TRUE,
);
$form['name'] = array(
'#type' => 'textfield',
'#title' => $this->t('名前'),
'#required' => TRUE,
);
$form['kana'] = array(
'#type' => 'textfield',
'#title' => $this->t('カナ'),
'#required' => TRUE,
);
$form['position'] = array(
'#type' => 'textfield',
'#title' => $this->t('職位'),
'#required' => TRUE,
);
$form['telephone'] = array(
'#type' => 'textfield',
'#title' => $this->t('電話')
);
$form['mail'] = array(
'#type' => 'textfield',
'#title' => $this->t('メール')
);
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('保存'),
'#button_type' => 'primary',
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// 入力された'employee_id'がすでに存在しているかを確認
$employee_id = $form_state->getValue('employee_id');
$conn = Database::getConnection();
$query = $conn->select('employee', 'emp');
$query->condition('employee_id', $employee_id)->fields('emp');;
$record = $query->execute()->fetchAssoc();
// エラーメッセージの出力
if ( 5 != strlen($form_state->getValue('employee_id')) ) {
$form_state->setErrorByName('employee_id', '社員IDは5文字で入力して下さい。');
} else if ( $record ) {
$form_state->setErrorByName('employee_id', '入力された社員IDはすでに存在しています:' . $employee_id);
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$fields['employee_id'] = $form_state->getValue('employee_id');
$fields['dept_code'] = $form_state->getValue('dept_code');
$fields['name'] = $form_state->getValue('name');
$fields['kana'] = $form_state->getValue('kana');
$fields['position'] = $form_state->getValue('position');
$fields['$telephone'] = $form_state->getValue('telephone');
$fields['$mail'] = $form_state->getValue('mail');
$query = \Drupal::database()->insert('employee');
$query->fields($fields)->execute();
drupal_set_message($this->t('保存しました: 社員ID ' . $fields['employee_id']));
// 一覧のルート名を指定してリダイレクト
$form_state->setRedirect('employee.list');
}
}
getFormId()では、Drupalが一意に識別できるように、ユニークなIDを返します。
buildForm()では、Drupalのレンダリング配列の形式で、入力用のテキストフィールドおよびsubmitボタンを記述しています。詳細はRender API overviewを参照して下さい。
サンプル用に、入力必須フィールドも設定しました。これに合わせて、employeeテーブルのNOT NULL成約も変更しています。
validateForm()では、入力チェックを行なっています。今回はサンプルとして、以下のチェックを行なっています。
- employee_idは5文字
- employee_idは重複不可
submitForm()では、渡ってきた値でemployeeテーブルにインサートを行なっています。そして、画面にメッセージを表示し、一覧画面へリダイレクトしています。
employee.routing.ymlには以下を追記します。
employee.add:
path: '/employee/add'
defaults:
_form: '\Drupal\employee\Form\EmployeeAddForm'
_title: 'Employeeの追加'
requirements:
_permission: 'access content'
_formキーには、上記のEmployeeAddFormを指定します。
上記で指定したパス'/employee/add'にアクセスすると、以下のような画面が表示されます。
編集用フォームの作成
次に、社員情報の編集のためのフォームを作成します。上記と同様、フォームの作成のために、DrupalのFormBaseクラスを継承したフォームクラスを作成します。
以下のようにファイルEmployeeUpdateForm.phpを作成します。
<?php
/**
* @file
* Contains \Drupal\employee\Form\EmployeeUpdateForm.
*/
namespace Drupal\employee\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Url;
class EmployeeUpdateForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'employee_update_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $employee_id = NULL) {
// 引数で渡された$employee_idから社員情報を取得
$conn = Database::getConnection();
$query = $conn->select('employee', 'emp');
$query->condition('employee_id', $employee_id)->fields('emp');;
$record = $query->execute()->fetchAssoc();
$dept_code = $record['dept_code'];
$name = $record['name'];
$kana = $record['kana'];
$position = $record['position'];
$telephone = $record['telephone'];
$mail = $record['mail'];
$form['employee_id'] = array(
'#type' => 'textfield',
'#default_value' => $employee_id,
'#title' => $this->t('社員ID'),
'#disabled' => TRUE
);
$form['dept_code'] = array(
'#type' => 'textfield',
'#default_value' => $dept_code,
'#title' => $this->t('部門コード'),
'#required' => TRUE,
);
$form['name'] = array(
'#type' => 'textfield',
'#default_value' => $name,
'#title' => $this->t('名前'),
'#required' => TRUE,
);
$form['kana'] = array(
'#type' => 'textfield',
'#default_value' => $kana,
'#title' => $this->t('カナ'),
'#required' => TRUE,
);
$form['position'] = array(
'#type' => 'textfield',
'#default_value' => $position,
'#title' => $this->t('職位'),
'#required' => TRUE,
);
$form['telephone'] = array(
'#type' => 'textfield',
'#default_value' => $telephone,
'#title' => $this->t('電話')
);
$form['mail'] = array(
'#type' => 'textfield',
'#default_value' => $mail,
'#title' => $this->t('メール')
);
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('更新'),
'#button_type' => 'primary',
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$fields['employee_id'] = $form_state->getValue('employee_id');
$fields['dept_code'] = $form_state->getValue('dept_code');
$fields['name'] = $form_state->getValue('name');
$fields['kana'] = $form_state->getValue('kana');
$fields['position'] = $form_state->getValue('position');
$fields['$telephone'] = $form_state->getValue('telephone');
$fields['$mail'] = $form_state->getValue('mail');
$query = \Drupal::database()->update('employee');
$query->fields($fields);
$query->condition('employee_id', $fields['employee_id']);
$result = $query->execute();
drupal_set_message($this->t('更新しました: 社員ID ' . $employee_id));
// 一覧のルート名を指定してリダイレクト
$form_state->setRedirectUrl(Url::fromRoute('employee.list'));
}
}
編集画面では、元の値を表示するために、buildForm()で、引数$employee_idを受け取り、employeeテーブルからselectした結果をそれぞれのフィールドに#default_valueとして表示しています。Primary Keyの社員IDについては、編集不可とするために、#disabledをTRUEにセットしています。
submitForm()では、渡ってきた値でemployeeテーブルのアップデートを行なっています。そして、画面にメッセージを表示し、一覧画面へリダイレクトしています。
employee.routing.ymlには以下を追記します。
employee.update:
path: '/employee/update/{employee_id}'
defaults:
_form: '\Drupal\employee\Form\EmployeeUpdateForm'
_title: 'Employeeの更新'
requirements:
_permission: 'access content'
employee_id: \d{5}
_formキーには、上記のEmployeeUpdateFormを指定します。
パラメータとして、{employee_id}をpathに設定します。パス'/employee/update/00001'にアクセスすると、以下のような画面が表示されます。
削除用フォームの作成
次に、社員情報の削除のためのフォームを作成します。今回は、ユーザー確認用のフォームとして使用されるConfirmFormBaseクラスを継承したフォームクラスを作成します。ConfirmFormBaseクラスを継承したクラスでは、以下の4つのメソッドの実装が必須です。
- getFormId()
- getQuestion()
- getCancelUrl
- submitForm()
その他、実装することがオプションのメソッドも含むConfirmFormInterfaceについては、ConfirmFormInterface API documentを参照して下さい。
以下のようにファイルEmployeeDeleteForm.phpを作成します。
<?php
namespace Drupal\employee\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Url;
/**
* Class EmployeetDeleteForm.
*
* @package Drupal\employee\Form
*/
class EmployeeDeleteForm extends ConfirmFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'employee_delete_form';
}
protected $employee_id;
/**
* {@inheritdoc}
*/
public function getQuestion() {
return t('社員ID:%employee_id のデータを削除します。よろしいですか?', [
'%employee_id' => $this->employee_id,
]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('employee.list');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('削除されたデータは元に戻すことはできません。');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return t('削除');
}
/**
* {@inheritdoc}
*/
public function getCancelText() {
return t('キャンセル');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $employee_id = NULL) {
// 引数で渡ってきた$employee_idをインスタンス変数にセット
$this->employee_id = $employee_id;
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// レコードの削除
$query = \Drupal::database();
$query->delete('employee')->condition('employee_id', $this->employee_id)->execute();
drupal_set_message($this->t('データが削除されました。社員ID:' . $this->employee_id));
// 一覧のルート名を指定してリダイレクト
$form_state->setRedirect('employee.list');
}
}
getQuestion()で削除対象の社員IDを表示するために、buildForm()では、引数として渡ってきた$employee_idをインスタンス変数に保持します。
submitForm()では、渡ってきた$employee_idのレコードをemployeeテーブルから削除します。そして、画面にメッセージを表示し、一覧画面へリダイレクトしています。
employee.routing.ymlには以下を追記します。
employee.delete:
path: '/employee/delete/{employee_id}'
defaults:
_form: '\Drupal\employee\Form\EmployeeDeleteForm'
_title: 'Employeeの削除'
requirements:
_permission: 'access content'
employee_id: \d{5}
パス'/employee/delete/00001'にアクセスすると、以下のような画面が表示されます。
一覧表示の変更
上記で作成した表示/追加/編集/削除へのリンクを一覧に追加します。
EmployeeListController.phpを以下のように記述します。
<?php
namespace Drupal\employee\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Url;
use Drupal\Core\Link;
/**
* EmployeeListControllerクラス
*/
class EmployeeListController extends ControllerBase {
/**
* employeeテーブルからのリストの取得
*/
function queryEmployeeList($header, $employee_id, $dept_code) {
// employeeテーブルのqueryを行う
$query = \Drupal::database()->select('employee', 'emp');
$query->fields('emp', ['employee_id','dept_code','name','kana','position','telephone','mail']);
// ヘッダーにソート項目を指定
$table_sort = $query->extend('Drupal\Core\Database\Query\TableSortExtender')->orderByHeader($header);
// ページャーを使用。表示件数:5行
$pager = $table_sort->extend('Drupal\Core\Database\Query\PagerSelectExtender')->limit(5);
// フィルターとして社員IDが入力された場合は、検索条件にemployee_idを追加する
if ( !empty($employee_id) ) {
$pager->condition('employee_id', $employee_id);
}
// フィルターとして部門コードが入力された場合は、検索条件にLIKEでdept_codeを追加する
if ( !empty($dept_code) ) {
$pager->condition('dept_code', '%' . $dept_code . '%', 'LIKE');
}
// 検索の実行
$results = $pager->execute()->fetchAll();
// 出力用の配列を初期化
$output = array();
// query結果を出力用の配列にセット
foreach ($results as $row) {
// 表示画面へのリンク
$view = Url::fromRoute('employee.view',['employee_id' => $row->employee_id] );
$name_link = \Drupal::l($row->name, $view);
// 編集画面、削除画面へのリンク
$edit = Url::fromRoute('employee.update',['employee_id' => $row->employee_id] );
$delete = Url::fromRoute('employee.delete',['employee_id' => $row->employee_id] );
// Drupal::l はDeprecated のため、Link::fromTextAndUrlを使用するべきのようだが、以下のコードだとエラーになるため
// 今回は、Drupal::l を使用する
// $edit_link = Link::fromTextAndUrl('編集', $edit);
// $delete_link = Link::fromTextAndUrl('削除', $delete);
$edit_link = \Drupal::l('編集', $edit);
$delete_link = \Drupal::l('削除', $delete);
$opeLink = t('@linkEdit @linkDelete', array('@linkEdit' => $edit_link, '@linkDelete' => $delete_link));
$output[] = [
'employee_id' => $row->employee_id,
'dept_code' => $row->dept_code,
'name' => $name_link,
'kana' => $row->kana,
'position' => $row->position,
'telephone' => $row->telephone,
'mail' => $row->mail,
'opt' => $opeLink,
];
}
return $output;
}
/**
* employeeテーブルの一覧表示
*
* @return array
* Return markup array.
*/
public function list() {
// フィルターFormからのパラメータの取得
$employee_id = \Drupal::request()->query->get('employee_id');
$dept_code = \Drupal::request()->query->get('dept_code');
// ゼロパディング
if ( !empty($employee_id) ) {
$employee_id = sprintf('%05d',$employee_id);
}
//filter controllerの読み込み
$form['form'] = $this->formBuilder()->getForm('Drupal\employee\Form\EmployeeFilterForm');
// 社員の追加
$form['employee'] = [
'#title' => $this->t('社員の追加'),
'#type' => 'link',
'#url' => Url::fromRoute('employee.add'),
'#attributes' => [
'class' => ['link-btn'],
]
];
// 'field'で指定された項目がソート項目となる
$header = [
[ 'data' => '社員ID', 'field' => 'employee_id', 'sort' => 'asc' ],
[ 'data' => '部門コード', 'field' => 'dept_code' ],
[ 'data' => '名前', ],
[ 'data' => 'カナ', 'field' => 'kana' ],
[ 'data' => '職位', ],
[ 'data' => '電話', ],
[ 'data' => 'メール', ],
[ 'data' => '操作', ],
];
$output = $this->queryEmployeeList($header, $employee_id, $dept_code);
$form['table'] = [
'#type' => 'table',
'#header' => $header,
'#rows' => $output,
'#empty' => 'データがありません',
];
// pagerの表示
$form['pager'] = [
'#type' => 'pager'
];
return $form;
}
}
一覧画面に、表示画面、編集画面、削除画面へのリンクを追加するために、queryEmployeeList()に、リンクの記述を行っています。
編集画面、削除画面へのリンクは、「操作」列に表示するために、list()の中で、headerに「操作」列を追加しています。
追加画面へのリンクボタンもlist()に記述を行なっています。ボタンのためのcssを設定するために#attributesでクラスの設定を行なっています。
employee.routing.ymlには以下のように記述しています。
employee.list:
path: '/employee/list'
defaults:
_controller: '\Drupal\employee\Controller\EmployeeListController::list'
_title: 'Employeeの一覧'
requirements:
_permission: 'access content'
options:
no_cache: 'TRUE'
ここでは、「no_cache: 'TRUE'」の指定を追加し、このページはキャッシュしないようにしています。これを指定しない状態では、編集後に一覧に戻ると編集結果が反映されていなかったために、追加しています。
フィルターの追加
上記のEmployeeListController.phpのqueryEmployeeList()内で、検索条件にemployee_idおよびdept_codeが追加されていますが、このフォームの表示のために、以下のような EmployeeFilterForm.phpを記述します。
<?php
/**
* @file
* Contains \Drupal\employee\Form\EmployeeFilterForm.
*/
namespace Drupal\employee\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
class EmployeeFilterForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'employee_filter_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['filters']['employee_id'] = [
'#title' => '社員ID',
'#type' => 'search'
];
$form['filters']['dept_code'] = [
'#title' => '部門コード',
'#type' => 'search'
];
$form['filters']['actions'] = [
'#type' => 'actions'
];
$form['filters']['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('絞り込み')
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$employee_id = $form_state->getValue('employee_id');
$dept_code = $form_state->getValue('dept_code');
$url = Url::fromRoute('employee.list')
->setRouteParameters(array('employee_id'=>$employee_id,'dept_code'=>$dept_code));
// パラメータをセットして一覧へリダイレクト
$form_state->setRedirectUrl($url);
}
}
ファイル一覧
以上で、今回のカスタムモジュールは完成です。ファイル一覧は以下となります。
modules/
└── custom/
└── employee/
├── employee.info.yml
├── employee.module
├── employee.routing.yml
├── src/
│ ├── Controller/
│ │ ├── EmployeeListController.php
│ │ └── EmployeeViewController.php
│ └── Form/
│ ├── EmployeeAddForm.php
│ ├── EmployeeDeleteForm.php
│ ├── EmployeeFilterForm.php
│ └── EmployeeUpdateForm.php
└── templates/
└── employee-template.html.twig
ソースファイル
上記のemployeeモジュールのソースファイルを以下に置きました。
コメント
コメントを追加