【チュートリアル】最新フレームワークCakePHP3でブックマーカーを作ってみる その2
前回の続きになります。
ログイン機能の実装
ログイン機能はCakePHP2同様AuthComponentを利用します。
AppContorollerに追加しましょう
// In 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' ] ]); // Allow the display action so our pages controller // continues to work. $this->Auth->allow(['display']); } }
ここではflashとauthコンポーネントをロードしています。
usernameにemail,passwordにpasswordを使用します。このへんはCakePHP2と同様ですね。
現在ログインしていないので、どのページにアクセスしても/users/loginにリダイレクトされます。
では/users/loginページを実装しましょう。
// In 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('Your username or password is incorrect.'); } }
// src/Template/Users/login.ctp <h1>Login</h1> <?= $this->Form->create() ?> <?= $this->Form->input('email') ?> <?= $this->Form->input('password') ?> <?= $this->Form->button('Login') ?> <?= $this->Form->end() ?>
これでログイン機能ができました。
ログアウト機能の実装
UsersController.phpにlogoutメソッドを追加します。
public function logout() { $this->Flash->success('You are now logged out.'); return $this->redirect($this->Auth->logout()); }
ここもCakePHP2と同様ですね。
ユーザ登録の有効化
現在ユーザ登録しようとしてもログイン画面へリダイレクトされるので、それをさせないようにします。
// In src/Controller/UsersController.php public function beforeFilter(\Cake\Event\Event $event) { $this->Auth->allow(['add']); }
addメソッドが認証を必要としないことを示します。
ブックマークへのアクセス制限
現在ログインしさえすればすべてのユーザのブックマークにアクセスできてしまします。
ですので、その人が登録したブックマークのみアクセス出来るようにしましょう。
authorizationアダプターを使用します。
// In src/Controller/AppController.php public function initialize() { $this->loadComponent('Flash'); $this->loadComponent('Auth', [ 'authorize'=> 'Controller',//added this line 'authenticate' => [ 'Form' => [ 'fields' => [ 'username' => 'email', 'password' => 'password' ] ] ], 'loginAction' => [ 'controller' => 'Users', 'action' => 'login' ], 'unauthorizedRedirect' => $this->referer() ]); // Allow the display action so our pages controller // continues to work. $this->Auth->allow(['display']); } // add public function isAuthorized($user) { return false; }
基本はアクセス禁止で、ホワイトリスト方式が良いでしょう。
// In src/Controller/BookmarksController.php public function isAuthorized($user) { $action = $this->request->params['action']; // The add and index actions are always allowed. if (in_array($action, ['index', 'add', 'tags'])) { return true; } // All other actions require an id. if (empty($this->request->params['pass'][0])) { return false; } // Check that the bookmark belongs to the current user. $id = $this->request->params['pass'][0]; $bookmark = $this->Bookmarks->get($id); if ($bookmark->user_id == $user['id']) { return true; } return parent::isAuthorized($user); }
これで表示、編集、削除しても元のページに戻ります。 エラーページも用意しましょう
// In src/Template/Layout/default.ctp // Under the existing flash message. <?= $this->Flash->render('auth') ?>
一覧ページとフォームの修正
追加機能と一覧ページには以下の問題があります。 1. ブックマークを追加や編集する場合、ユーザを選択出来てしまう 2. 一覧ページでは他のユーザのブックマークまで閲覧できてしまう
これらを修正していきます。
フォームの修正
Bookmarks/add.ctpから input('user_id') を削除してユーザIDを編集できないようにしましょう。
またBookmarksController.phpをaddメソッドとeditメソッドを以下のように修正しましょう。
public function add() { $bookmark = $this->Bookmarks->newEntity(); if ($this->request->is('post')) { $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data); $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.'); } $tags = $this->Bookmarks->Tags->find('list'); $this->set(compact('bookmark', 'tags')); }
public function edit($id = null) { $bookmark = $this->Bookmarks->get($id, [ 'contain' => ['Tags'] ]); if ($this->request->is(['patch', 'post', 'put'])) { $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data); $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.'); } $tags = $this->Bookmarks->Tags->find('list'); $this->set(compact('bookmark', 'tags')); }
一覧画面の修正
続いて一覧画面でログインしているユーザーのブックマークだけを表示するようにします。 pagenate()に条件を加えて以下の様に修正してください。
public function index() { $this->paginate = [ 'conditions' => [ 'Bookmarks.user_id' => $this->Auth->user('id'), ] ]; $this->set('bookmarks', $this->paginate($this->Bookmarks)); }
タグ機能の修正
現状ではTagsControllerがすべてのアクセスを拒否するため、新しいタグの登録ができません。 アクセスを許可する代わりに、区切りのテキストで入力するUIを開発しましょう。 これにより良いユーザユーザーエクスペリエンスを手に入れられるだけでなく、ORMのさらなる機能を使ってみることができます。
処理済フィールドの追加
エンティティへのアクセスをシンプルな方法にするために、エンティティに仮想的で処理済なフィールドを追加しましょう。src/Model/Entity/Bookmark.phpに以下を追加してください。
use Cake\Collection\Collection; protected function _getTagString() { if (isset($this->_properties['tag_string'])) { return $this->_properties['tag_string']; } if (empty($this->tags)) { return ''; } $tags = new Collection($this->tags); $str = $tags->reduce(function ($string, $tag) { return $string . $tag->title . ', '; }, ''); return trim($str, ', '); }
これは私たちが$bookmark->tag_stringとすれば処理済のプロパティにアクセスすることを可能にします。 後で、このプロパティをフォームのinputで使います。 また保存に必要なのでtag_stringプロパティをエンティティの_accessibleリストに追加してください。
protected $_accessible = [ 'user_id' => true, 'title' => true, 'description' => true, 'url' => true, 'user' => true, 'tags' => true, 'tag_string' => true, ];
ビューの更新
エンティティが更新されたので、新しいinput要素を追加することができます。add、editの、既存のtags._idsinputを以下のように変更してください。
<?= $this->Form->input('tag_string', ['type' => 'text']) ?>
タグ文字列の永続化
既存のタグを文字列として表示できるようになりました。同様にタグ文字列を保存できるようにしてみましょう。
tag_stringを$_accessibleに追加したので、ORMはこのデータをリクエストからエンティティにコピーします。
beforeSave()フックメソッドを使用して、エンティティに対してタグ文字列のパースとタグリストの検索/追加ができます。以下のコードをBookmarksTable.phpに追加してください。
public function beforeSave($event, $entity, $options) { if ($entity->tag_string) { $entity->tags = $this->_buildTags($entity->tag_string); } } protected function _buildTags($tagString) { $new = array_unique(array_map('trim', explode(',', $tagString))); $out = []; $query = $this->Tags->find() ->where(['Tags.title IN' => $new]); // Remove existing tags from the list of new tags. foreach ($query->extract('title') as $existing) { $index = array_search($existing, $new); if ($index !== false) { unset($new[$index]); } } // Add existing tags. foreach ($query as $tag) { $out[] = $tag; } // Add new tags. foreach ($new as $tag) { $out[] = $this->Tags->newEntity(['title' => $tag]); } return $out; }
まとめ
ブックマークアプリケーションにおいて、簡単なログイン認証、アクセスコントロールなどを実装できました。 これを機会にCakePHP3に触れる人が増えてくればなによりです。