CakePHP で学ぶ Web システム構築

目次

Web サイトについて

Web サイトとは何か?

Web サイトとは何でしょうか?

言葉で理解するよりも目の前のパソコンのモニターを注視します。四角いウィンドウの中に表示されているこのコンテンツこそ他ならぬ Web サイトなのです。

Web サイトの例として Yahoo! Japan や Google や楽天市場などが挙げられます。しばしば Web サイトの内容を指してコンテンツと表現することがあります。心にとどめておくと後の役に立つかもしれません。

なお当サイトでは便宜上、 CGI や JavaScript 、データベース接続などを駆使して目的を達成する Web サイトを Web アプリケーションと表現します。

Web サイトが動作する仕組み

Web サイトの動作は、閲覧したい Web ページの URL を入力、または閲覧しているページのリンクをクリックすることに始まり、目的のページが Web ブラウザに表示されるまでが1回のサイクルです。別のページを閲覧したい場合は再び同じ動作の繰り返します。

下図は Web サイトが動作する仕組みです。

web_sys_000.jpg

それぞれの動作を細かく解説します。

  1. まず、ユーザは閲覧したい Web ページの URL を Web ブラウザのアドレスバーに入力、またはすでに閲覧中のページのリンクをクリックします。次に Web ブラウザが Web ページのデータが保存されているサーバへ「 URL にある Web ページのデータがほしい」とリクエストを送ります。これを HTTP リクエストと呼びます。
  2. HTTP リクエストを受けた Web サーバは、自身の記憶領域(ハードディスク等)から Web ブラウザから目的のデータを探し出します。
  3. 目的のデータが見つかった場合は、そのデータを Web ブラウザへ送信します。これを HTTP レスポンスと呼びます。
  4. HTTP レスポンスを受信した Web ブラウザは、ページの本文 ( HTML / XHTML ) と画像 ( jpg / gif / png )とを適切に配置して、最終的に閲覧者(ユーザ)が閲覧する画面を作ります。つまり、普段目にしている Web ブラウザの画面です。

Web サイトを支える基盤

web_sys_001.jpg

こんにちでは「 Web サイトを構築する」と一言に表現しても、実に様々な技術を使用しています。

クライアント側

web_sys_002.jpg

Web サイトの中核は HTML です。近年 XML 化して XHTML と呼ばれるようになったこの技術は、コンピュータにとっては記号の連続でしかない文章に、参照、引用、強調、正誤など、さまざまな情報を付加できるようになりました。

Web サイトの中核である HTML ( XHTML ) としばしば併用される技術が CSS です。 CSS は HTML ( XHTML ) に表現という情報を付加します。 HTML ( XHTML ) はあくまで文章の意味以上の情報を持ちませんでしたが CSS と併用することによってタイポグラフィ、ドロップキャップス、イタリック、ボールド、カラーリングなど HTML ( XHTML ) を修飾する表現が可能になります。

HTML ( XHTML ) や CSS は、ここからさらに画像や動画 ( FLASH 等 ) へリンクしたり、ページ内に表示することが可能です。

サーバ側

web_sys_003.jpg

一方の Web サーバは、Web ブラウザからのリクエスト、つまり「このページが見たい」という要求を待ち続け、要求を受けた際には、自身の記憶領域(ハードディスクなど)から目的のファイルを探し出して Web ブラウザへとデータを送信します。

Web サーバの記憶領域(ハードディスク等)には、前述の HTML ( XHTML ) や CSS 画像や動画が納められています。しかし Web サーバはデータの種類の違いをほとんど意識しません。つまり HTML データがほしい場合と画像データがほしい場合とで同じ通信方法を用います。その通信方法が後述の HTTP です。

Web ブラウザと Web サーバ間の通信

web_sys_004.jpg

ネットワークという切り口で見ると、しばしば Web ブラウザを指してクライアント、 Web サーバを指してサーバと呼ばれることがあります。 Web ではクライアントとサーバ間の通信に HTTP という仕様の通信方法を採用しています。 HTTP という共通の仕様があることによって、パソコンの OS を問わずにインターネットに接続して Web サイトを閲覧できるのです。また HTTP 通信が可能であれば、たとえば携帯電話からでも Web サイトを閲覧することが可能です(実際にそうなっています)。

Web サイトの限界

この章で述べたように Web サイトは主に HTML ( XHTML ), CSS, 画像, 動画から構成されています。しかし、普段目にしているインターネットには、前述の4つの要素だけでは説明できない機能を有している Web サイトがあると気づいた方もいらっしゃるかと思います。

Amazon.co.jp を想像してください。 Amazon.co.jp は EC サイトと呼ばれ、インターネットを通じて書籍などを購入することが可能です。購入ができる以上、お金のやりとりが発生するはずですが、 HTML ( XHTML ), CSS, 画像, 動画ではお金のやりとりまでカバーできません。

そこで Web システムという発想です。

Amazon.co.jp は表向き(つまり我々が目にする画面)こそ Web サイトですが、実は運営側にとっては Web システムという、実に様々なテクノロジからなる Web 上の EC システムという側面を持っているのです。

本記事ではこの Amazon.co.jp の運営側から見た Web サイトを指して Web システムと表現します。

詳細は次章で説明します。

Web システムについて

Web システムとは何か?

Web システムとは、インタフェースを Web ブラウザに持ち、ネットワークを通じて提供されるコンピュータシステムです。

インタフェースとはたとえば…

  • リンクをクリックする
  • ボタンをクリックする
  • テキスト入力欄に文字を入力する
  • 選択肢の中から該当する項目にチェックマークをつける
  • 選択肢の中からひとつを選んでマークをつける
  • プルダウンメニューの中からひとつを選択する
  • ドラッグ&ドロップで Web ブラウザ上の部品を整理する

…などが該当します。

Web システムが動作する仕組み

Web システムが動作する仕組みは、表向きは Web サイトほとんどと代わりありません。

下図を参照してください。

web_sys_100.jpg

(1)に相当する HTTP リクエストの送信と、(3)に相当する HTTP レスポンスの受信、(4)に相当する結果の表示が Web サイトの場合と同様の流れになっていますし、やりとりするデータも同じです。

しかし Web システムの場合は HTTP リクエストに対する HTTP レスポンスを返すに当たり、送信するデータの構築方法に違いがあります。

web_sys_101.jpg

上図は最初の図の(2)に相当する部分の詳細を表現したものです。白抜きの矢印は HTTP リクエスト、赤ベタの矢印は HTTP レスポンスを意味していますが、 HTTP リクエストを受けて HTTP レスポンスを返す間に、実に様々な技術を用いていることがわかります。

ここで重要なのは CGI ( Common Gateway Interface ) という枠に囲われた部分です。 Web システムの場合、 Web ブラウザが送信する HTTP リクエストに対して、目的のファイルを返すだけでなく、場合によっては PHP や Perl, Java といったプログラムの実行結果を HTTP レスポンスとして返すことがあります。この仕組みを CGI と呼びます(近年の Web サーバは HTTP デーモンの一部として機能しますが、本記事では区別しません)。

この CGI という技術によってクレジットカード決済など HTML や画像データだけでは不可能であった機能を実現しているのです。

Web システムを支える基盤

Web サイトは CGI の導入によって Web ブラウザの前にいるユーザとの双方向性が広がります。従来は作り置きの HTML を閲覧することしかできませんでしたが、 CGI によって PHP 等のプログラムと連携して結果の HTML を返すことで、在庫状況をリアルタイムで表示するといったページを出力することができます。

実は冒頭で Web サイトと紹介した Yahoo! Japan や Google も裏では CGI が動作しています。たとえば検索キーワードを入力して「検索」ボタンを押した場合、各社内で持つ Web サイトのデータベースから目的のページを検索し、ユーザにリストで表示しています。

CGI と連携して利用される代表的な技術は次に挙げたものです。

web_sys_102.jpg

データベース

もっとも代表的な技術がデータベースです。

たとえば Amazon.co.jp のような EC サイトで買い物をする際、まずユーザは EC サイトのトップページへアクセスします。次に検索をするなりカテゴリを選択するなりして目的の商品を探します。ユーザが目的の商品へたどり着く一歩手前、つまりその商品の情報を表示する際に Web システムは価格や在庫状況などをデータベースへ問い合わせして結果を HTML の一部に埋め込んで返します(たとえば「在庫状況の欄」など)。

もし商品数が少ない場合は商品の分だけあらかじめ在庫状況などの情報を掲載した HTML を用意しておけば問題ないのですが、 Amazon.co.jp のように巨大な Web サイトの場合、商品情報の整合性を保つため、必然的にデータベースを用いる結果となります(たとえば在庫数を超えて受注しないなど)。

各種ファイル

CGI は HTTP リクエストからプログラムを実行します。プログラムが実行されるということはファイル入出力が可能ということです。ファイル入出力が可能ということは、たとえばユーザがページを渡り歩いている様をロギング(記録)したり、ユーザがファイルをアップロードしたりできるということです。

データベースほど密接ではないものの、ファイルの入出力も CGI によって実現します。

Web サービス

Web サーバは HTTP リクエストによってユーザの要求を待ち続けますが、自身で HTTP リクエストを発行し、他のサイトから HTTP レスポンスを受けることができます。

有名な例が Amazon ECS です。 Amazon ECS を利用することで、たとえば自分の店舗で販売している商品について ” Amazon.co.jp 内で投稿されたレビュー ” を取得して、自分の店舗ページに掲載することができます。

このように HTTP の枠組みの上で実現するサービスを Web サービスと呼びます。 Web 2.0 の提唱で、 Web サービスは近年増加傾向にあります。

Web システムのメリット

Web システムのメリットは前述のように HTML とその周辺要素だけでは実現できない機能を提供することができるという点です。

また Web システムがどんなに複雑でも HTTP の枠組みの上で動作する以上、どの Web ブラウザでもサービスを利用できる点も重要です。

Web システムの構築

必要な環境(サーバ側)

Web システムの構築や運営に用いられるサーバ環境は次のようになります。

web_sys_200.jpg

Web システムでは Web サーバが CGI によってプログラムを起動し、 CGI によって起動したプログラムは内部でデータベースへアクセスします。 Web システム構築関連の資料にはこのような3層の表現がよく使われます。

この環境は運営時だけでなく開発時にも必要となりますが、経済的な事情から運営時と同じ環境を用意できないこともしばしばあります。そこで多くの場合は運営時の環境によく似た環境を開発用に用意することになります。

開発スペースを用意する方法として代表的な例を挙げます。

  • 同じサーバマシンを開発用にもうひとつ購入する
  • XAMPP 等を利用してクライアントに開発環境を作る
  • VMware 等の仮想マシン環境ソフトウェアを利用してクライアントに開発環境を作る
  • 運営時の環境に近いホスティングサービスを利用する

必要な環境(サーバ側) - Web サーバ

web_sys_201.jpg

Web システム開発には Web サイトの構築と同様に Web サーバが必要です。 HTTP リクエストを受けて HTTP レスポンスを返すという役割は Web サイトや Web システムの区別はないためです。

Web サーバというと、ハードウェアとしてのサーバという意味と、ソフトウェアとしての HTTP デーモンという意味が混同しますが、本記事では HTTP デーモンを指して Web サーバと呼びます。

HTTP デーモンとは HTTP リクエストを受け取り、目的のデータを HTTP レスポンスとして返す、つまり Web サーバの中核的な役割のソフトウェアです。

デーモンという用語の意味を知りたい方は、e-Words の解説へアクセスしてください。

Apache

Windows 以外のコンピュータにおいてデファクトスタンダードなシェアを持つ Web サーバです。

Web サイトや書籍などから簡単に情報を収集できるため学習も容易です。

今後の章では特に断りのない限り Apache を使用する前提で解説をします。

IIS

Windows に付属している Web サーバです。

.NetFramework など Windows のテクノロジを利用して Web システムを構築する際に用いられます。

必要な環境(サーバ側) - プログラム

web_sys_202.jpg

Web システムの利点は CGI によるプログラムとの連携で様々なサービスが実現できる点です。

大部分の HTTP リクエストでは HTTP レスポンスとして HTML ( XHTML ) を要求されます。つまり HTML ( XHTML ) を出力することが可能なプログラミング言語であれば CGI として動作可能ということです。

以下は Web システム開発によく用いられるプログラミング言語です。

PHP

Web サイトや Web システムの開発を専門とするスクリプト言語です。

C言語のようなルック&フィールを持ち、 PEAR という充実したライブラリ群と連携して、実に様々なサービスを開発できる点が特徴です。

本記事の後半では CakePHP と呼ばれる PHP のフレームワークを紹介します。

Perl

古くからある強力な文字列操作が特徴的なスクリプト言語です。

どんな Web サイトや Web システムでも、最終的には HTML を含んだ HTTP レスポンスを返す必要があることから、文字列の扱いに長じた Perl が利用されてきました。

現在では PHP のシェアが目立ちます。

Java

オブジェクト指向を見越した言語設計が特徴的なプログラミング言語です。

動作には Web サーバの他に Web アプリケーションサーバが必要であるうえ、 Web システムを効率的に開発するには様々なソフトウェア( Struts など)の選定が必要です。

前述の理由から小規模な Web システム開発には用いられることが少ないものの、 Java 周辺のコミュニティのセンスがとぎすまされており、新しいパラダイムの評価・採用には Java が圧倒的に有利です。

必要な環境(サーバ側) - データベース

web_sys_203.jpg

Web システムではデータベースと連携するケースがほとんどです。

データベースの主な利点としては…

  • 検索が早く、目的のデータを瞬時に参照できる
  • 信頼性があり、データの不整合がおこらない
  • 業務上のデータの集計にも対応できる

…が挙げられます。

ビジネスのうえではどれも必要不可欠なスペックです。

次のリストは Web サービスとの親和性が高く Web システム開発によく用いられるデータベースです。

PostgreSQL

オープンソースのデータベースです。

C言語のような型体系をもち、国内での情報も多いため、学習が容易なデータベースです。

MySQL

オープンソースのデータベースです。

検索の速さを徹底的に追求した実装が特徴的です。

Oracle

Oracle 社のデータベースです。

大規模な Web システムではデファクトスタンダード的な存在となっています。

DB2

IBM 社のデータベースです。

中小規模より大きい Web システム開発向きかと思われますが、中小規模の Web システム開発技術者に向けて、さかんに情報を発信されています。

Web サーバ - ホスティングサービスを選ぶ際のポイント

もし Web システムの開発環境をホスティングサービスで用意する場合は次のリストにあるチェックポイントに注目してください。

  • Web サーバはどのユーザの権限で動くのか?
    自分以外のユーザで動作する場合はパーミッションの設定(後述)が必要です。
  • CGI はどのユーザの権限で動くのか?
    自分以外のユーザで動作する場合はパーミッションの設定が必要です。契約者のユーザ権限での動作が理想です。
  • データベースの容量に上限があるか?
    たいていの場合(常識の範囲内で)無制限ですが、 Web サイトの容量を消費する場合があります。(常識の範囲内で)無制限が理想です。
  • sendmail は使用できるか?
    Web システムからメール送信する場合は sendmail が使用できると情報を得やすくなります。

以下は PHP を利用する場合です。

  • register_globals は OFF になっているか?
    OFF が理想です。もし ON の場合は .htaccess で OFF にできるか確認します。それでも OFF にできない場合はXSS の危険性を把握した上で開発を進めます。
  • magic_quotes_gpc は OFF になっているか?
    OFF が理想です。もし ON の場合は .htaccess で OFF にできるか確認します。それでも OFF にできない場合はXSS の危険性を把握した上で開発を進めます。

パーミッションの設定

多くのホスティングサービスではパーミッションという、ファイルやディレクトリに対して「誰がどれだけの操作ができるか?」を意味する数値があります。もし Web サーバや CGI によって動作するプログラムが、契約者(あなた)のディレクトリにあるファイルを書き換えたりする場合、「このファイルは他人が書き換えてもよし」と契約者(あなた)が印をつける必要があります。もし印が無い場合はエラーとなります。

くわしくはとほほのWWW入門等のサイトを参照にしてください。

register_globals = ON の危険性

PHP の設定である register_globals が ON の場合、 XSS というセキュリティ上の欠陥ができる可能性があります。この設定は OFF にしたくてもできない場合があります。具体的には .htaccess に PHP のディレクティブ(設定)を指定できない場合 OFF にできません。やむなく ON で開発する場合は XSS に注意してください。

XSS については Wikipedia 等を参照してください。

magic_quotes = ON の危険性

厳密には危険ではないのですが、 PHP の設定である magic_quotes が ON の場合、 HTTP リクエストに際して特定の文字が2倍に増殖します。たとえば \ がそれに当たり、送信するたびに \\ , \\\\, \\\\\\\\ と増えていきます。これは PHP のセキュリティ上の措置なのですが、セキュリティはもっと別の方法で向上させるべきですので OFF であることが理想です。

register_globals と同様に Wikipedia を参照してください。

必要な環境(クライアント側)

Web システム開発に必要なクライアント環境はソースコードを編集するエディタとファイルをサーバに転送する転送ソフトのふたつに大別されます。次の図は、このふたつを使った開発のサイクルです。

dev_cycle.jpg

このサイクルで効率よく開発するためにもエディタとファイル転送ソフトの選定が重要です。

必要な環境(クライアント側) - エディタ

Web システム開発においてエディタとはプログラムを書くためのソフトウェアです。 Windows 付属のメモ帳 ( notepad.exe ) と違い、コーディングをする上で実に様々な機能がエディタには備わっています。ここでは、その中でも特に重要と思われる要件を説明します。

なお、ここではエディタと統合開発環境( IDE )を同様の扱いとしています。

ハードタブ・ソフトタブ相互変換

ハードタブとソフトタブとを相互に変換する機能です。

ハードタブとはキーボードの Tab キーを押すことで入力する空白のことです。一方のソフトタブとはキーボードのスペースキーを連続で押すことによってハードタブと同様の空白を表現することです。

この機能はプロジェクト(複数人での開発)のコーディング規約を守るために必要です。

editor_000.png editor_001.png editor_002.png

再帰的な検索

キーワードから再帰的にファイルを検索する機能です(ここでいう再帰的とはサブディレクトリ(サブフォルダ)を含めての検索という意味です)。

これは、たとえば共通で使っている関数の名前が変更になった場合などに、キーワードを指定して対象のデータをすべてのディレクトリから検索したい場合に有効です。

editor_100.png editor_101.png

タグジャンプ

タグジャンプとはファイル名と行番号をセットにした特定のフォーマット(下図を参考)から、そのファイルの中の該当する行へカーソルをジャンプさせる機能です。

これは前述の再帰的な検索と組み合わせて、共通で使用している関数名やクラス名を変更する際に、その名前を参照している場所を見つけて修正する場合に有効です。

editor_200.png editor_201.png

行番号への移動

行番号への移動は開いているファイルの指定した行へ移動する機能です。

プログラムをテスト中、エラーに遭遇してエラー番号まで移動したい場合に有効です。

editor_300.png editor_301.png

矩形編集

矩形編集は開いているファイルを Excel のようなスプレッドシートを使用しているかのようにテキストを編集する機能です。矩形とは四角形のことです。

これはインデントの微調整や関数の書式をそろえるなど様々なシーンで有効です。

editor_400.png editor_401.png editor_402.png

必要な環境(クライアント側) - ファイル転送ソフト

ファイル転送ソフトは、ソースコードをサーバへアップロードする際に用います。

アップロードの方法は FTP か SCP が代表的ですが、サーバ管理を担当している方に連絡してどちらが使用できるか確認します。近年では SCP が多く用いられる傾向にあります。

ファイルの同期

ファイル転送ソフトに求められる機能はファイルの同期です。

ソースファイルが多くなるにつれ、編集したファイルを見つけてアップロードする手間が増えます。かといってすべてのファイルをアップロードしていたのでは無駄に時間がかかります。そこでタイムスタンプから更新されたファイルを自動で見つけ出し、最小限のファイルをやりとりして、クライアントとサーバを最新の状態に保つ同期機能が有効というわけです。

file_frns_000.png

代表的なエディタとファイル転送ソフト

ここでは代表的なエディタとファイル転送ソフトを紹介ます。

エディタ

ファイル転送ソフト

データベースの基礎知識

データベースとは?

データベースとは Web システムなどのアプリケーションが利用するデータの集合です。また、データの集合を管理する機構も意味に含まれることがあります。代表的なデータベースとして PostgreSQL, MySQL, Oracle, DB2 が名を連ねます。

一般的にデータベースのイメージは複数のテーブルとテーブル同士の関係で表現されます。テーブルとは Excel のようにカラム(列)と行レコード(行)からなる方眼紙のようなイメージのデータ格納スペースです(次節で詳細)。

では、なぜ Excel ではなくデータベースを用いられるのか?

データベースには次のようなメリットがあります。

  • SQL のサポート
    SQL によって様々な観点からデータを参照できます。
  • 安全にトランザクション(取引)できる
    もし何らかの失敗でトランザクション(取引)が完了しなかった場合、取引前の状態に戻すことができます。
  • 同時アクセスに対応できる
    もし複数人から同じデータのアクセスがあってもデータが破壊されません。
  • 型と制約によって不正なデータが入らない
    型はテーブルのカラムに対して数値や文字列や日付などの縛りを設けます。制約はこのカラムには同じ値は入力できないなどカラムとテーブル全体に縛りを設けます。
  • ユーザの権限を管理できる
    どのユーザがどこまでの機能を使えるか実に細かく管理可能です。
  • 容易にバックアップとリカバリができる
    サーバの移転や障害復旧に際するバックアップとリカバリの手段が確立しています。

テーブル

データベースの中心的なデータ集合がテーブルです。当然 Web システムにおいてもっとも頻繁にやりとりする相手になります。テーブルのイメージは Excel のようにカラム(列)とレコード(行)が交差した、方眼紙のようなレイアウトをしています。

  カラム カラム カラム カラム
レコード データ データ データ データ
レコード データ データ データ データ
レコード データ データ データ データ

プライマリキー

プライマリキーとはレコードを特定するために用意されたカラムとその値です。プライマリキーはテーブル内で一意(重複なし)の値をとります。

前述のようにテーブルは Excel のようなレイアウトをしていますが Excel とは違い行番号がついていません。 Excel は行番号によってレコードを特定できますが、テーブルには行番号がありませんのでレコードを特定できない可能性があります。もしレコードが特定できなければ、人間の目視によるチェックで対象のレコードを探し出し、更新・削除する必要があります。これはもはやコンピュータシステムではありません。

プライマリキーには連番の数値や固定長の文字列が使われます。下図は数値によってレコードを特定できるようにしたテーブルのイメージです。

PK カラム カラム カラム カラム
1 データ データ データ データ
2 データ データ データ データ
3 データ データ データ データ

※ PK = Primary Key (プライマリキー)

マスタとトランザクション

データベースの機能ではありませんが、テーブルはマスタテーブルとトランザクションテーブルの2種類に大別されます。

マスタテーブルとは会員データや取引先データなど、取引の基幹となるデータを格納したテーブルです。

トランザクションテーブルとは売買記録などマスタテーブルがデータ同士が取引し、その過程で発生したデータを格納するテーブルです。

テーブル内にあるデータのやりとり

テーブル内のデータは SQL によって参照したり更新をします。運用しているデータベース上ではレコードに対して参照、追加、更新、削除の操作ができます。

レコードの参照(取得) - SELECT

SELECT はテーブルから目的のレコードを取得して参照できるようにします。

sample_records
id name division section mobile
1 一郎 営業部 営業課 070-1111-1111
2 次郎 製造部 第一製造課 080-2222-2222
3 三郎 経営企画部 (null) 090-3333-3333
SELECT
  -- 参照したいカラムを挙げます
  name,
  division,
  section,
  mobile
FROM
  -- 目的のデータがあるテーブル名を指定します
  sample_records
WHERE
  -- テーブルの中のどのデータを取得するのか、
  -- その条件を書きます
  id = 2;
SELECT によって取り出された内容
id name division section mobile
2 次郎 製造部 第一製造課 080-2222-2222

取得したレコードは各言語によって最適なデータ型で参照できます。たとえば PHP の場合、 SELECT の結果は配列に入ります。

レコードの追加 - INSERT

INSERT はテーブルに新規のレコードを追加します。

sample_records
id name division section mobile
1 一郎 営業部 営業課 070-1111-1111
2 次郎 製造部 第一製造課 080-2222-2222
3 三郎 経営企画部 (null) 090-3333-3333
INSERT INTO
  -- データを追加したいテーブルを指定します
  sample_records
  -- 追加するデータのカラム名を挙げます
  ( id, name, division, section,mobile )
VALUES
  -- 追加するデータの値を挙げます
  -- 文字列は ' でくくります
  ( 4, '四郎', '総務部', '総務課' , '070-4444-4444' );

なお INSERT では必須入力ではないカラムを省略することができます。カラムが必須入力かの指定はテーブルを作る際に決定します。

sample_records
id name division section mobile
1 一郎 営業部 営業課 070-1111-1111
2 次郎 製造部 第一製造課 080-2222-2222
3 三郎 経営企画部 (null) 090-3333-3333
4 四郎 総務部 総務課 070-4444-4444

レコードの更新 - UPDATE

UPDATE はテーブル内のすでにあるレコードの値を更新します。

sample_records
id name division section mobile
1 一郎 営業部 営業課 070-1111-1111
2 次郎 製造部 第一製造課 080-2222-2222
3 三郎 経営企画部 (null) 090-3333-3333
4 四郎 総務部 総務課 070-4444-4444
UPDATE
  -- データを更新したいテーブルを指定します
  sample_records
SET
  -- 更新したい値を名前に代入する形で挙げます
  division = null,
  section = '庶務二課',
  mobile = '000-0000-0000'
WHERE
  -- 目的のレコードを特定する条件( PK など)を指定します
  id = 3;
sample_records
id name division section mobile
1 一郎 営業部 営業課 070-1111-1111
2 次郎 製造部 第一製造課 080-2222-2222
3 三郎 (null) 庶務二課 000-0000-0000
4 四郎 総務部 総務課 070-4444-4444

INSERT と同様に必須入力ではないカラムを省略することができます。カラムが必須入力かの指定はテーブルを作る際に決定します。

なお UPDATE では WHERE を省略した場合、テーブル内のすべてのレコードが更新されてしまうため注意が必要です。 WHERE の内容が不完全でレコードがひとつに特定できなかった場合も同様に WHERE にマッチしたすべてのレコードが更新されます。

レコードの削除 - DELETE

DELETE はテーブル内のレコードを削除します。

sample_records
id name division section mobile
1 一郎 営業部 営業課 070-1111-1111
2 次郎 製造部 第一製造課 080-2222-2222
3 三郎 (null) 庶務二課 000-0000-0000
4 四郎 総務部 総務課 070-4444-4444
DELETE FROM
  -- 削除したいデータがあるテーブルを指定します
  sample_records
WHERE
 -- 目的のレコードを特定する条件( PK など)を指定します
  id = 3;
sample_records
id name division section mobile
1 一郎 営業部 営業課 070-1111-1111
2 次郎 製造部 第一製造課 080-2222-2222
4 四郎 総務部 総務課 070-4444-4444

UPDATE と同様に WHERE を省略した場合、テーブル内のすべてのレコードが削除されてしまうため注意が必要です。 WHERE の内容が不完全でレコードがひとつに特定できなかった場合も同様に WHERE にマッチしたすべてのレコードが削除されます。

リレーション

データベース、とりわけリレーショナルデータベースの強みは複数のテーブルに対するデータの取得( SELECT )です。 SELECT 時にテーブル同士の関係を付加することで、複数テーブルにまたがるデータを SQL ひとつで取り出すことが可能です。このテーブル同士の関係をリレーションと呼びます。

以下は代表的なリレーションの例です。

Has one.

テーブルAにあるレコードが、テーブルBのレコードのどれかひとつに対応している状態です。

たとえば社員テーブルのレコード(社員)と年金テーブルのレコード(年金加入年)が年金番号で対応している関係です。

社員テーブル
emp_id name division pension
1 太郎 200 12345714
2 次郎 100 13533678
3 三郎 200 65432345
4 四郎 100 52345554
    
社員テーブル
pension joined
12345714 2000
13533678 1982
65432345 1978
52345554 2001
結合後のイメージ
id name pension joined
1 太郎 12345714 2000
2 次郎 13533678 1982
3 三郎 65432345 1978
4 四郎 52345554 2001

Belongs to.

テーブルAにあるレコードが、テーブルBのレコードに属している状態です。

たとえば部署テーブルのレコード(部署)に社員テーブルのレコード(社員)が属している状態です。

部署テーブル
div_id name
100 営業部
200 総務部
300 法務部
400 財務部
    
社員テーブル
emp_id name division pension
1 太郎 200 12345714
2 次郎 100 13533678
3 三郎 200 65432345
4 四郎 100 52345554
結合後のイメージ
div_id name emp_id name division pension
100 営業部 2 次郎 100 13533678
100 営業部 4 四郎 100 52345554
200 総務部 1 太郎 200 12345714
200 総務部 3 三郎 200 65432345

Has many.

テーブルAのあるレコードがテーブルBのレコードを複数所持している状態です。

たとえば社員テーブルのレコード(社員)が定期券テーブルのレコード(定期券)複数所持している関係です。

社員テーブル
emp_id name division pension
1 太郎 200 12345714
2 次郎 100 13533678
3 三郎 200 65432345
4 四郎 100 52345554
    
定期券テーブル
tk_id emp_id take off charge
2400 1 神保原 上野 14500
2401 1 上野 品川 4000
2402 4 横浜 渋谷 8900
2403 3 中野 高円寺 4000
結合後のイメージ
emp_id name tk_id emp_id take off charge
1 太郎 2400 1 神保原 上野 14500
1 太郎 2401 1 上野 品川 4000
4 四郎 2402 4 横浜 渋谷 8900
3 三郎 2403 3 中野 高円寺 4000

Has many and belongs to many.

テーブルAのレコードとテーブルBのレコードが複数個と複数個で結合している状態です。

たとえば社員テーブルのレコード(社員)が会議室テーブル(会議室)を予約した記録などの関係です。

社員テーブル
emp_id name division pension
1 太郎 200 12345714
2 次郎 100 13533678
3 三郎 200 65432345
4 四郎 100 52345554
    
会議室テーブル
mtg_id name
10 大会議室
11 第1会議室
12 第2会議室
13 ミーティングスペース
会議室利用記録テーブル
emp_id name mtg_id name start end
3 三郎 13 ミーティングスペース 10:00 12:00
3 三郎 11 第1会議室 13:00 15:00
1 太郎 12 第2会議室 20:00 21:00
4 四郎 13 ミーティングスペース 9:30 10:00

PHP に見る Web システム

Web システムにおける PHP の役割

Web システムにおける PHP の役割は、ソースファイルに書かれているビジネスロジックやデータベース等と連携しながら目的を達成し、 HTTP レスポンスの本文に HTML を埋めることです。

下図は Web サイトとの違いです。

  HTTP リクエスト
の中身
Web サーバの役割 HTTP レスポンス
の中身
Web サイト URL *.html を見つけて返す HTML ( XHTML )
Web システム URL *.php を実行して結果を返す HTML ( XHTML )

Web サイトと Web システムとの違いは Web サーバの役割のみという事がわかります。 Web システムは Web ブラウザがあれば導入できるというメリットはここに起因しています。

websys_on_php_000.jpg

PHP のソースファイルと HTTP レスポンス

PHP は HTML の構文の中に < ?php ?> というキーワードでプログラムを埋め込むことが可能です。また、その逆に PHP のコードの中に HTML を書くことも可能です。

ソースコードは次のようになります。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>PHP のサンプル</title>
</head>
<body>

<?php // ↓インラインで PHP の命令を埋め込んでいます ?>
<p>現在の時刻は <?php echo date("H 時 i 分 s 秒") ?> です。</p>

<?php
  // 多くの言語と同様にループを書くこともできます
  foreach (range(1, 5) as $num) {
    // PHP コードの中に HTML を書くこともできます
    ?><p>現在 <?php echo $num ?> 回目のループです。</p><?php
  }
?>

</body>
</html>

実行後の HTTP レスポンスには次のようなデータが入ります。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>PHP のサンプル</title>
</head>
<body>

<p>現在の時刻は 16 時 30 分 12 秒です。</p>

<p>現在 1 回目のループです。</p>
<p>現在 2 回目のループです。</p>
<p>現在 3 回目のループです。</p>
<p>現在 4 回目のループです。</p>
<p>現在 5 回目のループです。</p>

</body>
</html>

PHP の代表的な周辺技術

PHP は PEAR をはじめとする様々な周辺技術と連携することができます。 Web システム開発では生産性と品質を高める目的からこれらの技術を用いる機会も少なくありません。ここでは代表的な周辺技術を紹介します。

PEAR

PEAR は PHP で書かれた PHP のためライブラリです。基本的に必要なファイルをすべてコピーすればホスティング環境にあっても使用することが可能です。

  • DB
    データベースとのデータのやりとりを抽象化するライブラリです。
  • HTML_QuickForm
    HTML の form エレメントの生成から入力データの検証、データの持ち回り、確認表示までカバーする form 関連要素のライブラリです。
  • HTTP_Request
    スクリプト中から別のサーバへ HTTP リクエストを発信するライブラリです。 PHP が SSL を含んでコンパイルされている必要があるため使用しにくいパッケージですが、他の PEAR のパッケージがこれに依存していることがありますので注意が必要です。

ライブラリ

  • Smarty
    PHP のテンプレートエンジンです。 PEAR の HTML_QuickForm や Dreamweaver などと連携が可能で、生産性の向上を目的に導入されることがあります。

フレームワーク

フレームワークとはコンピュータシステムを構築する上での土台となるライブラリです。開発者が業務ロジックの設計に集中できるようさまざまな下位処理を肩代わりする役割を持ちます。

  • Mojavi
    自由度の高いフレームワークです。 Mojavi の上に別のフレームワークを構築する事も可能です。
  • Ethna
    GREE の基礎に採用され、一般にも公開されているフレームワークです。
  • Symfony
    非常に多機能なフレームワークです。 scaffold や国際化対応、キャッシュ機構など企業向け開発もサポートします。
  • CakePHP
    本記事で採用しているフレームワークです。外部のライブラリに依存せず、 PHP4 でも動作が可能です。

CakePHP で作る Web システム

はじめに

本記事では CakePHP による Web システム の構築方法を紹介します。

CakePHP とは PHP のフレームワークです。 CakePHP は Web システムの土台となり、様々な下位処理を肩代わりします。これによって我々は、その Web システムによって解決すべき業務上の問題に集中することができます。

なぜ CakePHP か?

なぜ CakePHP を提案するのか?

過去記事の転載ですが、まさにこれが理由となっています。

1. 商用利用が可能
CakePHP は、 MIT ライセンスです。商用利用が可能です。
2. PHP4 or PHP5 かつ PEAR 非依存
CakePHP は、同じソースコードで PHP4 と PHP5 のどちらでも動作します。また、 PEAR を使用していないので、ホスティングをはじめとする多くの環境で動作します。
3. データベースと連携し SQL を wrapp した CRUD が可能
CakePHP は、独自のルールでデータベースのアソシエーション(リレーション)を理解し、ほとんどのスクリプトを SQL を用いずに記述可能です。
4. scaffolding が可能
CakePHP は、 RoR のような scaffolding を実現します。開発の初期にはマスタテーブルの仕様が決まっていないこともしばしばあることと思います。 scaffold で必要なデータとデータの形式を管理すれば、主となるロジックの実装に着手できるかも知れません。
5. MVC パターンを採用
CakePHP は、 Web アプリケーション開発において実績のある MVC パターンを用いた開発が可能です。
6. 見栄えの良い URL
CakePHP は、 mod_rewrite を用いずとも見栄えの良い URL をデザインすることが可能です。もちろん、 CakePHP は mod_rewrite にも対応しています。
7. 柔軟なテンプレート
CakePHP は、テンプレートを PHP で記述します。コーダーは PHP のみを理解すればテンプレートを記述することが可能です(ちなみに、テンプレートを Smarty で記述する方法も随所で見つけることが可能です)。また、エレメントを利用することで HTML のコードを再利用しやすくなります。たとえばログインフォームを全画面で出力する手間が省けるかも知れません。
8. 豊富な標準ライブラリ
CakePHP は、再利用可能なロジックをライブラリ(ビヘイビア、コンポーネントヘルパー)として提供します。たとえば、今までブラウザで動作していたリクエストを Ajax で記述し直す手間が省けるかも知れません。
9. サニタイジング
CakePHP は、データをサニタイズするライブラリを持っています。さらに徹底的にデータを洗いたい場合には、リクエストとレスポンスの全変数をフックすることが可能です(派生クラスを作る必要がある)。
10. アクセスコントロール
CakePHP は、標準にして十分なアクセスコントロールを持っています。アクセスコントロールにはデータベースを用いる方法と、設定ファイルを用いる方法の2種類があり、 Web アプリケーションの規模に応じて適切な手段を選択することが可能です。
11. ビューのキャッシュ
CakePHP は、以前処理したことのあるビューを記憶しておき、同じリクエストに対するレスポンスをキャッシュから返すことが可能です。キャッシュを用いることでレスポンスが向上します。また、キャッシュしないリクエストを指定したり、データベースの内容が変更された場合にキャッシュを再構築することが可能です。強制的にキャッシュをクリアすることも可能で、キャッシュの要件を実現します。
12. シンプルなソースの設置
CakePHP は、展開したアーカイブを Web サーバにディレクトリごと転送し、設定ファイルの一部(データベース接続情報)を編集するだけで動作します( Web サーバの設定によっては1カ所パーミッションの変更が必要)。 Web アプリケーションを正式リリースする際、ドキュメントルート以下には設定ファイルやロジックを置きたくないかも知れません。その場合でも定数をいくつか編集するだけで、そういったファイルをドキュメントルート以下から移動することが可能です。

CakePHP を使う準備

CakePHP を使うには PHP が動作する Web サーバ、データベース、 SQL を実行できる環境、 CakePHP のソースファイルが必要です。

  • PHP が動作する Web サーバ
    自分で用意できなければホスティングの利用をおすすめします。また PHP のインストールされているディレクトリも把握してください。
  • データベース
    自分で用意できなければホスティングの利用をおすすめします。
    CakePHP からデータベースを利用するために次の情報が必要です。必ずサーバ管理担当者(ホスティングの場合はホスティング業者)へ問い合わせて把握して降りてください。

    • サーバのホスト名(もしくは IP アドレス)
    • データベース名
    • データベースの接続ユーザ名
    • データベースの接続パスワード
  • SQL を実行できる環境
    自分で用意できなければホスティングの利用をおすすめします。
    具体的には phpMyAdmin や phpPgAdmin など、データベースが使えるホスティングサービスではほぼ 100% 用意されていますので問い合わせてください。
  • CakePHP のソースファイル
    CakePHP の本家サイトから入手してください。

次節よりの解説に必要となりますので以下のチェックリストを確認してください。

ファイルを Web サーバへアップロードする手順がわかるか?
Web サーバの自分のドキュメントルートがわかるか?
Web サーバのファイルのパーミッションの変更方法がわかるか?
PHP がインストールされているディレクトリがわかるか?
データベースのホスト名(または IP アドレス)がわかるか?
データベースの名前がわかるか?
データベースのユーザ名がわかるか?
データベースのパスワードがわかるか?
データベースへ SQL を実行する方法がわかるか?

ひとつでも不足していると先へ進めませんので、サーバ管理者へお問い合わせください。

さくらインターネットの場合のチェックリストに対する答え

ホスティングサービスを提供している、さくらインターネットを利用する場合のチェックリストに対する答えです。

さくらインターネットでは契約に際してアカウント名(ユーザ名)として任意の名前をつける必要があります。この名前を覚えておいてください。

  • ファイルを Web サーバへアップロードする手順がわかるか?
    WinSCP 等で “(アカウント名).sakura.ne.jp” へ接続します。
  • Web サーバの自分のドキュメントルートがわかるか?
    /home/(アカウント名)/www です。
  • Web サーバのファイルのパーミッションの変更方法がわかるか?
    WinSCP 等で変更します。
    ちなみにさくらインターネットでは PHP がアカウント名の権限で動作するため、よほどの事情がない限りパーミッションを変更する必要はありません。
  • PHP がインストールされているディレクトリがわかるか?
    /usr/local/bin/php です。
  • データベースのホスト名(または IP アドレス)がわかるか?
    下図を参照。
  • データベースの名前がわかるか?
    下図を参照。データベースのユーザ名と同じ名前になります。
  • データベースのユーザ名がわかるか?
    下図を参照。データベースの名前と同じ名前になります。
  • データベースのパスワードがわかるか?
    下図を参照。ちなみに一番最初にアクセスした場合はパスワードを新規に設定するための入力を求められます。
  • データベースへ SQL を実行する方法がわかるか?
    下図を参照。

データベース関連情報はコントロールパネルへログインすることで確認することができます。

sakura_000.png sakura_001.png sakura_002.png 

CakePHP のインストールと設定

ダウンロードした CakePHP のアーカイブファイル(圧縮ファイル)を解凍して、ドキュメントルートの下にコピーしてください。

たとえばドキュメントルートが /home/user/www の時は次のようになります。

ドキュメントルート
/home/user/www
  ├─app
  │  ├─config
  │  │  └─sql
  │  ├─controllers
  │  │  └─components
  │  ├─models
  │  ├─plugins
  │  ├─tmp
  │  │  ├─cache
  │  │  │  ├─models
  │  │  │  ├─persistent
  │  │  │  └─views
  │  │  ├─logs
  │  │  ├─sessions
  │  │  └─tests
  │  ├─vendors
  │  ├─views
  │  │  ├─elements
  │  │  ├─errors
  │  │  ├─helpers
  │  │  ├─layouts
  │  │  └─pages
  │  └─webroot
  │      ├─css
  │      ├─files
  │      ├─img
  │      └─js
  ├─cake
  │  ├─config
  │  ├─libs
  │  │  ├─controller
  │  │  │  └─components
  │  │  │      ├─dbacl
  │  │  │      │  └─models
  │  │  │      └─iniacl
  │  │  ├─model
  │  │  │  ├─datasources
  │  │  │  └─dbo
  │  │  └─view
  │  │      ├─helpers
  │  │      └─templates
  │  │          ├─elements
  │  │          ├─errors
  │  │          ├─layouts
  │  │          ├─pages
  │  │          └─scaffolds
  │  └─scripts
  │      └─templates
  │          ├─skel
  │          │  ├─config
  │          │  │  └─sql
  │          │  ├─controllers
  │          │  │  └─components
  │          │  ├─models
  │          │  │  └─behaviors
  │          │  ├─plugins
  │          │  ├─tmp
  │          │  │  ├─cache
  │          │  │  │  ├─models
  │          │  │  │  ├─persistent
  │          │  │  │  └─views
  │          │  │  ├─logs
  │          │  │  ├─sessions
  │          │  │  └─tests
  │          │  ├─vendors
  │          │  ├─views
  │          │  │  ├─elements
  │          │  │  ├─errors
  │          │  │  ├─helpers
  │          │  │  ├─layouts
  │          │  │  └─pages
  │          │  └─webroot
  │          │      ├─css
  │          │      ├─files
  │          │      ├─img
  │          │      └─js
  │          └─views
  ├─docs
  └─vendors

CakePHP の配置ができましたら次はデータベースへの接続設定です。

次の場所にあるファイルの名前を database.php に変更してファイルを開いてください。

ドキュメントルート
  └─app
      └─config
          └─database.php.default
                ↓ (1)名前を変更する
            database.php → (2)開く

内容はこのようになっています。

<?php
/* SVN FILE: $Id: database.php.default 4409 2007-02-02 13:20:59Z phpnut $ */
/**
 * This is core configuration file.
 *
 * Use it to configure core behaviour ofCake.
 *
 * PHP versions 4 and 5
 *
 * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
 * Copyright 2005-2007, Cake Software Foundation, Inc.
 *                      1785 E. Sahara Avenue, Suite 490-204
 *                      Las Vegas, Nevada 89104
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource

 * @copyright     Copyright 2005-2007, Cake Software Foundation, Inc.
 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
 * @package       cake
 * @subpackage    cake.app.config
 * @since         CakePHP(tm) v 0.2.9
 * @version       $Revision: 4409 $
 * @modifiedby    $LastChangedBy: phpnut $
 * @lastmodified  $Date: 2007-02-02 07:20:59 -0600 (Fri, 02 Feb 2007) $
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
 */
/**
 * In this file you set up your database connection details.
 *
 * @package    cake
 * @subpackage cake.config
 */
/**
 * Database configuration class.
 * You can specify multiple configurations for production, development and testing.
 *
 * driver =>
 * mysql, postgres, sqlite, adodb, pear-drivername
 *
 * connect =>
 * MySQL set the connect to either mysql_pconnect of mysql_connect
 * PostgreSQL set the connect to either pg_pconnect of pg_connect
 * SQLite set the connect to sqlite_popen  sqlite_open
 * ADOdb set the connect to one of these
 * (http://phplens.com/adodb/supported.databases.html) and
 * append it '|p' for persistent connection. (mssql|p for example, or just mssql for not persistent)
 *
 * host =>
 * the host you connect to the database
 * MySQL 'localhost' to add a port number use 'localhost:port#'
 * PostgreSQL 'localhost' to add a port number use 'localhost port=5432'
 *
 */
class DATABASE_CONFIG
{
   var $default = array('driver' => 'mysql',
                        'connect' => 'mysql_connect',
                        'host' => 'localhost',
                        'login' => 'user',
                        'password' => 'password',
                        'database' => 'project_name',
                        'prefix' => '');

   var $test = array('driver' => 'mysql',
                     'connect' => 'mysql_connect',
                     'host' => 'localhost',
                     'login' => 'user',
                     'password' => 'password',
                     'database' => 'project_name-test',
                     'prefix' => '');
}
?>

このファイルの下部にある次の箇所にデータベースの接続情報を記述してください。

var $default = array('driver' => 'mysql',
                     'connect' => 'mysql_connect',
                     'host' => 'localhost',
                     'login' => 'user',
                     'password' => 'password',
                     'database' => 'project_name',
                     'prefix' => '');

たとえばデータベース名が “my_database” 、ホスト名(または IP アドレス)が “localhost” 、データベースのユーザ名が “db_user” 、ユーザのパスワードが “pass” の場合は次のように書きます。

var $default = array('driver' => 'mysql',
                     'connect' => 'mysql_connect',
                     'host' => 'localhost',
                     'login' => 'db_user',
                     'password' => 'pass',
                     'database' => 'my_database',
                     'prefix' => '');

ここまでの例は MySQL の場合ですが、データベースが PostgreSQL の場合は次のようになります。

var $default = array('driver' => 'postgres',
                     'connect' => 'pg_pconnect',
                     'host' => 'localhost',
                     'login' => 'db_user',
                     'password' => 'pass',
                     'database' => 'my_database',
                     'prefix' => '');

データベースの接続情報が記述できましたら Web ブラウザでドキュメントルートを開いてください。

次のような画面が表示されればインストールは完了です。

cakephp_installed.jpg

チュートリアル - ブックマークリストを作る

CakePHP の入門としてブックマークリストを作っていきます。ただのブックマークリストでは面白みがありませんので、自分が登録したブックマークのサイトについて第三者が評価できるようにします。

仕様は次の通りです。

  • ブックマークとしてサイト名、アドレス( URL )、詳細説明を登録できる
  • すでに登録したブックマークの一覧を閲覧できる
  • すでに登録したブックマークの情報を編集できる
  • 不要なブックマークを削除できる
  • 第三者がブックマークが示すサイトを評価できる
  • ひとつのブックマークに寄せられた評価を閲覧できる

チュートリアル - ブックマークの管理

CakePHP では、まずモデルの元となるテーブルを作成します。

モデルとは Web システムにおいて、主にビジネスロジックを遂行したり、データの永続化(データベースに保存)を担当するオブジェクトです。 CakePHP ではテーブルひとつに対してモデルをひとつ作成します。詳しくは CakePHP における MVC パターンをご覧ください。

今回はこのようなテーブルを作成します。

CREATE TABLE bookmarks
(
  id           INTEGER      NOT NULL AUTO_INCREMENT,
  site_name    VARCHAR(100) NOT NULL,
  url          VARCHAR(255) NOT NULL,
  description  VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
);

id はプライマリキー、 site_name はサイト名、 url は URL 、 description は詳細説明を意味します。

ここで CakePHP にはテーブルの命名に規約がありますので注意してください。詳しくはフォーラム(CakePHPシステムデザイン)をご覧ください。

ここでは抜粋を挙げます。

  • テーブル名は英単語の複数形
  • プライマリキーの名前は id
  • 外部参照キーの名前は対象のテーブル名の英単語を単数形にして後ろに _id を付加(後述)

テーブルが作成できましたらブラウザでドキュメントルートの URL の後ろに “bookmarks/” をつけたアドレスへアクセスしてください。たとえばドキュメントルートの URL が http://www.blueocean.bz/ の場合は http://www.blueocean.bz/bookmarks/ へアクセスします。

アクセスすると次のような画面が出てきます。

cakephp_tut_000.png

これはブックマークリストを管理(登録削除など)するためのコントローラが必要という意味です。

コントローラについての詳細はモデルと同様に CakePHP における MVC パターンを参照してください。

コントローラはモデルのメソッドを駆使して目的を達成するためのオブジェクトです。 CakePHP ではコントローラが HTTP リクエストを処理して HTTP レスポンスを返すために必要なデータをビューへと渡す役割があります。コントローラはもっとも Web システムという枠組みに依存しています。それゆえ一番最初に警告が発せられる重要なオブジェクトとなっています。

ビューについての詳細はモデルと同様に CakePHP における MVC パターンを参照してください。

警告画面では /app/controllers ディレクトリの下に bookmarks_controller.php というファイル名で次のようなクラスを書いてくださいというメッセージが添えられていますのでその通りにしてください。

/app/controllers/bookmarks_controller.php

<?php
class BookmarksController extends AppController
{
  var $name = 'Bookmarks';
}
?>

クラスが書けましたら再び同じアドレスを読み込んでください。今度は次のような画面が出てきます。

cakephp_tut_001.png

今度はビジネスロジックの遂行や永続層(データベース)との接点であるモデルが無いという意味の警告です。 BookmarksController から見ると、登録・削除・編集・一覧の対象であるモデルが無いと動作のしようがないというわけです。このように CakePHP の最も単純な例ではモデル・コントローラ・ビューがモデル名(今回の場合は Bookmark )で一組になります。

警告画面では /app/models ディレクトリの下に bookmark.php というファイル名で次のようなクラスを書いてくださいというメッセージが添えられていますのでその通りにしてください。

/app/models/bookmark.php

<?php
class Bookmark extends AppModel
{
  var $name = 'Bookmark';
}
?>

クラスが書けましたら再び同じアドレスを読み込んでください。今度は次にような画面が出てきます。

cakephp_tut_002.png

今度は再びコントローラの警告ですが、これはアクションが定義されていないという意味です。

現在 /bookmarks/ というアドレスを用いていますが、厳密にはこの後にアクション名が続きます。コントローラはアクション名とコントローラがもつメソッド名とをつきあわせて一致したメソッドの内容を実行します。

たとえば追加であれば /bookmarks/add で実行されるメソッドは BookmarksController::add() 、編集であれば /bookmarks/edit で実行されるメソッドは BookmarksController::edit() 、削除であれば /bookmarks/del で実行されるメソッドは BookmarksController::del() 、一覧であれば /bookmarks/index で実行されるメソッドは BookmarksController::index() といった具合です。ちなみにアクション名は自由に命名することができます。なお、先ほどのようにアクション名を省略して /bookmarks/ とした場合は /bookmarks/index が指定されたものと見なします。

本来であればこの後、各アクションの実装に入っていくのですが CakePHP の有用なポイントをお伝えする関係上ここでは scaffolding を利用します。

scaffolding とは先進的なフレームワークが実現している技術で、先ほど作成したモデルとコントローラから、必要な処理の土台を肩代わりする機能です。具体的には Bookmark モデルと BookmarksController コントローラとの関係を自動的に理解して、追加・編集・削除・一覧に必要な処理を自動的に組み上げるものです。ソースコードを自動生成するわけではない点に注意してください。

方法は非常に簡単です BookmarksController に次のフィールドを追加してください。

var $scaffold;

つまりこうなります。

/app/controllers/bookmarks_controller.php

<?php
class BookmarksController extends AppController
{
  var $scaffold;
  var $name = 'Bookmarks';
}
?>

編集が済みましたら /bookmarks/ へアクセスしてください。今度は警告のない画面ができているはずです。

cakephp_tut_003.png

入力データの検証はできませんが、すでにブックマークの登録・編集・削除・一覧が動作しています。一覧ページからリンクをたどって各機能を試してみてください。

cakephp_tut_004.png cakephp_tut_005.png

チュートリアル - ブックマークに評価をつける

ブックマークの管理ができるようになりましたので、次はブックマークを評価できるよう拡張します。

さきほどと同じ手順で evaluations テーブルに関するモデルとコントローラを作成してください。

CREATE TABLE evaluations
(
  id          INTEGER      NOT NULL AUTO_INCREMENT,
  bookmark_id INTEGER      NOT NULL,
  name        VARCHAR(30)  NOT NULL,
  comment     VARCHAR(100) NOT NULL,
  PRIMARY KEY (id)
);

scaffolding の状態でも評価の登録は可能ですが、現段階ではブックマークとの関連がありません。ブックマークと評価を関連づけるには CakePHP のアソシエーションという機能を使用します。

アソシエーションに関しましてはリレーションを参照してください。単語は違いますが意味は同じです。もう少し言うとデータベースの視点から見るとリレーション、 CakePHP の視点から見るとアソシエーションと呼ばれるようです。

ブックマークと評価の関係は、ひとつのブックマークが親となり、複数の評価がそれに属しています。これを CakePHP 風に解釈すると、ブックマークは評価を hasMany 、評価はブックマークに belongsTo の関係にあると言えます。

ブックマークと評価を関連づける具体的なコードは次のようになります。

/app/models/bookmark.php

<?php
class Bookmark extends AppModel
{
  var $name = 'Bookmark';
  var $displayField = 'site_name';

  var $hasMany = array('Evaluation' =>
                       array('className' => 'Evaluation'));
}
?>

/app/models/evaluation.php

<?php
class Evaluation extends AppModel
{
  var $name = 'Evaluation';

  var $belongsTo = array('Bookmark' =>
                         array('className' => 'Bookmark'));
}
?>

hasMany の場合は var $hasMany 配列、 belongsTo の場合は var $belongsTo 配列にそれぞれクラス名を登録することでお互いを関連づけます。これがアソシエーションです。

ここで Bookmark クラスに var $displayField という文字列型のフィールドが追加された点に注目してください。これはブックマークの抜粋を意味します。前述のコードの場合 Bookmark の抜粋は site_name です。この抜粋は Bookmark の子供である Evaluation から親を見たとき、親はどのように表示されるかを意味しています。

具体例を見た方が早いかもしれません。

ブックマークの時と同様に /evaluations/ を見てください…と言いたいところですが、せっかくアソシエーションを設定しましたので /bookmarks/ を見てください。一覧から好きなブックマークを選んで Actions 列の View をクリックします。

cakephp_tut_100.png

実行しているのはブックマークのコントローラですのでブックマークの詳細が表示されるのは当然ですが、子供であるブックマークにひも付いた評価もリスト表示されています。これがアソシエーションの結果です。

次はブックマークに評価をつけてる画面です。

cakephp_tut_101.png

対象のブックマークを抜粋( site_name )から選択している点に注意してください。

これで当初の予定であるブックマークリストの大枠が完成しました。

チュートリアル - 入力データのチェック

2007年9月11日現在、最新の CakePHP 安定版では、入力チェックとして正規表現を用いることができます。

正規表現とは文字列のパターンを示す記号の集まりで、たとえば4桁の数字は /[0-9][0-9][0-9][0-9]/ と表現します。

しかし、正規表現は視認性に疑問が残るため CakePHP では定数によってよく使う入力チェックのパターンが用意されています。

  • VALID_NOT_EMPTY
    入力データは必須入力である。
  • VALID_NUMBER
    入力データは数値でなければならない。
  • VALID_EMAIL
    入力データはEメールアドレスでなければならない。
  • VALID_YEAR
    入力データは年数でなければならない。

入力チェックを WEB システムに反映させるには、モデルの var $validate という配列に対象のフィールドと正規表現(もしくは前述の定数)を記述します。

ブックマークリストの例ではサイト名と URL が必須入力ですので次のようになります。

/app/models/bookmark.php

<?php
class Bookmark extends AppModel
{
  var $name = 'Bookmark';
  var $displayField = 'site_name';

  var $validate = array('site_name' => VALID_NOT_EMPTY,
                        'url' => VALID_NOT_EMPTY);

  var $hasMany = array('Evaluation' =>
                       array('className' => 'Evaluation'));
}
?>

評価モデルに関しても、投稿者と評価の本文が必須入力ですので次のようになります。

/app/models/evaluation.php

<?php
class Evaluation extends AppModel
{
  var $name = 'Evaluation';

  var $validate = array('bookmark_id' => VALID_NUMBER,
                        'name' => VALID_NOT_EMPTY,
                        'comment' => VALID_NOT_EMPTY);

  var $belongsTo = array('Bookmark' =>
                         array('className' => 'Bookmark'));
}
?>

設定後、必須項目が未入力の場合エラーメッセージが出力されるようになり、入力チェックをパスしない限り先へ進めなくなります。

cakephp_tut_200.png

チュートリアル - scaffolding からソースコードへ

scaffolding はあくまで開発をサポートする機能に過ぎません。具体的な Web システムの構築にはコーディングの作業が必要になります。そこで bake.php というスクリプトを使って scaffold と同等のソースコードを作ってみます。

bake.php は CakePHP をインストールしたディレクトリの…

└─cake
    └─scripts
        └  bake.php

…にあります。

bake.php はスクリプトですので、コマンドラインから…

php -q ./bake.php

…と、実行します。

今回 bake.php で生成するオブジェクトは BookmarksController, BookmarksController の表示テンプレート, EvaluationsController, EvaluationsController の表示テンプレートの4つです。モデルはすでに完成系ですので生成しません。

具体的な手順です。 bake.php では bake.php からの質問→ユーザ(つまり私たち)の回答のサイクルで徐々に設定を決定します。

>

…が表示されたら、ユーザ(つまり私たち)がキーボードで回答を入力してくださいという合図です。入力後、確定するにはリターンキーを押します。

[y] >

…のように表示されることがあります。これはデフォルトが y という意味です。デフォルトのままで問題ない場合は未入力でエンターキーを押して確定することができます。

BookmarksController

  1.  -bash-2.05b$ php -q bake.php
  2.  
  3.   ___  __  _  _  ___  __  _  _  __      __  __  _  _  ___
  4.  |    |__| |_/  |__  |__] |__| |__]    |__] |__| |_/  |__
  5.  |___ |  | | \_ |___ |    |  | |      |__] |  | | \_ |___
  6.  ---------------------------------------------------------------
  7.  
  8.  
  9.  Bake -app in /CakePHP/app (y/n)
  10.  [y] > y
  11.  
  12.  
  13.  Baking...
  14.  ---------------------------------------------------------------
  15.  Name: app
  16.  Path: /CakePHP/app
  17.  ---------------------------------------------------------------
  18.  [M]odel
  19.  [C]ontroller
  20.  [V]iew
  21.  
  22.  What would you like to Bake? (M/V/C)
  23.  > c
  24.  ---------------------------------------------------------------
  25.  Controller Bake:
  26.  ---------------------------------------------------------------
  27.  Possible Controllers based on your current database:
  28.  1. Bookmarks
  29.  2. Evaluations
  30.  
  31.  Enter a number from the list above, or type in the name of another controller. 
  32.  > 1
  33.  
  34.  Would you like bake to build your controller interactively?
  35.  Warning: Choosing no will overwrite  controller if it exist. (y/n)
  36.  [y] > y
  37.  
  38.  Would you like to use scaffolding? (y/n)
  39.  [y] > n
  40.  
  41.  Would you like to include some basic class methods (index(), add(), view(), edit())? (y/n)
  42.  [n] > y
  43.  
  44.  Would you like to create the methods for admin routing? (y/n)
  45.  [n] > n
  46.  
  47.  Would you like this controller to use other models besides 'Bookmark'? (y/n)
  48.  [n] > y
  49.  
  50.  Please provide a comma separated list of the classnames of other models you'd like to use.
  51.  Example: 'Author, Article, Book' 
  52.  > Evaluation
  53.  
  54.  Would you like this controller to use other helpers besides HtmlHelper and FormHelper? (y/n)
  55.  [n] > n
  56.  
  57.  Would you like this controller to use any components? (y/n)
  58.  [n] > n
  59.  
  60.  Would you like to use Sessions? (y/n)
  61.  [y] > y
  62.  
  63.  ---------------------------------------------------------------
  64.  The following controller will be created:
  65.  ---------------------------------------------------------------
  66.  Controller Name:        Bookmarks
  67.  Uses:            Evaluation
  68.  ---------------------------------------------------------------
  69.  
  70.  Look okay? (y/n)
  71.  [y] > y
  72.  
  73.  Creating file /CakePHP/app/controllers/bookmarks_controller.php
  74.  File exists, overwrite? /CakePHP/app/controllers/bookmarks_controller.php (y/n/q):y
  75.  Wrote/CakePHP/app/controllers/bookmarks_controller.php
  76.  
  77.  Cake test suite not installed.  Do you want to bake unit test files anyway? (y/n)
  78.  [y] > n

BookmarksController

  1.  -bash-2.05b$ php -q bake.php
  2.  
  3.   ___  __  _  _  ___  __  _  _  __      __  __  _  _  ___
  4.  |    |__| |_/  |__  |__] |__| |__]    |__] |__| |_/  |__
  5.  |___ |  | | \_ |___ |    |  | |      |__] |  | | \_ |___
  6.  ---------------------------------------------------------------
  7.  
  8.  
  9.  Bake -app in /CakePHP/app (y/n)
  10.  [y] > y
  11.  
  12.  
  13.  Baking...
  14.  ---------------------------------------------------------------
  15.  Name: app
  16.  Path: /CakePHP/app
  17.  ---------------------------------------------------------------
  18.  [M]odel
  19.  [C]ontroller
  20.  [V]iew
  21.  
  22.  What would you like to Bake? (M/V/C)
  23.  > v
  24.  ---------------------------------------------------------------
  25.  View Bake:
  26.  ---------------------------------------------------------------
  27.  Possible Controllers based on your current database:
  28.  1. Bookmarks
  29.  2. Evaluations
  30.  
  31.  Enter a number from the list above, or type in the name of another controller. 
  32.  > 1
  33.  
  34.  Would you like bake to build your views interactively?
  35.  Warning: Choosing no will overwrite  views if it exist. (y/n)
  36.  [y] > y
  37.  
  38.  Would you like to create some scaffolded views (index, add, view, edit) for this controller?
  39.  NOTE: Before doing so, you'll need to create your controller and model classes (including associated models). (y/n)
  40.  [n] > y
  41.  
  42.  Would you like to create the views for admin routing? (y/n)
  43.  [n] > n
  44.  
  45.  Creating file /CakePHP/app/views/bookmarks/index.thtml
  46.  Wrote/CakePHP/app/views/bookmarks/index.thtml
  47.  
  48.  Creating file /CakePHP/app/views/bookmarks/view.thtml
  49.  Wrote/CakePHP/app/views/bookmarks/view.thtml
  50.  
  51.  Creating file /CakePHP/app/views/bookmarks/add.thtml
  52.  Wrote/CakePHP/app/views/bookmarks/add.thtml
  53.  
  54.  Creating file /CakePHP/app/views/bookmarks/edit.thtml
  55.  Wrote/CakePHP/app/views/bookmarks/edit.thtml
  56.  ---------------------------------------------------------------
  57.  
  58.  View Scaffolding Complete.

EvaluationsController

  1.  -bash-2.05b$ php -q bake.php
  2.  
  3.   ___  __  _  _  ___  __  _  _  __      __  __  _  _  ___
  4.  |    |__| |_/  |__  |__] |__| |__]    |__] |__| |_/  |__
  5.  |___ |  | | \_ |___ |    |  | |      |__] |  | | \_ |___
  6.  ---------------------------------------------------------------
  7.  
  8.  
  9.  Bake -app in /CakePHP/app (y/n)
  10.  [y] > y
  11.  
  12.  
  13.  Baking...
  14.  ---------------------------------------------------------------
  15.  Name: app
  16.  Path: /CakePHP/app
  17.  ---------------------------------------------------------------
  18.  [M]odel
  19.  [C]ontroller
  20.  [V]iew
  21.  
  22.  What would you like to Bake? (M/V/C)
  23.  > c
  24.  ---------------------------------------------------------------
  25.  Controller Bake:
  26.  ---------------------------------------------------------------
  27.  Possible Controllers based on your current database:
  28.  1. Bookmarks
  29.  2. Evaluations
  30.  
  31.  Enter a number from the list above, or type in the name of another controller. 
  32.  > 2
  33.  
  34.  Would you like bake to build your controller interactively?
  35.  Warning: Choosing no will overwrite  controller if it exist. (y/n)
  36.  [y] > y
  37.  
  38.  Would you like to use scaffolding? (y/n)
  39.  [y] > n
  40.  
  41.  Would you like to include some basic class methods (index(), add(), view(), edit())? (y/n)
  42.  [n] > y
  43.  
  44.  Would you like to create the methods for admin routing? (y/n)
  45.  [n] > n
  46.  
  47.  Would you like this controller to use other models besides 'Evaluation'? (y/n)
  48.  [n] > y
  49.  
  50.  Please provide a comma separated list of the classnames of other models you'd like to use.
  51.  Example: 'Author, Article, Book' 
  52.  > Bookmark
  53.  
  54.  Would you like this controller to use other helpers besides HtmlHelper and FormHelper? (y/n)
  55.  [n] > n
  56.  
  57.  Would you like this controller to use any components? (y/n)
  58.  [n] > n
  59.  
  60.  Would you like to use Sessions? (y/n)
  61.  [y] > y
  62.  
  63.  ---------------------------------------------------------------
  64.  The following controller will be created:
  65.  ---------------------------------------------------------------
  66.  Controller Name:        Evaluations
  67.  Uses:            Bookmark
  68.  ---------------------------------------------------------------
  69.  
  70.  Look okay? (y/n)
  71.  [y] > y
  72.  
  73.  Creating file /CakePHP/app/controllers/evaluations_controller.php
  74.  File exists, overwrite? /CakePHP/app/controllers/evaluations_controller.php (y/n/q):y
  75.  Wrote/CakePHP/app/controllers/evaluations_controller.php
  76.  
  77.  Cake test suite not installed.  Do you want to bake unit test files anyway? (y/n)
  78.  [y] > n

EvaluationsController

  1.  -bash-2.05b$ php -q bake.php
  2.  
  3.   ___  __  _  _  ___  __  _  _  __      __  __  _  _  ___
  4.  |    |__| |_/  |__  |__] |__| |__]    |__] |__| |_/  |__
  5.  |___ |  | | \_ |___ |    |  | |      |__] |  | | \_ |___
  6.  ---------------------------------------------------------------
  7.  
  8.  
  9.  Bake -app in /CakePHP/app (y/n)
  10.  [y] > y
  11.  
  12.  
  13.  Baking...
  14.  ---------------------------------------------------------------
  15.  Name: app
  16.  Path: /CakePHP/app
  17.  ---------------------------------------------------------------
  18.  [M]odel
  19.  [C]ontroller
  20.  [V]iew
  21.  
  22.  What would you like to Bake? (M/V/C)
  23.  > v
  24.  ---------------------------------------------------------------
  25.  View Bake:
  26.  ---------------------------------------------------------------
  27.  Possible Controllers based on your current database:
  28.  1. Bookmarks
  29.  2. Evaluations
  30.  
  31.  Enter a number from the list above, or type in the name of another controller. 
  32.  > 2
  33.  
  34.  Would you like bake to build your views interactively?
  35.  Warning: Choosing no will overwrite  views if it exist. (y/n)
  36.  [y] > y
  37.  
  38.  Would you like to create some scaffolded views (index, add, view, edit) for this controller?
  39.  NOTE: Before doing so, you'll need to create your controller and model classes (including associated models). (y/n)
  40.  [n] > y
  41.  
  42.  Would you like to create the views for admin routing? (y/n)
  43.  [n] > n
  44.  
  45.  Creating file /CakePHP/app/views/evaluations/index.thtml
  46.  Wrote/CakePHP/app/views/evaluations/index.thtml
  47.  
  48.  Creating file /CakePHP/app/views/evaluations/view.thtml
  49.  Wrote/CakePHP/app/views/evaluations/view.thtml
  50.  
  51.  Creating file /CakePHP/app/views/evaluations/add.thtml
  52.  Wrote/CakePHP/app/views/evaluations/add.thtml
  53.  
  54.  Creating file /CakePHP/app/views/evaluations/edit.thtml
  55.  Wrote/CakePHP/app/views/evaluations/edit.thtml
  56.  ---------------------------------------------------------------
  57.  
  58.  View Scaffolding Complete.

これら bake.php によって新規追加(コントローラの場合は更新)されるのは次のファイルです。

└─app
    ├─controllers
    │      bookmarks_controller.php
    │      evaluations_controller.php
    │      
    └─views
        ├─bookmarks
        │      add.thtml
        │      edit.thtml
        │      index.thtml
        │      view.thtml
        │      
        └─evaluations
                add.thtml
                edit.thtml
                index.thtml
                view.thtml

/app/controllers/bookmarks_controller.php を開いてください。 bake.php によって追加されたソースコードが確認できます。

/app/controllers/bookmarks_controller.php

<?php
class BookmarksController extends AppController {

	var $name = 'Bookmarks';
	var $uses = array('Bookmark', 'Evaluation');
	var $helpers = array('Html', 'Form' );

	function index() {
		$this->Bookmark->recursive = 0;
		$this->set('bookmarks', $this->Bookmark->findAll());
	}

	function view($id = null) {
		if (!$id) {
			$this->Session->setFlash('Invalid id for Bookmark.');
			$this->redirect('/bookmarks/index');
		}
		$this->set('bookmark', $this->Bookmark->read(null, $id));
	}

	function add() {
		if (empty($this->data)) {
			$this->render();
		} else {
			$this->cleanUpFields();
			if ($this->Bookmark->save($this->data)) {
				$this->Session->setFlash('The Bookmark has been saved');
				$this->redirect('/bookmarks/index');
			} else {
				$this->Session->setFlash('Please correct errors below.');
			}
		}
	}

	function edit($id = null) {
		if (empty($this->data)) {
			if (!$id) {
				$this->Session->setFlash('Invalid id for Bookmark');
				$this->redirect('/bookmarks/index');
			}
			$this->data = $this->Bookmark->read(null, $id);
		} else {
			$this->cleanUpFields();
			if ($this->Bookmark->save($this->data)) {
				$this->Session->setFlash('The Bookmark has been saved');
				$this->redirect('/bookmarks/index');
			} else {
				$this->Session->setFlash('Please correct errors below.');
			}
		}
	}

	function delete($id = null) {
		if (!$id) {
			$this->Session->setFlash('Invalid id for Bookmark');
			$this->redirect('/bookmarks/index');
		}
		if ($this->Bookmark->del($id)) {
			$this->Session->setFlash('The Bookmark deleted: id '.$id.'');
			$this->redirect('/bookmarks/index');
		}
	}

}
?>

これは scaffolding と同等のソースです。

アクションである各メソッド index, view, add, edit, delete に注目してください。その名前に対応する *.thtml ファイルが /app/views/bookmarks/ 以下に保存されています。

└─app
    └─views
        ├─bookmarks
        │      add.thtml
        │      edit.thtml
        │      index.thtml
        │      view.thtml
        │      
        └─evaluations
                add.thtml
                edit.thtml
                index.thtml
                view.thtml

これは HTML のテンプレートファイルですが、すべて PHP で書かれていますので直接編集することができ、 PHP の知識があれば思い通りのデザインのページを作ることができます。

より実践的な CakePHP の使い方

CakePHP プログラマーズ リファレンスガイドや当サイトのCakePHP ガイド 入門編には CakePHP を理解する上で重要な解説が多くあります。ここではその中でも重要と思われる機能を紹介します。

コンポーネントを使う

コンポーネントを参照してください。

ヘルパーを使う

ヘルパーを参照してください。

登録・編集ページで確認画面を挟む

確認画面を挟むを参照してください。

保存しようとしているデータは一意か?を検証する

モデルクラス内にあるデータを保存するための save() メソッドは内部的に validates() メソッドを呼んでいます。

validates() メソッドはデータの入力をチェックするメソッドですが、モデルクラスには beforeValidate() という検証前に自動的に呼ばれるメソッドがあります。

このメソッドをオーバーライドして返り値を false にするとデータの保存が中止されるという性質を利用して、値の一意チェックが可能になります。

<?php
class SampleModel extends AppModel
{

  // …中略…

  function beforeValidate()
  {
    // $this->data には入力した値が入ってくる
    $data = $this->data;

    if ($this->findBySampleID($data["SampleID"])) {
      // 重複した sample_id があったため false を返して失敗にする
      return false;
    }

    return true;
  }
}
?>

不正なページからの送信を遮断する

HTML の form タグを偽装して悪意のあるデータを Web システムに post する攻撃があります。このような攻撃に対して CakePHP ではページが遷移するたびに「トークン」と呼ばれる文字列を生成し、トークンを含まない悪意のある post データを検知してブロックする機能があります。

使用するためには、まずコントローラで次のように書きます。

<?php
class SampleController extends AppController
{
  // …前略…

  // この配列に Security を追加します。
  var $components = array("Security");

  // …攻略…
}
?>

次に form タグを出力しているテンプレート部分を書き換えます。

<!-- "/sample/controller/action" はサンプルです -->
<form action="/sample/controller/action" method="post">

…と書いてある部分を…

<?php echo $html->formTag("/sample/controller/action, "post") ?>

…とします。

これにより、正しい post データは以前と変わらず処理しますが、トークンを含まない、あるいはトークンの内容が不正な post はブロックされます。

ブロックされた際に空白のページが出力される点に注意してください。これは不正な post に対して正常なページを返す必要はないというポリシーからの仕様だそうです。

HTTP リクエストは POST のみ受け付ける

CakePHP では GET 通信 POST 通信にかかわらず URL から HTTP リクエストを受け取るコントローラとアクションを決定します。

もし POST 通信のみ受け取りたい場合はコントローラ内に次のように書きます。

<?php
// /samples/action を post 専用にしたい場合

class SamplesController extends AppController
{
  // …前略…

  // Security コンポーネントを配列に追加します
  var $components = array("Security");

  // …中略…

  function beforeFilter()
  {
    $this->Security->requirePost('action');
  }

  function action()
  {
    // ここが post 専用になります
  }

  // …後略…
}
?>

不正なページからの送信を遮断する

HTML の form タグを偽装して悪意のあるデータを Web システムに post する攻撃があります。このような攻撃に対して CakePHP ではページが遷移するたびに「トークン」と呼ばれる文字列を生成し、トークンを含まない悪意のある post データを検知してブロックする機能があります。

使用するためには、まずコントローラで次のように書きます。

<?php
// /samples/action にトークンによる認証をかけたい場合

class SamplesController extends AppController
{
  // …前略…

  // Security コンポーネントを配列に追加します
  var $components = array("Security");

  // …中略…

  function beforeFilter()
  {
    $this->Security->requireAuth('action');
  }

  function action()
  {
    // ここがトークンによる認証で守られます
  }

  // …後略…
}
?>

次に form タグを出力しているテンプレート部分を書き換えます。

<!-- 前略 -->

<form action="/samples/action" method="post">
<?php "/samples/action" はサンプルです ?>

<!-- 後略 -->

…と書いてある部分を…

<!-- 前略 -->

$html->formTag("/samples/action", "post");

<!-- 後略 -->

…とします。

これにより、正しい post データは以前と変わらず処理しますが、トークンを含まない、あるいはトークンの内容が不正な post はブロックされます。

ブロックされた際に空白のページが出力される点に注意してください。これは不正な post に対して正常なページを返す必要はないというポリシーからの仕様だそうです。

HTTP リクエストに対する CakePHP の動作フロー

CakePHP を深く理解するために CakePHP が1回の HTTP リクエストに対して HTTP レスポンスを返るまでの動作フローを把握しておきます。

ここでは http://www.example.tld/ に CakePHP がインストールされているサーバへ http://www.example.tld/users/list/username/10 という HTTP リクエストを送った場合を解説します。

cakephp_flow.png

(1) HTTP リクエストを受ける

Web の枠組みで動作する以上 CakePHP も HTTP リクエストを受信しなければ動作できません。

CakePHP ではすべての HTTP リクエストをフロントコントローラである index.php が受信します。実際には http://www.example.tld/ と指定して http://www.example.tld/index.php が HTTP リクエストを受信できるように mod_rewrite が URL 書き換えます。

mod_rewrite とはパターンにあわせて URL を書き換えるための Apache の拡張モジュールです。

(2) URL からコントローラ名、アクション名、パラメータを解析する

フロントコントローラである index.php が HTTP リクエストを受けると、その URL をコントローラ名とアクション名とパラメータへ分離します。分離は CakePHP がインストールされている場所からスラッシュ区切りで「コントローラ名」「アクション名」「パラメータ」と順番に続いているものとして解釈します。

たとえば http://www.example.tld/users/list/username/10 の場合は http://www.example.tld/ に CakePHP をインストールしていますので、コントローラ名は http://www.example.tld/ の後ろの users 、アクション名は http://www.example.tld/users/ の後ろの list 、パラメータは http://www.example.tld/users/list/ の後ろの username/10 となります。しかし、パラメータはそこからさらにスラッシュで分離します。つまり username/10 の場合は username と 10 というふたつのパラメータになります。

http://www.example.tld/users/list/username/10
                       ~~~~~ ~~~~ ~~~~~~~~ ~~
                        (1)  (2)    (3)    (4)
(1) コントローラ名
(2) アクション名
(3) パラメータ1
(4) パラメータ2

(3) URL で指定されたコントローラ名に対応するクラスをインスタンス化する

URL から解析したコントローラ名に対応するクラスをインスタンス化します。

今回の例ではコントローラ名は users でしたので、 CakePHP の /app/controllers ディレクトリから users_controller.php というファイルを見つけ出し、そこに書かれている UsersController クラスをインスタンス化します。

<?php
class UsersController extends AppController
{
  // 前略…

  // このコントローラをインスタンス化します

  // 後略…
}
?>

(4) data[(ControllerName)][(FieldName)] をコントローラに取り込む

コントローラをインスタンス化したら次は HTTP パラメータの中から data[(ControllerName)][(FieldName)] という形式のデータがないか解析し、あった場合はコントローラが持つフィールド data に格納します。

今回の例では data[][] はありませんので、 CakePHP の基本的なデータ構造を参考にしてください。

<?php
class UsersController extends AppController
{
  // 前略…

  // HTTP リクエスト中に data[][] 形式のデータがあった場合はこの配列へ格納します
  // アクション内からは $this->data でアクセス可能です
  var $data;

  // 後略…
}
?>

(5) 必要に応じてコンポーネントをインスタンス化する

コントローラは必要に応じてコンポーネントをインスタンス化します。

<?php
class UsersController extends AppController
{
  // 前略…

  var $components = array("ACL", "Security");

  // 後略…
}
?>

コンポーネントを参照してください。

(6) 必要に応じてモデルをインスタンス化する

コントローラがアクションの実行に必要なモデルをインスタンス化します。必須ではありませんがたいていのアクションはモデルを必要とします。

<?php
class UsersController extends AppController
{
  // 前略…

  var $uses = array("User", "Subject");

  // 後略…
}
?>

(7) URL で指定されたアクション名に対応するメソッドを実行する

data[][] の取り込みやコンポーネントのインスタンス化が終了したら URL から解析したアクション名に対応するメソッドを実行します。

今回の例では UsersController::list() を実行することになりますが、もし URL にパラメータがあった場合はその値を引数に取ります(次節を参照)。

(8) URL で指定されたパラメータを引数とする

もし URL にパラメータがある場合は、そのデータを引数としてアクションへ渡します。

今回の例では username と 10 というパラメータがありましたので UsersController::list(”username”, 10) を実行することになります。

<?php
class UsersController extends AppController
{
  // 前略…

  function list($soft_column, $num)
  {
    // ここを実行します
  }

  // 後略…
}
?>

(9) 必要に応じてモデルを実行する

コントローラはアクションを実行する中であらかじめインスタンス化したモデルのメソッドを駆使して目的を達成します。

(10) 表示する画面に必要なデータをビューへ渡す

コントローラはアクションを実行する中で HTTP レスポンスとして返す HTML データの構築に必要なデータをビューへ渡します。

たとえば会員リストを出力したい場合は会員情報を要素とする配列をビューへ渡します。

<?php
class UsersController extends AppController
{
  // 前略…

  function list($soft_column, $num)
  {
    $user_list = $this->User->findAll(); // データを取得して…
    $this->set("user_list", $user_list); // ビューへ渡す
  }

  // 後略…
}
?>

(11) URL で指定されたアクション名に対応するビューテンプレートをインスタンス化する

アクションが終了するとコントローラはビューをインスタンス化します。

(12) 必要に応じてヘルパーをインスタンス化する

コントローラが HTTP レスポンスとして返す HTML の構築に必要なヘルパーをインスタンス化します。たいていの場合 HtmlHelper と FormHelper が必要です。

<?php
class UsersController extends AppController
{
  // 前略…

  var $helpers = array("Html", "Form");

  // 後略…
}
?>

(13) コントローラのアクション内で渡された値をテンプレートへ差し込む

アクション内で渡されたデータを駆使して HTTP レスポンスとして返す HTML を構築します。

先の例では会員情報を要素とする配列を渡されましたので foreach 文で要素がなくなるまで繰り返し会員情報を出力します。

<html>
<head>
<title>会員リスト</title>
</head>
<body>

<?php
foreach ($user_list as $user) : ?>
名前:<?php echo $user[name]; ?><br />
愛称:<?php echo $user[nickname]; ?><br />
趣味:<?php echo $user[hobby]; ?><br />
<hr />
<?php endforeach ?>

</body>
</html>

(14) HTTP レスポンスを返す

値をテンプレートへ差し込んだらそのデータをまるごと HTTP レスポンスとしてブラウザへ返します。

<html>
<head>
<title>会員リスト</title>
</head>
<body>

名前:太郎<br />
愛称:タロー<br />
趣味:ギター<br />
<hr />

名前:次郎<br />
愛称:ジロー<br />
趣味:読書<br />
<hr />

名前:三郎<br />
愛称:サブ<br />
趣味:テニス<br />
<hr />

</body>
</html>

リリースへ向けてのコーディング

実際の Web システム開発ではほとんどの場合、リリースへ向けて scaffolding や bake.php で作られた仮のコードをリリース仕様のコードに書き換える必要があります。

たとえばデータを更新する際に、更新するユーザがそのデータの所有権を持っているかのチェックや、パフォーマンスチューニングのためにアソシエーションを一時的に無効にするなどの実装が必要になってきます。

ここでは開発者がリリースへ向けてのコーディングができるよう CakePHP の基本的なメソッドの使い方を解説していきます。

表示系ページの構築に使うクラスとメソッド

ここでいう表示系ページとは、チュートリアルで作成したブックマークリストの /bookmarks/index のようにテーブルのレコードを一覧表示するページと、/bookmarks/view のようにあるレコードに対する詳細情報を表示するページを指します。

Model::findAll()

モデルに関連付いたテーブルから条件を満たすすべてのレコードを取得します。

Model::findAll ( $conditions = null,
                 $fields = null,
                 $order = null,
                 $limit = null,
                 $page = 1,
                 $recursive = null
)

$conditions 取得する条件(省略時は全件取得)
$fields     取得するフィールドのリスト(省略時は全フィールド取得)
$order      取得するレコードの表示順(省略時はレコードの格納順)
$limit      取得する件数(省略時は全件取得)
$page       取得するページ数(省略時は1ページ目を取得)
$recursive  再帰的に取得するアソシエーションの深さ(省略時はひとつ深いモデルまで取得)

$conditions をすべて満たすレコードを取得して、結果を CakePHP 配列へ詰めて返します。

このメソッドはチュートリアルではブックマーク一覧ページ( /bookmarks/index )を作る際に使用していました。

もしブックマークモデルの中に「削除」を意味する delete_flag を用意して、 delete_flag が1のブックマークは表示したくないという場合は $conditions に “delete_flag != 1″ と書きます。

Model::find()

モデルに関連付いたテーブルから条件を満たすレコードを1件取得します。

Model::find ( $conditions = null,
              $fields = null,
              $order = null,
              $recursive = null
)

$conditions 取得する条件(省略時は一番最初に取得したレコード)
$fields     取得するフィールドのリスト(省略時は全フィールド取得)
$order      取得するレコードの表示順(省略時はレコードの格納順)
$recursive  再帰的に取得するアソシエーションの深さ(省略時はひとつ深いモデルまで取得)

$conditions をすべて満たすレコードを1件取得して、結果を CakePHP 配列へ詰めて返します。1件しか取得しないため1件を特定する条件を $conditions に書く必要があります。

Model::read()

モデルに関連付いたテーブルからプライマリキーに対応するレコードを1件取得します。

Model::read ( $fields = null,
              $id = null
)

$fields 取得するフィールドのリスト(省略時は全フィールド取得)
$id     取得するレコードのプライマリキー

プライマリキーが $id のレコードを1件取得して、結果を CakePHP 配列へ詰めて返します。アソシエーションを1度だけ再帰的に取得する点に注意してください。

このメソッドはチュートリアルではブックマーク詳細情報の表示ページ( /bookmarks/view )を作る際に使用していました。

Model::findByXXXX

モデルに関連付いたテーブルから XXXX で示すフィールドの値が指定した値と一致するレコードを取得します。

これはマジックメソッドで XXXX にはフィールド名が入ります。

たとえばチュートリアルの BookmarksController の中で…

<?php
$result = $this->Bookmark->findBySiteName("Yahoo! Japan");
?>

…と書くことができます。

この場合 site_name というフィールドの値が “Yahoo! Japan” のレコードを取得して、結果を CakePHP 配列へ詰めて返します。

Model::findBy に続く名前がフィールド名の単語の頭文字を大文字にしてアンダーバーを除去した形式になる点に注意してください。たとえば site_name の場合は SiteName になります。

Model::beforeFind()

Model::find() や Model::findAll() の前に実行されるコールバックメソッドです。

Model::beforeFind ( &$queryData
)

&$queryData find() や findAll() へ渡された引数

$queryData へ渡される Model::find() や Model::findALl() の引数を編集することで、取得条件にフィルタをかけることができます。 $queryData 配列は仮引数の名前をキーとしています。

たとえば delete_flag という可視不可視を意味するフィールドを持つテーブルに対して、透過的に可視テーブルのみを取得したい場合、 Model::beforeFind() の中で $queryData[”conditions”] = “delete_flag = 0″; と記述すると、 Model::find() の引数が空でも delete_flag = 1 のフィールドは取得されません。

なお、このメソッドは返り値として true を返す必要がある点に注意してください。もし false を返した場合 Model::find() や Model::findAll() は終了します。

Model::afterFind()

Model::find() や Model::findAll() の後に実行されるコールバックメソッドです。

Model::afterFind ( $results
)

$results Model::find() や Model::findAll() の結果

$results へ渡される Model::find() や Model::findAll() の取得結果を編集することで、最終的な取得結果に手を加えることができます。

$results の形式は CakePHP 配列です。

Model::generateList()

モデルに関連付いたテーブルから条件を満たすすべてのレコードを取得し、あるフィールドを配列のキー、あるフィールドを配列の値とする配列で取得します。

Model::generateList ( $conditions = null,
                      $order = null,
                      $limit = null,
                      $keyPath = null,
                      $valuePath = null 
)

$conditions 取得する条件(省略時は全件取得)
$order      取得するレコードの表示順(省略時はレコードの格納順)
$limit      取得する件数(省略時は全件取得)
$keyPath    返り値となる配列のキーになるフィールド
$valuePath  返り値となる配列の値となるフィールド

$conditions をすべて満たすレコードを取得して、 $keyPath が示すフィールドをキー、 $valuePath が示すフィールドを値とした配列で返します。

たとえば…

prefectures
id name note
1 東京 首都
2 埼玉 夏に最高気温を観測
3 千葉 ディズニーランド

…というテーブルに…

<?php
$results = $this->Prefecture->generateList(null, null, null,
                                           "{n}.Prefecture.id",
                                           "{n}.Prefecture.name");
?>

…と実行すると、 $results は次のようになります。

<?php
array(1 => "東京",
      2 => "埼玉",
      3 => "千葉");
?>

このメソッドはチュートリアルではブックマークに評価をつける画面( /evaluations/add )で使用していました。

たとえば Bookmark モデルの id を $keyPath へ指定する場合は “{n}.Bookmark.id” と書きます。同様に Bookmark モデルの site_name を $valuePath へ指定する場合は “{n}.Bookmark.site_name” と書きます。

このメソッドの結果は、ビューで <select> の <option> タグを出力する際によく用いられます。

登録系ページの構築に使うクラスとメソッド

ここでいう登録系ページとは、チュートリアルで作成したブックマークリストの /bookmarks/add や /bookmarks/edit のようにモデルを通じてテーブルのレコードを追加・更新するページのことです。

Model::validates()

引数で渡す CakePHP 形式の入力値を検証し、値が正しいかの真偽値を返します。

Model::validates ( $data = array()
)

$data 検証したい data[Model][Field] 形式のデータ

このメソッドは Model::save() で内部的に呼ばれます。

Model::save()

引数で渡す CakePHP 形式の入力値をデータベースへ保存します。

Model::save ( $data = null,
              $validate = true,
              $fieldList = array() 
) 

$data      保存したい data[Model][Field] 形式のデータ
$validate  保存の前に入力値を検証するかの真偽値(省略時は検証する)
$fieldList 保存したいフィールド名のリスト(省略時は $data すべて)

CakePHP 形式のデータ $data をデータベースに保存します。

$data 中にプライマリキーを指定した場合は UPDATE によるレコードの更新、指定しない場合は INSERT による新規レコードの追加が実行されます。

このメソッドはチュートリアルではブックマークの新規登録と編集画面 ( /bookmarks/add, /bookmarks/edit )を作る際に使用していました。

Model::beforeValidate()

Model::validates() の前に呼ばれるコールバックメソッドです。

Model::beforeValidate(
)

独自のルールで入力値を検証したい場合にオーバーライドします。

入力値は Model::data (メソッドから見た場合は $this->data )に CakePHP 形式で格納されています。

検証にパスした場合は返り値に true 、失敗した場合は false を返す必要があります。

Model::beforeSave()

Model::save() の前に呼ばれるコールバックメソッドです。

Model::beforeSave (
)

保存前に入力データを加工したい場合にオーバーライドします。

入力値は Model::data (メソッドから見た場合は $this->data )に CakePHP 形式で格納されています。

返り値には true を指定しますが、もし Model::save() を中断したい場合は false を指定してください。

Controller::cleanUpFields()

Controller::cleanUpFields() を参照ください。

HtmlHelper::formTag()

form タグを表示するためのヘルパーメソッドです。

HtmlHelper::formTag ($target = null,
                     $type = 'post',
                     $htmlAttributes = array() 
) 

$target         送信先(省略時は同じページ)
$type           送信方式(省略時は post )
$htmlAttributes エレメントの追加属性

<?php $html->formTag("/path/to/action", "post"); ?>
        ↓
<form action="/path/to/action" method="post" />

HtmlHelper::input()

input type=”text” タグを表示するためのヘルパーメソッドです。

HtmlHelper::input ( $fieldName,
                    $htmlAttributes = array(),
                    $return = false 
) 

$fieldName      モデル名付きフィールド名( Modelname/fieldname )
$htmlAttributes HTML 属性
$return         生成した HTML を返り値として返すか?(デフォルトは出力する)

<?php $html->input("Bookmarks/site_name", array("size" => 50, "maxlength" => 100)); ?>
        ↓
<input type="text" name="data[Bookmarks][site_name]" size="50" maxlength="100" />

もし Controller::data[Bookmarks][site_name] に入力済みのデータがある場合は、自動的にその値をデフォルト値として出力します。

HtmlHelper::password()

input type=”password” タグを表示するためのヘルパーメソッドです。

HtmlHelper::password ( $fieldName,
                       $htmlAttributes = array(),
                       $return = false 
) 

$fieldName      モデル名付きフィールド名( Modelname/fieldname )
$htmlAttributes HTML 属性
$return         生成した HTML を返り値として返すか?(デフォルトは出力する)

<?php $html->password("Users/password", array("size" => 50, "maxlength" => 100)); ?>
        ↓
<input type="password" name="data[Users][password]" size="50" maxlength="100" />

もし Controller::data[Users][password] に入力済みのデータがある場合は、自動的にその値をデフォルト値として出力します。

HtmlHelper::hidden()

input type=”hidden” タグを表示するためのヘルパーメソッドです。

HtmlHelper::hidden ( $fieldName,
                     $htmlAttributes = array(),
                     $return = false 
) 

$fieldName      モデル名付きフィールド名( Modelname/fieldname )
$htmlAttributes HTML 属性
$return         生成した HTML を返り値として返すか?(デフォルトは出力する)

<?php $html->hidden("Hidden/field", array("value" => "HiddenValue")); ?>
        ↓
<input type="hidden" name="data[Hidden][field]" value="HiddenValue" />

もし Controller::data[Hidden][field] に入力済みのデータがある場合は、自動的にその値をデフォルト値として出力します。

HtmlHelper::textarea()

textarea タグを表示するためのヘルパーメソッドです。

HtmlHelper::textarea ( $fieldName,
                       $htmlAttributes = array(),
                       $return = false 
) 

$fieldName      モデル名付きフィールド名( Modelname/fieldname )
$htmlAttributes HTML 属性
$return         生成した HTML を返り値として返すか?(デフォルトは出力する)

<?php $html->textarea("Users/notes", array("cols" => 100, "rows" => 10)); ?>
        ↓
<textarea name="data[Users][notes]" cols="100", rows="10">
</textarea>

もし Controller::data[Users][notes] に入力済みのデータがある場合は、自動的にその値をデフォルト値として出力します。

HtmlHelper::checkbox()

input type=”checkbox” タグを表示するためのヘルパーメソッドです。

HtmlHelper::checkbox ( $fieldName,
                       $title = null,
                       $htmlAttributes = array(),
                       $return = false 
) 

$fieldName      モデル名付きフィールド名( Modelname/fieldname )
$title          非推奨につき null を渡します
$htmlAttributes HTML 属性
$return         生成した HTML を返り値として返すか?(デフォルトは出力する)

<?php $html->checkbox("Users/optinmail", array("value" => "1", "checked" => "checked")); ?>
        ↓
<input type="checkbox" name="data[Users][optinmail]" value="0" />
<input type="checkbox" name="data[Users][optinmail]" value="1" checked="checked" />

もし Controller::data[Users][optinmail] に入力済みのデータがある場合は、自動的にその値をデフォルト値として出力します。

HtmlHelper::radio()

input type=”radio” タグを表示するためのヘルパーメソッドです。

HtmlHelper::radio ( $fieldName,
                    $options,
                    $inbetween = null,
                    $htmlAttributes = array(),
                    $return = false
)

$fieldName      モデル名付きフィールド名( Modelname/fieldname )
$options        array("value" => "title") 形式の配列
$inbetween      radio ボタン同士を隔てる文字列
$htmlAttributes HTML 属性
$return         生成した HTML を返り値として返すか?(デフォルトは出力する)

<?php $html->radio("Users/sex", array("1" => "男性", "2" => "女性")); ?>
        ↓
<input type="radio" name="data[Users][sex]" value="1" />
<input type="radio" name="data[Users][sex]" value="2" />

もし Controller::data[Users][sex] に入力済みのデータがある場合は、自動的にその値をデフォルト値として出力します。

HtmlHelper::selectTag()

select タグを表示するためのヘルパーメソッドです。

HtmlHelper::selectTag ( $fieldName,
                        $optionElements,
                        $selected = null,
                        $selectAttr = array(),
                        $optionAttr = null,
                        $showEmpty = true,
                        $return = false 
) 

$fieldName      モデル名付きフィールド名( Modelname/fieldname )
$optionElements array("value" => "title") 形式の配列
$selected       初期選択する値
$selectAttr     select タグの HTML 属性
$optionAttr     option タグの HTML 属性
$showEmpty      空の option タグを生成するかの真偽値
$return         生成した HTML を返り値として返すか?(デフォルトは出力する)

<?php $html->radio("Users/sex", array("1" => "男性", "2" => "女性"), null, null, null, false); ?>
        ↓
<select name="data[Users][sex]">
  <option value="1">男性</option>
  <option value="2">女性</option>
</select>

もし Controller::data[Users][sex] に入力済みのデータがある場合は、自動的にその値をデフォルト値として出力します。

HtmlHelper::tagErrorMsg()

指定したフィールドの入力値にエラーがある場合、エラーメッセージを表示します。

HtmlHelper::tagErrorMsg ( $field,
                          $text 
)

$field モデル名付きフィールド名( Modelname/fieldname )
$text  エラーメッセージ

$field で指定したモデルのフィールドにエラーがあるか検証し、エラーがあった場合 $text を出力します。

登録系のページでエラーのある項目に再入力を促す場合に使用します。

トップページへ戻る / 前のページへ戻る

back to top