CakePHPで高速Webアプリ開発

第6回CakePHPで作るToDoアプリ(2)

第5回ではテストデータの表示までが完了しました。今回はタスクの追加機能を実装します。

実装においては、作業上のデメリットの発生やセキュリティ問題などを引き起こさないように配慮したコードにしました。その代償として他のチュートリアルに比べて比較的複雑になっていますが、より実践的なサンプルとして扱えると思います。

入力フォームを作る

まずは入力フォームです。app/views/tasks/index.thtmlを書き換えて、ToDo一覧ページにフォームを設置します。リスト1の赤字の部分が追記されたコードになります。

リスト1 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>

<table>
<tr>
<th>Id</th>
<th>タスク内容</th>
<th>状態</th>
<th>作成日</th>
</tr>
<?php foreach ($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 h($task['Task']['created']) ?></td>
</tr>
<?php } ?>
</table>

見慣れないコードが沢山出てきましたが、頻繁に使うことになる類のコードですのでしっかり追っていきます。まずはform要素の開始タグです。

form要素の開始タグと、formTagメソッドにまつわるあれこれ

<form action="<?php echo h($html->url('/tasks/add')) ?>" method="post" style="margin-bottom:1em">

$htmlはHTMLヘルパーのオブジェクトです。読み込むヘルパーをコントローラ内で指定していなければ、HTMLヘルパーは自動で読み込まれます。

HTMLヘルパーのurlメソッドは、CakePHPアプリケーションのURLを、CakePHPを設置したディレクトリに応じて書き換えて返してくれるメソッドです。このメソッドを通してURLを記述しておくことで、Webサーバーのどのディレクトリにアプリケーションを配置しても正しくリンクがつながります。

form要素のaction属性においても同じことが言えますので、使用しています。第4回でセットアップした環境でこのコード動作させた場合、action属性の値は「/~gihyo/todo/tasks/add」となります。

method属性はHTTPのPOSTメソッドで送信することを明示しています。style属性にはフォーム下の表との間隔をとって見栄えを良くするために1文字ぶんのマージン(間隔)を設定するスタイルシートを記述しています。

ここではform要素のaction属性のみHTMLヘルパーで記述しましたが、実はHTMLヘルパーにはform要素の開始タグそのものを出力するformTagメソッドがあります。CakePHP的にはformTagメソッドを使用したほうがいいのかもしれませんが、1.1のHTMLヘルパーには開始タグを出力するメソッドはあれど閉じタグを出力するメソッドがありません。閉じタグはそのままHTMLとして「</form>」と記述することになるのですが、この書き方を行うとDreamweaverなどのWYSIWYGエディタでこのビューファイルを開いたときにエディタ上のレイアウトが大きく崩れることがあります。

form要素の開始タグのみヘルパーで書かれているため、エディタがform要素を認識できないからです。認識できないだけなら良いのですが中途半端な閉じタグが存在していると自動的に修正されたり、エラーを表示してしまうことがあります。分業の進んだWebアプリケーション開発においては、この問題は見過ごせません。

それでもformTagメソッドを使った方がメリットが大きいと判断されることもあるのですが、ここではとりあえず使わないことにします。

input要素

<p><?php echo $html->input('Task/content', array('size' => '40')) ?>

HTMLヘルパーのinputメソッドは、type="text"なinput要素、いわゆる「1行テキスト入力フォーム」を返すメソッドです。第一引数には、対象となるモデルとカラムを「モデル名/カラム名」でフォーマットして渡します。第二引数には連想配列でinput要素の属性と値を渡します。この例では size="40" となるように書かれています。これに加えてvalue属性の値を固定したければ、array('size' => '40', 'value' => 'デフォルト値') といったように連想配列の要素を次々に記述することで設定を加えることができます。

ここで先ほどform要素ではWYSIWYGエディタに配慮してacton属性のみをHTMLヘルパーで記述したこととの矛盾を感じたかもしれません。その感覚は確かにその通りで、input要素が完全にPHPコードに置き換えられているため、この例ではWYSIWYGエディタ上には入力フォームが表示されません。ただしform要素に比べて問題としては軽微といえます。input要素は置換要素であるため、表示されないだけでレイアウト上大きな問題となりにくいからです。

しかし表示されないことに変わりはなく、場合によってはinput要素はそのまま記述してもよいでしょう。その際にはname属性のフォーマットを「data[モデル名][カラム名](大括弧も記述⁠⁠」として記述する必要があります。

さらに注意点として、inputメソッドで記述できるinput要素のname属性には制限があります。どのような制限かといいますと、第一引数のフォーマットは必ず「モデル名/カラム名」である必要があります。このフォーマットを外れるとWarningエラーが出力され、正しく動作しません。

このため、モデルに関連しないパラメータを受け取るためにinputメソッドを使うと、フォーマットに沿うためだけに存在しないモデル名やカラム名をむりやり記述することになってしまいます。一応動作はしますが、混乱を招く原因となり得るため、あまりお勧めできません。

無用な混乱を避けるため、モデルに関連しないパラメータについてはinput要素はそのままHTMLで記述することをおすすめします。コントローラ側では $this->params['form']を参照してください。$_POSTと同じ構造で値が格納されています。

submitボタン

<?php echo $html->submit('タスクを追加') ?></p>

HTMLヘルパーのsubmitメソッドは、type="submit"なinput要素、いわゆる「送信ボタン」のHTMLを返すメソッドです。第一引数にはボタン名となるvalue属性の値を渡します。この部分もWYISIWYGエディタに配慮してHTMLで書いてしまっても差し支えありません。

完成した入力フォーム

図1のような画面が表示されたら、入力フォームの追加は完了です。

図1 入力フォームが追加された画面
図1 入力フォームが追加された画面
コントローラにaddアクションを追加する

tasksコントローラのaddアクションとして、フォームから送られてきたデータを追加する機能を実装します。リスト2の赤字の部分が追記されたコードになります。

リスト2 app/controllers/tasks_controller.php
<?php
class TasksController extends AppController {
  var $name = 'Tasks';
  var $uses = array('Task');
  function index() {
    $this->set('tasks', $this->Task->findAll(null, null, 'Task.created ASC'));
  }
function add() {
    if (!empty($this->data)) {
      if ($this->Task->save($this->data, true, array('content', 'created', 'modified'))) {
        $this->flash('タスクが追加されました', '/tasks');
        return;
      }
    }
    $this->redirect('/tasks');
  }
}

モデルへの値が渡されてきているかチェック

    if (!empty($this->data)) {

$this->dataにはモデルへの値が連想配列で格納されています。HTMLヘルパーのinputメソッドで記述されたinput要素の値などがそれにあたります。URLを入力しただけでデータが追加されないように、$this->dataに値が格納されている場合のみ追加処理を行うため、このようにして簡易に判定しています。

モデルへのデータの追加とフィールドのホワイトリスト

  if ($this->Task->save($this->data, true, array('content', 'created', 'modified'))) {

モデルのsaveメソッドは、DBへのレコードの追加や変更を行うメソッドです。第一引数には値を連想配列で指定します。$this->dataにはsaveメソッドに合う形で値が格納されているため、そのままsaveメソッドに渡すことができます。

第二引数はモデルのバリデーション機能を使用するかどうかのフラグです。必須ではなく、デフォルトはtrueです。まだバリデーション機能は実装していませんが、後々実装するのでここではtrueにしておきます。

第三引数は、第一引数に渡された値から使用するカラムを限定するために使用します。WebアプリケーションへのHTTPリスエストは誰でも自由に構築できるため、悪意をもったユーザによって設定された値で予期しない動作を引き起こされてしまう可能性があります。この引数を利用すると、予期しないカラムへの値のセットを避けることができます。この値は必須ではなく、デフォルト値はarray()でまったく限定されません。

ここではToDoの内容を格納するcontentカラムと、CakePHPが自動で設定する created(作成日時)および modified(更新日時)カラムを指定しています。

createdおよびmodifiedは常に指定しておくとよいでしょう。ちなみに$this->dataにcreatedとmodifiedが存在しても無視されるので、作成日時や更新日時を詐称されることはありません。

簡易な完了メッセージ表示

    $this->flash('タスクが追加されました', '/tasks');
    return;

コントローラのflashメソッドを使うと、簡易なリンクつきメッセージが表示できます。ここではsaveメソッドが成功したら完了を知らせるメッセージとToDo一覧へのリンクを表示しています。

$this->dataに値がないか、saveに失敗したときはリダイレクト

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

予期しないリクエストや問題が起きた際はToDo一覧ページへリダイレクトしています。これを最後に書いておかないとaddアクションに対応するビューを表示しようとして、ビューが存在しないエラーが発生してしまいます。

タスクを追加してみよう

tasksコントローラの書き換えが完了したら、実際にタスクを追加してみましょう。正常にタスクが追加できれば今回の作業は終了です図2~4⁠。

図2 タスク内容を入力して、タスクに追加ボタンを押す
図2 タスク内容を入力して、タスクに追加ボタンを押す
図3 完了メッセージが表示されるので、メッセージ上のリンクを辿る
図3 完了メッセージが表示されるので、メッセージ上のリンクを辿る
図4 タスクが追加された
図4 タスクが追加された

次回予定はタスクの状態変更の開発です。

おすすめ記事

記事・ニュース一覧