カテゴリー
コンピューター

CakePHP2 でユーザ情報の更新ページみたいなのを作るときのメモ♪

ウェブサービスを利用しているとプロフィールページががあって、メールアドレスや、住所とか、変更できるようになっています。これを CakePHP2 で作るにはどうしたらよいか?なにが問題となったか?ポイントか?メモ程度ですが残しておきます。

メモるべきポイント

  • 同一内容を入力させるフォームとバリデーションチェックを行う。たとえば、メールアドレスとか、パスワードを修正するために、「確認のためもう一度入力してください」といったフォーム。
  • パスワードを更新する。パスワードのフォームは最初空で表示し、入力があった場合のみ、データベースのレコードを更新する。
  • 一度の post で複数テーブルの情報のバリデーションチェックを行い、情報の更新を行う。

具体的に書いていきます。

同一内容を入力させるフォームとバリデーションチェックを行う。

参考になったページはこちらです。こちらをベースに、独自に修正を加えました。すばらしいページです、ありがとうございます。

わたくしの場合をパスワードの入力フォームで説明していきます。

まずビューですが、

submit してアクションに post したら、User.new_password1、User.new_password2 が送られるようにしました。

<?php echo $this->Form->input('User.new_password1', array('type' => 'password')); ?>
<?php echo $this->Form->input('User.new_password2', array('type' => 'password')); ?>

これら2つが同一かどうかバリデーションチェックすることになります。

続いてモデル。

どのモデルでも使えるようにしたいので、バリデーションチェック用の関数を AppModel.php に追加しました。

app/Model/AppModel.php

<?php
class AppModel extends Model {

/**
 * 2つのフィールドの入力文字列が同じであれば true を、異なれば false を返却します。
 * @param array $valid_field1 バリデーションルールのフィールド名がキー、入力値が値の配列
 * @param string $valid_field2 比較対象のフィールド名
 * @return boolean 2つのフィールドの入力文字列が同じであれば true を、異なれば false
 */
	function checkCompare($valid_field1 , $valid_field2){

		// フィールド名とフォームへの入力値の配列から、キーであるフィールド名を取得
		$fieldname = key($valid_field1);

		// 2つのフィールドの入力値を比較
		if($this->data[$this->name][$fieldname] === $this->data[$this->name][$valid_field2]){
			return true;
		}
		return false;
	}
}
?>

これをバリデーションルールで使用します。User モデルの、new_password2 に same という名前で追加しました。次のソースの32~35行目にあたります。

ちょっと変更して、new_password1 だけに追加したり、両方に追加しても問題ありません。バリデーションエラーとなったときに、どちらに表示されるか、両方に表示されるかが変わるだけです。また、new_password1 に追加する際は、rule に指定するフィールド名は new_password2 となることにご注意です!

また、rule への指定の仕方ですが、配列とします。1番目の要素に checkCompare 関数を、2番目に関数の第2引数となるフィールドを指定します。

checkCompare 関数の第1引数は指定しません。自動で第1引数にルールを設定したフィールド名と入力フォームの値の配列が、第2引数に指定したフィールドに対応する入力フォームの入力値の文字列が渡ります。

では、バリデーションルールのソースです。app/Model/User.php

/**
 * Validation rules
 *
 * @var array
 */
	public $validate = array(
		'username' => array(
			'notempty' => array(
				'rule' => array('notempty'),
			),
		),
		'password' => array(
			'notempty' => array(
				'rule' => array('notempty'),
			),
			'alphaNumeric' => array(
				'rule' => '/^[a-z\d]*$/i',
				'message' => '半角英数で入力してください',
			),
		),
		'new_password1' => array(
			'alphaNumeric' => array(
				'rule' => '/^[a-z\d]*$/i',
				'message' => '半角英数で入力してください',
			),
		),
		'new_password2' => array(
			'alphaNumeric' => array(
				'rule' => '/^[a-z\d]*$/i',
				'message' => '半角英数で入力してください',
			),
			'same' => array(
				'rule' => array('checkCompare', 'new_password1'),
				'message' => '上下で同じ内容を入力してください',
			),
		),
		'group_id' => array(
			'numeric' => array(
				'rule' => array('numeric'),
			),
		),
	);

パスワードを更新する。パスワードのフォームは最初空で表示し、入力があった場合のみ、データベースのレコードを更新する。

こちらのページが大変参考になりました。特に、unset してなかったことにしてバリデーションチェック対象からはずす、そのとき required は false にする、という点がすばらしかったです。ありがとうございます。

前提

続きで作っていくという前提です。また、パスワードを保存するテーブルのカラムは users の password としています。CakePHP モデルクラスにはもちろんこれに対応する password フィールドがありますが、今回の目的のために new_password1、new_password2 を追加しています。

ビューですることは実は結構簡単

そうすると、パスワードのフォームを最初空で表示するというのは簡単です。何もしなくても、User.password に対応するフォームは用意しないので、表示のしようがありません。

かわりに User.new_password1、User.new_password2 のフォームを用意しますが、これに対応するテーブルとカラムがないため、初期表示画面には何も表示されないのです。

初期表示画面、は。・・・そうです、更新処理が完了してから、一工夫必要です。更新処理後にはまだ User.new_password1、User.new_password2 に入力値がそのまま残っているので、消す必要があります。消し方も、入力内容を消すだけの方法と、配列のキーも値も消してしまう方法があります。今回、後者の方法を採用しました。unset を使ったのです。これは、下のソースの29、30行目にあたります。

バリデーションも簡単

次に、post したときどうなるでしょうか?バリデーションは問題ありませんね。上述の方法でクリアできます。バリデーションルールの required が false (デフォルトの値です)であればルールを定義した項目の入力値がなくてもチェックしないためエラーとなりません。

ですので、User.password、User.new_password1、User.new_password2 のバリデーションルールの required が false となっているか、required の項目自体が無ければ OK です。

テーブルへの更新処理はひとひねり必要ですよ!

では、更新はどうでしょう?new_password1、new_password2 というカラムはテーブルにありませんので、当然レコードの update はできません。そこで、save などの関数に渡す引数に、User.password を用意してやり、バリデーションが OK となった値をセットする必要があります。下のソースでいえば、18~22行目にあたります。

・・・と思ったのですが、バリデーション OK となってからの値でなくても、チェックの前にあらかじめセットしてあげて、1回のバリデーションでpassword、new_password1、new_password2 全部を一気にチェックする方法で問題ありませんでした。

ソースはこうなりました

app/Controller/UsersController.php

/**
 * edit method
 *
 * @throws NotFoundException
 * @param string $id
 * @return void
 */
	public function edit($id = null) {
		//pr('★START');
		//pr($this->request->data);

		$this->User->id = $id;
		if (!$this->User->exists()) {
			throw new NotFoundException(__('Invalid user'));
		}
		if ($this->request->is('post') || $this->request->is('put')) {
			// 更新処理時
			// User.new_password1、User.new_password2 ともに入力の場合はパスワードの更新を行う
			if ($this->request->data['User']['new_password1'] !== '' && $this->request->data['User']['new_password2'] !== '') {
				// User.password が存在しないので、リクエストの配列に追加
				$this->request->data['User']['password'] = $this->request->data['User']['new_password1'];
			}

			//pr('★UPDATE直前');
			//pr($this->request->data);
			if ($this->User->save($this->request->data)) {
				// 更新成功
				$this->Session->setFlash(__('The user has been saved'));
				// 2つのパスワード入力フォームを空で表示するために、配列から破棄
				unset($this->request->data['User']['new_password1'], $this->request->data['User']['new_password2']);
			} else {
				// 更新失敗
				$this->Session->setFlash(__('The user could not be saved. Please, try again.'));
			}
		} else {
			// 初期表示時
			$this->request->data = $this->User->read(null, $id);
		}
		$groups = $this->User->Group->find('list');
		$this->set(compact('groups'));

		//pr('★END');
		//pr($this->request->data);
	}

重要なポイントはすでに述べました。ここでは補足的に、プログラミングをしていて便利だったことをメモしておきます。アクションに入った直後、更新処理直前、アクション終了直前に、リクエスト情報を pr 関数で出力しておくと、とても見通しがよくなります。

注意点としては、リクエスト情報が配列のためか、文字列の後につなげて出力すると、配列の中身が表示されなくてナンデ???となります。

app/Model/User.php

	public function beforeSave() {
		if (isset($this->data['User']['password'])) {
			$this->data['User']['password'] = AuthComponent::password($this->data['User']['password']);
		}
		return true;
	}

これは補足的に載せました。beforeSave は保存処理の前、どこかは実はよく知りませんが、に動きます。

User.password は保存の直前に暗号化するようにしますけれども、保存の際にパスワードは更新しない場合、2行目4行目でハイライトしたように判定をしないと配列に要素が無いためにエラーとなります。

実際、エラーとなりましたので判定処理を追加しました。

一度の post で複数テーブルの情報のバリデーションチェックを行い、情報の更新を行う。

これはおまけ的です。普通に saveAll を使うだけです。記憶がおぼろげですが、save でもできるような気がします。

ただし、バリデーションだけを行いたいときは、saveAll を使うこと、そして saveAll の第2引数に array(‘validate’ => ‘only’) をつけることがポイントです。

saveAll ナンデ??? validate じゃないの?などと思うのですけれど、公式に掲載されている情報なのでこれでよいのでしょう!

長々と書いてしまいました。以上です♪

「CakePHP2 でユーザ情報の更新ページみたいなのを作るときのメモ♪」への1件の返信

コメントを残す