c. データベース設計演習(3/4)
2017年度資料
先週のブックマークチュートリアルの続き。先回は Tag 検索機能を実装するところまで進めた。
今回は、ブックマークチュートリアルのPart2 ユーザ認証、ログイン画面の作成に取り掛かる。
第10回・第11回の演習でトラブル等で、今回の作業を進められない受講生は↓を参照。
前回の作業内容を完了した例をレポートフォルダに入れておく。
CakePHPの作業が完了していない受講生は、こちらを利用して今回の作業を進めること。
bookmarkerデータベースの作成と、php.ini の設定変更は 第10回の資料を参考に進める。
php.ini の設定変更後はWebサーバの再起動が必要。
bookmarks users tags に動作確認用のデータを数件登録しておく。
作業を開始する前に、
http://localhost/bookmarker/users/ にアクセスして、登録済みのメールアドレスとパスワードを確認する。
パスワードがハッシュ化されている場合、先回設定したパスワードを忘れている場合は、パスワードを
編集して設定しなおす。
作業手順は、↓のリンク先を参照のこと。
https://book.cakephp.org/3.0/ja/tutorials-and-examples/bookmarks/part-two.html
以下に、演習室PCでの作業の要点・概略を記す。
赤字の部分を追加
// src/Controller/AppController.php の中で
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'unauthorizedRedirect' => $this->referer() // 未認証時、元のページを返します。
]);
// PagesController が動作し続けるように
// display アクションを許可
$this->Auth->allow(['display']);
}
http://localhost/bookmarker/bookmarks/
login() がないのでエラーが表示される
赤字の部分を追加
// src/Controller/UsersController.php の中で
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error('あなたのユーザー名またはパスワードが不正です。');
}
}
http://localhost/bookmarker/bookmarks/
login.ctp がないのでエラーが表示される
src/Template/Users/login.ctp を新規作成(Usersフォルダを 右クリック→新しいファイル)。
以下を記述。
<?= の記号は、PHP のコード省略記法。 <?php echo $変数名 ?>
<h1>Login</h1>
<?= $this->Form->create() ?>
<?= $this->Form->control('email') ?>
<?= $this->Form->control('password') ?>
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>
http://localhost/bookmarker/bookmarks/
表示されたページのソースコードをブラウザで確認。
UsersController に以下のコードを追加。
public function initialize()
{
parent::initialize();
$this->Auth->allow(['logout']);
}
public function logout()
{
$this->Flash->success('ログアウトします。');
return $this->redirect($this->Auth->logout());
}
http://localhost/bookmarker/users/logout
ログイン画面に戻る。
ユーザ登録URLにアクセスする。
http://localhost/bookmarker/users/add
ログインページに切り替わり、ユーザ登録が出来ない状態になる。
UsersController を修正。
public function initialize()
{
parent::initialize();
// 許可するアクション一覧に add を追加
$this->Auth->allow(['logout', 'add']);
}
http://localhost/bookmarker/users/add
ユーザ登録が出来るようになっている。test用のユーザを追加で登録する。
e-mail test@nagoya-bunri.ac.jp
password test
Users/add.ctp にある、ユーザ一覧表示 List Users へのリンクはユーザ登録時にはアクセスできないので消してもよい。
ログアウト用のURL http://localhost/bookmarker/users/logout にアクセスして、ログアウトする。
先ほど登録した test ユーザでログインしなおす。
AppController に以下を追加します。
public function isAuthorized($user)
{
return false;
}
AppController のinitialize() メソッドを修正。
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authorize'=> 'Controller',//この行を追加
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'unauthorizedRedirect' => $this->referer()
]);
// PagesController が動作し続けるように
// display アクションを許可
$this->Auth->allow(['display']);
}
http://localhost/bookmarker/bookmarks
↑のコード修正で全機能 bookmarksコントローラの index view add delete などへのアクセスを一律に禁止したのでログイン画面に戻される。
BookmarksControllerに以下を追加
public function isAuthorized($user)
{
$action = $this->request->getParam('action');
// add と index アクションは常に許可します。
if (in_array($action, ['index', 'add', 'tags'])) {
return true;
}
// その他のすべてのアクションは、id を必要とします。
if (!$this->request->getParam('pass.0')) {
return false;
}
// ブックマークが現在のユーザーに属するかどうかをチェック
$id = $this->request->getParam('pass.0');
$bookmark = $this->Bookmarks->get($id);
if ($bookmark->user_id == $user['id']) {
return true;
}
return parent::isAuthorized($user);
}
CakePHP のコードについて:
$this->request->getParam('action') は、サーバにアクセスするURLのアクション部分(コントローラーの下の階層)を読み取る。アクションには、コントローラのphpコードの function が対応する。
$this->request->getParam('pass') は、URLのアクションの下のパスセグメント( / で区切ったキーワード)を数字をインデックスとする配列で呼べるようにする。'pass.0' とインデックスを指定すると配列の先頭要素、つまりパスセグメントの先頭要素を取り出せる。
↑のコードでは、index add tagsのアクション以外の全てのアクション、view や add などは、URLにidが含まれているかチェックしている。
if ( $bookmark->user_id == $user['id'] の部分で
edit や delete など 操作にユーザ id が必要で、一律に許可がされていない操作(addやindexは許可済み)の
権限チェックをする。
bookmarks のユーザid と ログインユーザのid が一致 → 操作許可 自分のブックマークは操作できる
bookmarks のユーザid と ログインユーザのid が不一致 → 操作不許可 他人のブックマークは操作できない
http://localhost/bookmarker/bookmarks
↑のページから、ブックマークの 一覧表示 と 追加 が出来るようになる。他人のidによるブックマークの編集や削除は不能で、自分のidによるブックマークは編集・削除可能。
6月28日(水)の授業はここまで進んだ。
時間に余裕がない場合、ここまでの内容で課題提出としてOK
↓この作業は不要
// src/Template/Layout/default.ctp の中の
// 既存のフラッシュメッセージの下で
<?= $this->Flash->render() ?>
src/Template/Bookmarks/add.ctp から1行削除
echo $this->Form->control('user_id', ['options' => $users]);
src/Controller/BookmarksController.php の add() アクションを修正。
public function add()
{
$bookmark = $this->Bookmarks->newEntity();
if ($this->request->is('post')) {
$bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->getData());
$bookmark->user_id = $this->Auth->user('id'); // この行を追加
if ($this->Bookmarks->save($bookmark)) {
$this->Flash->success(__('The bookmark has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The bookmark could not be saved. Please, try again.'));
}
$users = $this->Bookmarks->Users->find('list', ['limit' => 200]); //この行を削除
$tags = $this->Bookmarks->Tags->find('list', ['limit' => 200]);
$this->set(compact('bookmark', 'users', 'tags')); // usersを削除
$this->set('_serialize', ['bookmark']);
}
http://localhost/bookmarker/bookmarks/add
で動作確認。
ログイン後、ユーザはブックマーク登録の際に、自分のユーザidを指定しなくても登録できるようになった。
自分のユーザidを使って登録するためのコード $bookmark->user_id = $this->Auth->user('id'); の効果。
以下、Webページを参考に作業を続ける。
課題提出
bookmarker のWebサービスの画面で、
・ログイン用画面
・ログイン後の画面 ※ ユーザー認証の状況が分かるように、他のユーザのブックマークを編集したり、削除を試みる。→ 認証エラー が表示される。 この状態でスクリーンショットを撮っておく。
↑の2つの画像を、ワープロファイルに貼り付け、ファイルをWebclassの 第C回課題 にアップロードする。
例)
ログアウト後に表示されるログイン画面
他のユーザのブックマークを編集しようとして、認証エラーが出ている画面
応用
Template の 各種 ctp ファイルを編集:
・ログアウト用のリンクを表示する
・ユーザ登録画面の List Users のリンクなど、不要なリンクを削除する
・ログイン状態の各種画面に、ログインユーザーのIDを表示するようにする。$this->Auth->user('id') を利用。
ルーティングの変更、トップページのURLを設定:
・config/routes.php の / の scope を編集し、 bookmarker/ にアクセスすると bookmarksコントローラの index アクションを実行するように設定する。
先回作成した、 tag検索画面を利用するための 画面を作成:
・検索tag入力用の画面を login.ctp と同様の要領で作成する。
検索tagを文字で入力するFormを作成し、Template/Bookmarksに配置する。
入力されたtagは、Bookmarksコントローラのtagsアクションでpostリクエストで処理されるようにコードを修正する。
など、Webサービスとしてページの体裁を整えてみる。
↑
上記の応用例を実装した結果を、レポートフォルダに、
bookmarker_20170630BookmarkTutorialComplete_Custom
として入れておいたので参考にしてよい。
作業例)
Template/Layouts/default.ctp を修正、Title を修正。
Template/Layouts/default.ctp を修正、API Documents などへのリンクを削除。
Template/Bookmarks/index.ctp を修正、左サイドメニューから、ユーザ登録やタグ登録のリンクを削除。
Template/Bookmarks/index.ctp を修正、左サイドメニューに、[Tag Search] と [Logout]のリンクを追加。
Template/Bookmarks/index.ctp を修正、ブックマークリスト表示の項目から、User id を削除。
config/app.php を修正、デバッグモードをオフにして、デバッグ用のアイコンを非表示。
config/routes.php を修正、 http://localhost/bookmarker/ をサイトのトップページに設定。( / へのアクセスを bookmarsコントローラーの index アクションにルート設定)
Template/Bookmarks/tags.ctp を修正、トップページに戻るリンクを配置。
Template/Users/login.ctp を修正、新規ユーザ登録ページへのリンクを配置。
タグ検索用の画面を作成。(レポートフォルダに置いたコードは、複数タグの検索に非対応)
bookmarksコントローラに search アクションを作成。
search.ctp を作成。
検索タグ入力フォームを作成。
検索ボタンを押すと bookmarks の tags を呼ぶように設定する。
bookmarks の tags を編集し、 post リクエスト時は getData()で入力されたタグを処理するように変更。
bookmarkチュートリアル part2の作業を完了すると↓のように、ブックマーク登録画面が変更される。
ブックマークの登録時には、ユーザIDの選択は不要。タグ入力は、選択式から、テキスト入力式に変更。
複数のタグは , 区切りで入力する。
データベースには変更は無い。(タグ指定欄が繰り返し項目になっているが、そこはコードで対応)
関連タグの修正: コードでブックマークに関連した複数のタグを検索し、CSV形式に文字列処理して表示している。
関連タグの設定: CSV形式の要素を1件ずつ登録処理する。既存のタグが指定されていた場合は、Tagsテーブルでそのidを調べて設定。
新規タグの場合は、Tagsテーブルに新規レコードとして設定し、そのidをブックマークの関連テーブルに記録。
2016年度資料
データの登録・修正・削除をPHPで実装する。
キーワード: CRUD HTMLフォーム
Blogの記事に対して、コメントを付けることができるようになったら完成。
準備: 次の2点は設定済み。もし設定が抜けていたら、設定しておく。
■デバッグモード を 開発中は ONにしておくことを推奨。 Config/core.php の記述で、Level 0 -> Level 2 に戻す。
■文字化けの対応:
CakePHPの設定ファイル編集:
D:\XAMPP\htdocs\blog学籍番号\app\Config の database.php
class DATABASE_CONFIG {
public $default = array(
'datasource' => 'Database/Mysql',
'persistent' => false,
'host' => '192.168.111.??',
'login' => 'cakephp',
'password' => 'cakephp',
'database' => 'blog',
'prefix' => '',
'encoding' => 'utf8',
);
上記の様に、文字コードを utf8 に設定する。
■前回の内容:
プログラムの入力やファイル名、保存場所等のミスを修正する。先回の講義資料に、プログラムが掲載されているので参照のこと。
■ 記事の編集機能 と ビュー
PostsController.php に以下を追加。
public function edit($id = null) {
$this->Post->id = $id;
if($this->request->is('get')){
$this->request->data = $this->Post->read();
} else {
if($this->Post->save($this->request->data)) {
$this->Session->setFlash('Success!');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash('failed!');
}
}
}
edit.ctp を作成。
<h2>Edit Post</h2>
<?php
echo $this->Form->create('Post',array('action'=>'edit'));
echo $this->Form->input('title');
echo $this->Form->input('body',array('rows'=>3));
echo $this->Form->end('保存');
index.ctp の記事一覧表示のループに、編集用のリンクを追加
※↓このコードでは、記事タイトルのリンクと 編集 リンクがつながって分かりにくいので、適当に隙間を空けるなどで対応してみる。
<?php echo $this->Html->link('編集','/posts/edit/'.$post['Post']['id']); ?>
編集の動作確認。
編集ページ
■ 記事の削除
PostsController.php
public function delete($id = null) {
if($this->request->is('get')){
throw new MethodNotAllowedException();
}
if($this->Post->delete($id)) {
$this->Session->setFlash('Deleted!');
$this->redirect(array('action' => 'index'));
}
}
index.ctp の記事一覧表示のループに、削除用のリンクを追加
<?php echo $this->Form->postlink('削除','/posts/delete/'.$post['Post']['id'],array('confirm'=>'削除しますか?')); ?>
動作確認
削除メッセージの表示例
■ コメント機能
blog データベースに commnets テーブルを追加。
create table comments (
id int not null auto_increment primary key,
commenter varchar(255),
body text,
post_id int not null,
created datetime default null,
modified datetime default null
);
コメントテーブルにデータを追加。postsテーブルに存在する idを、post_id と指定すること。※cakePHPの命名規約に従う。テーブル名は複数形、フィールド名は単数形。
■ アソシエーションの設定
posts テーブルと comments テーブルの関係を設定する。
1つの記事に複数のコメントが付くので、1対多の関係になっている。これを Model に記述を加え設定する。
Comment.php
<?php
class Comment extends AppModel {
public $belongsTo = 'Post';
}
?>
Post.php
<?php
class Post extends AppModel {
public $hasMany = 'Comment';
}
?>
view.ctp に以下を追加
<h2>コメント</h2>
<ul>
<?php foreach ($post['Comment'] as $comment): ?>
<li><?php echo h($comment['body']) ?> by <?php echo h($comment['commenter']); ?></li>
<?php endforeach; ?>
</ul>
↑のコードの解説
view.ctp のコードはPostsController.php の view メソッドでPostモデルの検索結果の表示に使用されています。
【PostsController.phpの一部再掲】
public function view($id = null) {
$this->Post->id = $id;
$this->set('post', $this->Post->read());
}
↑で、URLの末尾のidの記事を検索した結果が変数 $post に代入された状態で、view.ctp が動きます。
$post は配列で、
$post['Post']['title'] や $post['Post']['body']
で、検索結果の記事の タイトル(title)と 本文(body) を参照できます。
Postモデルにアソシエーション、hasMany を設定したことにより、
$post['Comment']
に、commentsテーブルのpost_idフィールド と postsテーブルのid フィールドの値が一致するすべてのコメントが代入されます。
$post は配列であり、その要素、 $postt['Comment']も配列です。(配列の中に配列が入っていいる)
最終的に、$postt['Comment']の中の各コメントを foreach でループ処理して表示しています。
cakePHP2系 を授業で扱っています。
cakePHP3系では仕様が変更されているので、自分で調べたコードを適用する際は要注意。
※ CakePHPのアソシエーションについては公式サイトの説明を参照。
http://localhost/blog学籍番号/ からBlogの記事を適当に選んでコメントが表示されるか確認する。
※↑この時点で、サンプルコメントをデータベースに書き込んでいないので、コメントは表示されない。
■ コメントの追加
CommentsController.php
<?php
class CommentsController extends AppController {
public $helpers = array('Html', 'Form');
public function add() {
if($this->request->is('post')){
if($this->Comment->save($this->request->data)) {
$this->Session->setFlash('Success!');
$this->redirect('/posts/view/'.$this->data['Comment']['post_id']);
} else {
$this->Session->setFlash('failed!');
}
}
}
}
view.ctp にコメント投稿フォームを追加する。
<h2>コメント追加</h2>
<?php
echo $this->Form->create('Comment', array('action' => 'add'));
echo $this->Form->input('commenter');
echo $this->Form->input('body', array('rows' =>3));
echo $this->Form->input('Comment.post_id', array('type'=>'hidden', 'value'=>$post['Post']['id']));
echo $this->Form->end('コメント投稿');
コメントの投稿と表示を確認する。
■ コメントの削除
CommentsController.php にコメント削除機能を追加する。
public function delete($id = null) {
if($this->request->is('get')){
throw new MethodNotAllowedException();
}
if($this->Comment->delete($id)) {
$this->Session->setFlash('Deleted!');
$this->redirect('/posts/index');
}
}
view.ctp のコメント表示のループの中に、各コメントごとの 削除リンクを追加
<?php echo $this->Form->postlink('削除','/comments/delete/'.$comment['id'],array('confirm'=>'削除しますか?')); ?>
動作確認
■ 提出物
データベース設計演習1/4 ~ 3/4 について、以下の手順で cakePHP のファイルと blog データベースを
学籍番号付のフォルダやファイルとして、レポートフォルダへ提出する。
・ cakePHP : d:\xampp\htdocs\blog学籍番号 を レポートフォルダ(データベース)の提出場所へ提出
・ blog データベース : phpMyAdmin から blogデータベースを選択して エクスポート する。
ファイル名を blog3113999.sql に変更してレポートフォルダ(データベース)の提出場所へ提出
■ 宿題
期末課題として作成する、Webサービスのテーマが決まっていないものは、決めておく。
CakePHP で制作するために必要な項目を決めておく。
・データベース名
・テーブル名(1~3個でよい)
・フィールド名 と 型(各テーブルについて)
例1)
テーマ: ボードゲーム貸し出し管理
データベース名: boardgame
テーブル名とフィールド名
games
id int auto_incremental primary key
name varchar(20) not null
desc text
users
id int auto_incremental primary key
name varchar(20) not null
rentals
id int auto_incremental primary key
user_id not null
game_id not null
date datetime
例2)
テーマ: ゲームカタログ
データベース名: gamecatalog
games
id int auto_incremental primary key
name varchar(20) not null
desc text
genre_id
genres
id int auto_incremental primary key
name varchar(20) not null
■期末課題の提出物
blog を作成したと同様に、データベース名のフォルダを作成し、CakePHPを準備する。
・学籍番号+データベース名 のフォルダ
・学籍番号+データベース名.sql のエクスポートファイル
の2点を期日までに提出する。(〆切は7月末の予定)
■期末課題採点基準:(30点満点+)
・データベースの設計 正規化 ・ テーブル間の関係 (20点)
・サンプルデータ (5点)
・独自の工夫(カスタマイズビューの作成・操作メニューの作成・Webリンク・並べ替え・集計機能・セキュリティなど)(5点~)
ビューとコントローラーは、 scaffold で単純に作成してもよい。
テーブルが1つしかないデータベースの制作でもかまわないが、複数テーブルのデータベースに対して、評価を半減する。
データベースの構造で減点されない分を、独自の工夫でカバーするように。