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つしかないデータベースの制作でもかまわないが、複数テーブルのデータベースに対して、評価を半減する。

データベースの構造で減点されない分を、独自の工夫でカバーするように。