CakePHPで簡単にSchedule管理アプリを作る
どうも、よしかわです。
今回は、学校のサークルなどで PHP で Web サービスを作成したいとのことだったので綴っていきます。
コードはこちらから見ることが出来ます.
目次
対象読者
データベースの設定
マイグレーション
以下のコマンドをターミナルで実行します.
bin/cake bake migration CreateUsers username:string email:string:unique:EMAIL_INDEX password:string created modified
config/Migrations/CreateUsers.php
<?php use Migrations\AbstractMigration; class CreateUsers extends AbstractMigration { /** * Change Method. * * More information on this method is available here: * http://docs.phinx.org/en/latest/migrations.html#the-change-method * @return void */ public function change() { $table = $this->table('users'); $table->addColumn('username', 'string', [ 'default' => null, 'limit' => 255, 'null' => false, ]); $table->addColumn('email', 'string', [ 'default' => null, 'limit' => 255, 'null' => false, ]); $table->addColumn('password', 'string', [ 'default' => null, 'limit' => 255, 'null' => false, ]); $table->addColumn('created', 'datetime', [ 'default' => null, 'null' => false, ]); $table->addColumn('modified', 'datetime', [ 'default' => null, 'null' => false, ]); $table->addIndex([ 'email', ], [ 'name' => 'EMAIL_INDEX', 'unique' => true, ]); $table->create(); } }
以下のコマンドをターミナルで実行します.
bin/cake bake migration CreateScehdules user_id:int:foreign title:string description:text schedulememo:string scheduledate:date starttime:time endtime:time created modified
config/Migrations/CreateSchedules.php
<?php use Migrations\AbstractMigration; class CreateSchedules extends AbstractMigration { /** * Change Method. * * More information on this method is available here: * http://docs.phinx.org/en/latest/migrations.html#the-change-method * @return void */ public function change() { $table = $this->table('schedules'); $table->addColumn('user_id', 'string', [ 'default' => null, 'limit' => 255, 'null' => false, ]); $table->addColumn('title', 'string', [ 'default' => null, 'limit' => 255, 'null' => false, ]); $table->addColumn('description', 'text', [ 'default' => null, 'null' => false, ]); $table->addColumn('schedulememo', 'string', [ 'default' => null, 'limit' => 255, 'null' => false, ]); $table->addColumn('scheduledate', 'date', [ 'default' => null, 'null' => false, ]); $table->addColumn('starttime', 'time', [ 'default' => null, 'null' => false, ]); $table->addColumn('endtime', 'time', [ 'default' => null, 'null' => false, ]); $table->addColumn('created', 'datetime', [ 'default' => null, 'null' => false, ]); $table->addColumn('modified', 'datetime', [ 'default' => null, 'null' => false, ]); $table->addIndex([ 'user_id', ], [ 'name' => 'BY_USER_ID', 'unique' => false, ]); $table->create(); } }
Bake で自動生成する
ターミナルで以下のコマンドを実行していきます.
bin/cake bake all users bin/cake bake all schedules
多分これで,ほとんどのファイルが自動で生成されたはずです.
次にログイン機構などを作成していきます.
ログイン機構を作成
上記を参考にして,作成していきます.
src/Model/Entity/User.php を編集していきます.
<?php namespace App\Model\Entity; use Cake\ORM\Entity; use Cake\Auth\DefaultPasswordHasher; /** * User Entity * * @property int $id * @property string $username * @property string $email * @property string $password * @property \Cake\I18n\FrozenTime $created * @property \Cake\I18n\FrozenTime $modified * * @property \App\Model\Entity\Schedule[] $schedules */ class User extends Entity { /** * Fields that can be mass assigned using newEntity() or patchEntity(). * * Note that when '*' is set to true, this allows all unspecified fields to * be mass assigned. For security purposes, it is advised to set '*' to false * (or remove it), and explicitly make individual fields accessible as needed. * * @var array */ protected $_accessible = [ 'username' => true, 'email' => true, 'password' => true, 'created' => true, 'modified' => true, 'schedules' => true ]; /** * Fields that are excluded from JSON versions of the entity. * * @var array */ protected $_hidden = [ 'password' ]; // _setPassword is to set password protected function _setPassword($password) { return (new DefaultPasswordHasher)->hash($password); } }
src/Controller/UsersController.php
<?php namespace App\Controller; use App\Controller\AppController; use Cake\Utility\Security; use Cake\Event\Event; /** * Users Controller * * @property \App\Model\Table\UsersTable $Users * * @method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = []) */ class UsersController extends AppController { public function beforeFilter(Event $event) { parent::beforeFilter($event); // ユーザーの登録とログアウトを許可します。 $this->Auth->allow(['add', 'logout']); } 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(__('Invalid email or password, try again')); } } public function logout() { return $this->redirect($this->Auth->logout()); } /** * Index method * * @return \Cake\Http\Response|void */ public function index() { $users = $this->paginate($this->Users); $this->set(compact('users')); } /** * View method * * @param string|null $id User id. * @return \Cake\Http\Response|void * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. */ public function view($id = null) { $user = $this->Users->get($id, [ 'contain' => ['Schedules'] ]); $this->set('user', $user); } /** * Add method * * @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise. */ public function add() { $user = $this->Users->newEntity(); if ($this->request->is('post')) { $user = $this->Users->patchEntity($user, $this->request->getData()); if ($this->Users->save($user)) { $this->Flash->success(__('The user has been saved.')); return $this->redirect(['action' => 'index']); } $this->Flash->error(__('The user could not be saved. Please, try again.')); } $this->set(compact('user')); } /** * Edit method * * @param string|null $id User id. * @return \Cake\Http\Response|null Redirects on successful edit, renders view otherwise. * @throws \Cake\Network\Exception\NotFoundException When record not found. */ public function edit($id = null) { $user = $this->Users->get($id, [ 'contain' => [] ]); if ($this->request->is(['patch', 'post', 'put'])) { $user = $this->Users->patchEntity($user, $this->request->getData()); if ($this->Users->save($user)) { $this->Flash->success(__('The user has been saved.')); return $this->redirect(['action' => 'index']); } $this->Flash->error(__('The user could not be saved. Please, try again.')); } $this->set(compact('user')); } /** * Delete method * * @param string|null $id User id. * @return \Cake\Http\Response|null Redirects to index. * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. */ public function delete($id = null) { $this->request->allowMethod(['post', 'delete']); $user = $this->Users->get($id); if ($this->Users->delete($user)) { $this->Flash->success(__('The user has been deleted.')); } else { $this->Flash->error(__('The user could not be deleted. Please, try again.')); } return $this->redirect(['action' => 'index']); } }
src/Controller/AppController.php
<?php /** * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 0.2.9 * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace App\Controller; use Cake\Controller\Controller; use Cake\Event\Event; /** * Application Controller * * Add your application-wide methods in the class below, your controllers * will inherit them. * * @link https://book.cakephp.org/3.0/en/controllers.html#the-app-controller */ class AppController extends Controller { /** * Initialization hook method. * * Use this method to add common initialization code like loading components. * * e.g. `$this->loadComponent('Security');` * * @return void */ public function initialize() { parent::initialize(); $this->loadComponent('RequestHandler', [ 'enableBeforeRedirect' => false, ]); $this->loadComponent('Flash'); $this->loadComponent('Auth', [ 'authenticate' => [ 'Form' => [ 'fields' => [ 'username' => 'email', 'password' => 'password' ] ] ], 'loginRedirect' => [ 'controller' => 'Schedules', 'action' => 'index' ], 'logoutRedirect' => [ 'controller' => 'Users', 'action' => 'login' ] ]); /* * Enable the following component for recommended CakePHP security settings. * see https://book.cakephp.org/3.0/en/controllers/components/security.html */ //$this->loadComponent('Security'); } }
src/Template/Users/login.ctp
<?php /** * Copyright 2010 - 2017, Cake Development Corporation (https://www.cakedc.com) * * Licensed under The MIT License * Redistributions of files must retain the above copyright notice. * * @copyright Copyright 2010 - 2017, Cake Development Corporation (https://www.cakedc.com) * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ use Cake\Core\Configure; ?> <?= $this->Html->css('users') ?> <?= $this->fetch('head') ?> <div class="login-form"> <?= $this->Flash->render('auth') ?> <?= $this->Form->create() ?> <fieldset> <legend class="form-title"><?= __d('Users', 'Login') ?></legend> <?= $this->Form->input('email', array('label' => false, 'required' => true, 'placeholder' => 'email')) ?> <?= $this->Form->input('password', array('label' => false, 'required' => true, 'placeholder' => 'password')) ?> </fieldset> <?= $this->Form->button(__d('Users', 'Login')); ?> <?= $this->Form->end() ?> </div>
ログイン画面も作成していきましょう.
src/Template/Users/login.ctp
<?php /** * Copyright 2010 - 2017, Cake Development Corporation (https://www.cakedc.com) * * Licensed under The MIT License * Redistributions of files must retain the above copyright notice. * * @copyright Copyright 2010 - 2017, Cake Development Corporation (https://www.cakedc.com) * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ use Cake\Core\Configure; ?> <?= $this->Html->css('users') ?> <?= $this->fetch('head') ?> <div class="login-form"> <?= $this->Flash->render('auth') ?> <?= $this->Form->create() ?> <fieldset> <legend class="form-title"><?= __d('Users', 'Login') ?></legend> <?= $this->Form->input('email', array('label' => false, 'required' => true, 'placeholder' => 'email')) ?> <?= $this->Form->input('password', array('label' => false, 'required' => true, 'placeholder' => 'password')) ?> </fieldset> <?= $this->Form->button(__d('Users', 'Login')); ?> <?= $this->Form->end() ?> </div>
スケジュールを見れるようにする
src/Controller/SchedulesController.php
<?php namespace App\Controller; use App\Controller\AppController; /** * Schedules Controller * * @property \App\Model\Table\SchedulesTable $Schedules * * @method \App\Model\Entity\Schedule[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = []) */ class SchedulesController extends AppController { /** * Index method * * @return \Cake\Http\Response|void */ public function index() { $this->paginate = [ 'contain' => ['Users'] ]; $schedules = $this->paginate($this->Schedules); $this->set(compact('schedules')); } /** * View method * * @param string|null $id Schedule id. * @return \Cake\Http\Response|void * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. */ public function view($id = null) { $schedule = $this->Schedules->get($id, [ 'contain' => ['Users'] ]); if ($this->Auth->user('id') == $schedule['user_id']) { $this->set('schedule', $schedule); $this->set('_serialize', ['schedule']); } else { $this->Flash->error(__('ユーザーIDが違います。同じユーザーIDのみ視聴できます。')); return $this->redirect(['action' => 'index']); } } /** * Add method * * @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise. */ public function add() { $schedule = $this->Schedules->newEntity(); if ($this->request->is('post')) { $schedule = $this->Schedules->patchEntity($schedule, $this->request->getData()); if ($this->Schedules->save($schedule)) { $this->Flash->success(__('The schedule has been saved.')); return $this->redirect(['action' => 'index']); } $this->Flash->error(__('The schedule could not be saved. Please, try again.')); } $users = $this->Schedules->Users->find('list', ['limit' => 200]); $this->set(compact('schedule', 'users')); } /** * Edit method * * @param string|null $id Schedule id. * @return \Cake\Http\Response|null Redirects on successful edit, renders view otherwise. * @throws \Cake\Network\Exception\NotFoundException When record not found. */ public function edit($id = null) { $schedule = $this->Schedules->get($id, [ 'contain' => [] ]); if ($this->request->is(['patch', 'post', 'put'])) { $schedule = $this->Schedules->patchEntity($schedule, $this->request->getData()); if ($this->Schedules->save($schedule)) { $this->Flash->success(__('The schedule has been saved.')); return $this->redirect(['action' => 'index']); } $this->Flash->error(__('The schedule could not be saved. Please, try again.')); } $users = $this->Schedules->Users->find('list', ['limit' => 200]); $this->set(compact('schedule', 'users')); } /** * Delete method * * @param string|null $id Schedule id. * @return \Cake\Http\Response|null Redirects to index. * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. */ public function delete($id = null) { $this->request->allowMethod(['post', 'delete']); $schedule = $this->Schedules->get($id); if ($this->Schedules->delete($schedule)) { $this->Flash->success(__('The schedule has been deleted.')); } else { $this->Flash->error(__('The schedule could not be deleted. Please, try again.')); } return $this->redirect(['action' => 'index']); } /** * Weekly method * * @return \Cake\Http\Response|void */ public function weekly() { $schedules = $this->Schedules->find()->where(["user_id = " => $this->Auth->user('id')]) ->andwhere(["DATE(scheduledate) >= CURDATE()"]) ->andwhere(["DATE(scheduledate) <= DATE(DATE_ADD(CURDATE(), INTERVAL 7 DAY))"]); $this->paginate($schedules); $this->set(compact('schedules')); } /** * Monthly method * * @return \Cake\Http\Response|void */ public function monthly() { $schedules = $this->Schedules->find()->where(["user_id = " => $this->Auth->user('id')]) ->andwhere(["DATE(scheduledate) >= CURDATE()"]) ->andwhere(["DATE(scheduledate) <= DATE(DATE_ADD(CURDATE(), INTERVAL 30 DAY))"]); $this->paginate($schedules); $this->set(compact('schedules')); } }
src/Template/Schedules/weekly.ctp
<?php /** * @var \App\View\AppView $this * @var \App\Model\Entity\Schedule[]|\Cake\Collection\CollectionInterface $schedules */ ?> <nav class="large-3 medium-4 columns" id="actions-sidebar"> <ul class="side-nav"> <li class="heading"><?= __('Actions') ?></li> <li><?= $this->Html->link(__('New Schedule'), ['action' => 'add']) ?></li> <li> <?= $this->Html->link(__('List Users'), ['controller' => 'Users', 'action' => 'index']) ?> </li> <li> <?= $this->Html->link(__('New User'), ['controller' => 'Users', 'action' => 'add']) ?> </li> </ul> </nav> <div class="schedules index large-9 medium-8 columns content"> <h3><?= __('Schedules') ?></h3> <table cellpadding="0" cellspacing="0"> <thead> <tr> <th scope="col"><?= $this->Paginator->sort('id') ?></th> <th scope="col"><?= $this->Paginator->sort('title') ?></th> <th scope="col"><?= $this->Paginator->sort('scheduledate') ?></th> <th scope="col"><?= $this->Paginator->sort('created') ?></th> <th scope="col"><?= $this->Paginator->sort('modified') ?></th> <th scope="col" class="actions"><?= __('Actions') ?></th> </tr> </thead> <tbody> <?php foreach ($schedules as $schedule): ?> <tr> <td><?= $this->Number->format($schedule->id) ?></td> <td><?= h($schedule->title) ?></td> <td><?= h($schedule->scheduledate) ?></td> <td><?= h($schedule->created) ?></td> <td><?= h($schedule->modified) ?></td> <td class="actions"> <?= $this->Html->link(__('View'), ['action' => 'view', $schedule->id]) ?> <?= $this->Html->link(__('Edit'), ['action' => 'edit', $schedule->id]) ?> <?= $this->Form->postLink(__('Delete'), ['action' => 'delete', $schedule->id], ['confirm' => __('Are you sure you want to delete # {0}?', $schedule->id)]) ?> </td> </tr> <?php endforeach; ?> </tbody> </table> <div class="paginator"> <ul class="pagination"> <?= $this->Paginator->first('<< ' . __('first')) ?> <?= $this->Paginator->prev('< ' . __('previous')) ?> <?= $this->Paginator->numbers() ?> <?= $this->Paginator->next(__('next') . ' >') ?> <?= $this->Paginator->last(__('last') . ' >>') ?> </ul> <p> <?= $this->Paginator->counter(['format' => __('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')]) ?> </p> </div> </div>
src/Template/Schedules/monthly.ctp
<?php /** * @var \App\View\AppView $this * @var \App\Model\Entity\Schedule[]|\Cake\Collection\CollectionInterface $schedules */ ?> <nav class="large-3 medium-4 columns" id="actions-sidebar"> <ul class="side-nav"> <li class="heading"><?= __('Actions') ?></li> <li><?= $this->Html->link(__('New Schedule'), ['action' => 'add']) ?></li> <li> <?= $this->Html->link(__('List Users'), ['controller' => 'Users', 'action' => 'index']) ?> </li> <li> <?= $this->Html->link(__('New User'), ['controller' => 'Users', 'action' => 'add']) ?> </li> </ul> </nav> <div class="schedules index large-9 medium-8 columns content"> <h3><?= __('Schedules') ?></h3> <table cellpadding="0" cellspacing="0"> <thead> <tr> <th scope="col"><?= $this->Paginator->sort('id') ?></th> <th scope="col"><?= $this->Paginator->sort('title') ?></th> <th scope="col"><?= $this->Paginator->sort('scheduledate') ?></th> <th scope="col"><?= $this->Paginator->sort('created') ?></th> <th scope="col"><?= $this->Paginator->sort('modified') ?></th> <th scope="col" class="actions"><?= __('Actions') ?></th> </tr> </thead> <tbody> <?php foreach ($schedules as $schedule): ?> <tr> <td><?= $this->Number->format($schedule->id) ?></td> <td><?= h($schedule->title) ?></td> <td><?= h($schedule->scheduledate) ?></td> <td><?= h($schedule->created) ?></td> <td><?= h($schedule->modified) ?></td> <td class="actions"> <?= $this->Html->link(__('View'), ['action' => 'view', $schedule->id]) ?> <?= $this->Html->link(__('Edit'), ['action' => 'edit', $schedule->id]) ?> <?= $this->Form->postLink(__('Delete'), ['action' => 'delete', $schedule->id], ['confirm' => __('Are you sure you want to delete # {0}?', $schedule->id)]) ?> </td> </tr> <?php endforeach; ?> </tbody> </table> <div class="paginator"> <ul class="pagination"> <?= $this->Paginator->first('<< ' . __('first')) ?> <?= $this->Paginator->prev('< ' . __('previous')) ?> <?= $this->Paginator->numbers() ?> <?= $this->Paginator->next(__('next') . ' >') ?> <?= $this->Paginator->last(__('last') . ' >>') ?> </ul> <p> <?= $this->Paginator->counter(['format' => __('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')]) ?> </p> </div> </div>
まとめ
今回は,簡単にログイン機構やスケジュール機能を作成しただけなのですが,結構コード量が多いですね...