CakePHPで高速Webアプリ開発

第7回CakePHPで作るToDoアプリ(3)

第6回ではタスクの追加機能を実装しました。今回は以下の機能を実装します。

  • タスクの完了
  • タスクの削除
  • タスクの編集
  • 完了と未完了のタスクを分離

Tasksコントローラの修正

Tasksコントローラのindexアクションを修正し、doneアクション、editアクション、delアクションを追加しました。addアクションに変更はありませんリスト1⁠。

リスト1 app/controllers/tasks_controller.php
<?php
class TasksController extends AppController {
  var $name = 'Tasks';
  var $uses = array('Task');
  function index() {
    $this->set('yet_tasks', $this->Task->findAllByStatus('yet', null, 'Task.created ASC'));
    $this->set('done_tasks', $this->Task->findAllByStatus('done', null, 'Task.modified DESC'));
  }
  function add() {
    if (!empty($this->data)) {
      if ($this->Task->save($this->data, true, array('content', 'created', 'modified'))) {
        $this->flash('タスクが追加されました', '/tasks');
        return;
      }
    }
    $this->redirect('/tasks');
  }
  function done($id) {
    if ($this->Task->findById($id)) {
      $this->Task->id = $id;
      $this->Task->save(array('status' => 'done'));
    }
    $this->redirect('/tasks');
  }
  function edit($id) {
    $task = $this->Task->findById($id);
    if (!$task) {
      $this->redirect('/tasks');
      return;
    }
    if (!empty($this->data)) {
      $task['Task']['content'] = $this->data['Task']['content'];
      $this->Task->save($task);
    }
    $this->set('task', $task);
  }
  function del($id) {
    $this->Task->del($id);
    $this->redirect('/tasks');
  }
}

indexビューの修正

indexビューは使用する変数が変わり、2つのリストを表示するようになったので大幅に修正していますリスト2⁠。タスクごとに、完了(Done⁠⁠、編集(Edit⁠⁠、削除(Del)へリンクも加わっています。

リスト2 app/views/tasks/index.thtml
<form action="<?php echo h($html->url('/tasks/add')) ?>" method="post" style="margin-bottom:1em">
<p><?php echo $html->input('Task/content') ?>
<?php echo $html->submit('タスクを追加') ?></p>
</form>

<h2>未完了タスク</h2>

<table>
<tr>
<th>Id</th>
<th>タスク内容</th>
<th>状態</th>
<th>操作</th>
<th>作成日</th>
</tr>
<?php foreach ($yet_tasks as $task) { ?>
<tr>
<td><?php echo h($task['Task']['id']) ?></td>
<td><?php echo h($task['Task']['content']) ?></td>
<td><?php echo h($task['Task']['status']) ?></td>
<td>
 <?php echo $html->link('完了', '/tasks/done/' . $task['Task']['id'], null, '完了してもよろしいですか?') ?>
 <?php echo $html->link('編集', '/tasks/edit/' . $task['Task']['id']) ?>
 <?php echo $html->link('削除',  '/tasks/del/'  . $task['Task']['id'], null, '削除してもよろしいですか?') ?>
</td>
<td><?php echo h($task['Task']['created']) ?></td>
</tr>
<?php } ?>
</table>


<h2>完了タスク</h2>

<table>
<tr>
<th>Id</th>
<th>タスク内容</th>
<th>状態</th>
<th>操作</th>
<th>作成日</th>
</tr>
<?php foreach ($done_tasks as $task) { ?>
<tr>
<td><?php echo h($task['Task']['id']) ?></td>
<td><?php echo h($task['Task']['content']) ?></td>
<td><?php echo h($task['Task']['status']) ?></td>
<td><?php echo $html->link('削除', '/tasks/del/' . $task['Task']['id'], null, '削除してもよろしいですか?') ?></td>
<td><?php echo h($task['Task']['created']) ?></td>
</tr>
<?php } ?>
</table>

editビューの作成

editアクション用のビューとしてapp/views/tasks/edit.thtmlファイルを作成し、リスト3のコードを記述します。

リスト3 app/views/tasks/edit.thtml
<h1>タスクの編集</h1>
<p><a href="<?php echo h($html->url('/tasks')) ?>">タスク一覧へ戻る</a></p>

<form action="<?php echo h($html->url('/tasks/edit/' . $task['Task']['id'])) ?>" method="post">
<?php echo $html->hidden('Task/id', $task['Task']['id']) ?>

<h2>内容</h2>
<p><?php echo $html->textarea('Task/content', array('cols' => '60', 'rows' => '3', 'value' => $task['Task']['content'])) ?></p>

<p><input type="submit" value="保存"></p>

修正内容の解説:コントローラ編

以上でタスクの完了、編集、削除、完了未完了の分離機能が実装できました。上記修正内容を読み解いていきます。

indexアクション:ステータスを指定してタスクを取得する

  function index() {
    $this->set('yet_tasks', $this->Task->findAllByStatus('yet', null, 'Task.created ASC'));
    $this->set('done_tasks', $this->Task->findAllByStatus('done', null, 'Task.modified DESC'));
  }

findAllBy[フィールド名]メソッドによってstatusフィールドの値を指定してレコードを取得しています。

doneアクション:タスクを完了する

タスクの完了は「完了」リンクをクリックしてdoneアクションにアクセスし、タスクを完了します。

  function done($id) { 

アクションの定義に引数を設定すると「http://example.com/~gihyo/todo/tasks/done/1」のようなURLからパラメータを受け取ることができます。

    if ($this->Task->findById($id)) {
      $this->Task->id = $id;
      $this->Task->save(array('status' => 'done'));
    }

このブロックでは、引数で渡されてきたidのタスクの存在を確認し、statusフィールドの値をdoneに変更しています。データを更新するときは$this->Task->idに更新したいデータのidを代入した後にsaveします。

    $this->redirect('/tasks');

タスクの完了画面は出さずに即座にタスク一覧に戻すため、redirectしています。

editアクション:編集画面の表示と編集内容の保存を行う

editアクションは少々複雑で、編集画面の表示と保存を兼ね備えています。

  function edit($id) {
    $task = $this->Task->findById($id);
    if (!$task) {
      $this->redirect('/tasks');
      return;
    }

引数で渡されてきたidでデータ取得を試みて、$taskに代入します。結果がfalseであればデータが存在しないので一覧表示へリダイレクトします。

    if (!empty($this->data)) {
      $task['Task']['content'] = $this->data['Task']['content'];
      $this->Task->save($task);
    }

データの存在チェックが通り、$this->dataのチェックでデータがPOSTされてきたときは、先ほど取得したデータのcontentフィールドを書き換えてsaveします。

    $this->set('task', $task);

最後にビューへTaskモデルのデータを渡して終了です。

delアクション:タスクを削除する

delアクションはタスクを削除し、一覧表示へリダイレクトします。

  function del($id) {
    $this->Task->del($id);
    $this->redirect('/tasks');
  }

データの削除はモデルのdelメソッドにidを渡すことで行います。

ちなみにここではあえてデータの存在チェックを行っていません。削除行為自体はデータがあってもなくても行えるため、とくにエラー表示をしないのであれば不要だからです。

修正内容の解説:ビュー編

indexビュー:完了と未完了を分け、各機能へのリンクを設置

indexビューでは、$tasksに代わって$done_tasksと$yet_tasksで完了と未完了のタスクが分けて渡されてきたことに対応して、それぞれで一覧を表示するようにしました。その際、未完了タスクには完了、編集、削除へのリンクを作成しました。以下の部分でリンクを表示しています。

<td>
 <?php echo $html->link('完了', '/tasks/done/' . $task['Task']['id'], null, '完了してもよろしいですか?') ?>
 <?php echo $html->link('編集', '/tasks/edit/' . $task['Task']['id']) ?>
 <?php echo $html->link('削除',  '/tasks/del/'  . $task['Task']['id'], null, '削除してもよろしいですか?') ?>
</td>

ここではHTMLヘルパーのlinkメソッドを使用してリンクを表示しています。完了と削除のリンクでは、リンクを選択したあとに確認メッセージを表示するようにしています。

余談ですがアプリケーション設計の1つの考え方として、アプリケーションの操作では元に戻せない処理を今回のようなリンクの選択とという手軽なインターフェースで行えてしまうときは、確認メッセージを表示すると誤操作を防ぐことができて良いでしょう。この考え方では、他への影響がなく状態を元に戻せる場合は必ずしも確認メッセージは出さなくてもよいとされます。

editビュー:タスク内容の修正フォーム

editビューではタスク内容の修正フォームを表示しています。

<p><a href="<?php echo h($html->url('/tasks')) ?>">タスク一覧へ戻る</a></p>

タスク一覧表示へのリンクです。HTMLヘルパーのlinkメソッドを使わず、URLのみHTMLヘルパーのurlメソッドで表示しています。違いとしては、WYISIWYGエディタからビューを調整する場合はこのように書いた方が都合が良いことがあります。

<form action="<?php echo h($html->url('/tasks/edit/' . $task['Task']['id'])) ?>" method="post">

formの開始タグです。

<h2>内容</h2>
<p><?php echo $html->textarea('Task/content', array('cols' => '60', 'rows' => '3', 'value' => $task['Task']['content'])) ?></p>

HTMLヘルパーのtextareaメソッドを使用してtextarea要素を表示しています。

<p><input type="submit" value="Save"></p>

Submitボタンの表示です。ここでもHTMLヘルパーのsubmitメソッドを使わずにSubmitボタンを表示していますが、動作上問題ありません。

修正したアプリケーションの動作を確認する

修正したアプリケーションの動作を確認します。図1→3のような画面で遷移できれば修正は完了です。

図1 完了と未完了タスクが分離された一覧表示
図1 完了と未完了タスクが分離された一覧表示
図2 完了と削除のリンク選択時に確認メッセージが表示される
図2 完了と削除のリンク選択時に確認メッセージが表示される
図3 Edit(編集)アクションのビュー
図3 Edit(編集)アクションのビュー

今回はコードの書き方に統一性がありませんでしたが、多様な書き方を紹介するためにわざと統一しませんでした。実際の開発ではなるべく統一した書き方を心がけると良いでしょう。

次回予定はタスク追加のAjax化です。

おすすめ記事

記事・ニュース一覧