CakePHPを勉強する人へ

私は入社半年の新人プログラマーです。研修では2か月間、Javaを勉強したのですが、配属された後、CakePHPというPHPフレームワークを使うことになり、大変苦労して学習しました。

そこで、今後Cakeを勉強しようとしている方や、未来の新人プログラマーの助けに少しでもなればと考え、私なりにCakePHPの基本について記事を書いてみることにしました。

何と言っても自分がつい半年前に頭を抱えていたヴィヴィッドな記憶があるので、それを生かして、自身の経験、理解するための工夫等を伝えられればいいなと考えております。是非ご一読下さい。

 

  1. そもそもMVCとは
    CakePHPは、MVC(Model-View-Controller)という構造を採用しているフレームワークである。しかし、大半の新人プログラマーはまずこの概念を理解するのに苦労するのではないだろうか。私もそうだった。
    そもそも1ファイルの中であれこれプログラムを書くことでさえヒィヒィ言いながらやっている状態なのに、それが3つに分かれる!?となれば混乱するのも当然だ。しかも、MVCでググってみたところで、意味不明の用語が並ぶスーパー分かり辛い解説しか出てこない。
    ならば、出来る限り難解な専門用語を控え、簡潔にこの構造を説明することこそ私に課せられた使命であろう。
    なお、概念の解説だけではすこぶる分かり辛いので、実際にコードを書きながら解説していくが、ひとつ留意点がある。
    それは、コードを書くにあたってCakeのバージョン2.x(以下、2系)を使用していることである。必然的に解説も2系寄りのものになるが、幸い自分には3.x(以下、3系)の経験も少しあるので、相違点を補完できる所は出来る限り補完していく。

    モデル(M)
    PHPはWeb開発に適した言語であるが、Web上で動く何らかのサービスを創ろうと思えば、とりあえずDBとの接続は必要になってくる。
    たとえば簡単な掲示板を作るとしよう。そうすると、画面上にはとりあえず過去の投稿のタイトルと内容くらいは最低でも表示されることになる。
    じゃ、タイトルとかのデータはどこに置いておくの?

    f:id:withoneAdventCalendar:20171214155033p:plain
    こういう構造のDBに入れておく。(ここではMySQLというDBに入れてある。)
    ただし、DBは、あくまでデータの入るただのだ。それ以上の機能はない。
    データを使って画面に投稿を表示したり、画面から投稿されたものを箱に入れたりするのはプログラムの役目になるわけだ。
    CakePHPで開発をするには、Cake側(データを使う側)とDB側(データを保持する側)を接続しなければならない。
    簡単な設定だけでこれをやってくれるのがモデルクラスであり、DBからデータを検索して引っ張ってくるメソッド(find)やDBにデータをセーブするメソッド(save)等、様々なメソッドが用意されている。
    要は、DBと直接レコードのやりとりをする処理は全部ここでやっちまおう!というクラスになる。

    モデルを作る
    実際に上記のテーブルから掲示板を作ってみることにする。
    まず重要なのが

    f:id:withoneAdventCalendar:20171214155033p:plain
    適当に作ったこのテーブルの名前。
    掲示板のデータが入るので掲示板っぽい名前なら何でもよいのだが、CakePHP の規約(重要)に従ってboardsテーブル(単語の複数形)とかにしておく。
    そして、このテーブルと接続してデータの取得、更新を行ってくれるモデルクラスのファイルBoard.phpはこんな感じ。

    f:id:withoneAdventCalendar:20171225093458p:plain

    気を付けること。クラスファイルを作る場所を間違えないこと(2系ではapp/Modelの下)。クラス名をCakePHP の規約(ほんとに重要)に従って単数形にすること。Cakeの用意してくれてるモデルクラス(2系ならAppModel)を継承すること。以上。
    データをどうこうしてくれるメソッドとかは全部Cake側で用意されているため、そいつを継承してしまえば、ただ単にデータをどうこうするだけなら何も書かなくてもできる。マジで。

    ※3系では
    モデルがTableクラスとEntityクラスという謎クラスに分かれているが、基本は同じ。

    CMS チュートリアル - データベース作成 - 3.x
    上記リンクを参考に、規約に沿った階層に規約に沿った名前で、Cakeのクラスを継承して作れば、基本的なメソッドは全部使えるはず。


    ビュー(V)

    ビューとは文字通り、実際にクライアント(サービスを使う側)がURLにアクセスした際に見ることのできる出力部分、つまりWebページだと考えてよいだろう。
    Webページの枠(見た目)を定義するHTMLにPHPを組み込むことで、Webページ上でデータを扱わなければできない複雑なことが簡単に出来てしまう。
    例えば、掲示板の投稿内容等のデータを変数に入れておき、その変数を出力することで初めて、掲示板を訪れた人が過去の投稿を見ることができるのである。
    また反対に、来訪者が入力したものを投稿する為の掲示板の書き込みフォーム等も、CakePHPのビューなら簡単に作ることができる。
    要は、ページ上でユーザーとの接点を持つのは任せろ!データの入出力処理とか俺がやってやる!ってな感じの部分である。

    ビューを作る
    早速、掲示板の投稿が表示されるようなビューを作ってみよう。
    Cakeではctpという拡張子でビューファイルを作る決まりになっている。また、これも慣例のようなものだが、一覧表示を担当するビューファイルは、Cakeでは「index(元々は索引という意味)」という名前になる。
    よって、index.ctpという名前でビューファイルを作ってみよう。
    このファイルを、また所定のディレクトリに配置する。ディレクトリは、2系ならapp/View/モデル名の複数形、3系ならsrc/Template/モデル名の複数形となる。

    f:id:withoneAdventCalendar:20171225112120p:plain

    配列の中に入ってる数だけぐるぐる回してくれるforeach文で全体を囲うことで、投稿が100件あろうが1000件あろうが全件表示できる。
    但し、まだこの段階では、このページのurlを叩いても何も表示されないどころかエラーになることに注意されたし。
    ここで使用している変数$postsは、文字通りただの変数なので、ここにデータの配列を入れないと表示ができないのは当然だ。
    しかし、この段階では、モデルでDBからデータを取ってくる準備は整った、ビューでデータを表示する準備は整ったという状態でしかない。実際にデータを取ってきてビューに渡すという行為を、誰も行っていない。
    いまいちパっときていない人も、この先を読み進めれば多分理解して頂けるだろう。

    コントローラ(C)
    モデルがDBとの接続、ビューがクライアントとデータのやり取りをやってくれるというのは分かった。
    でも、実際に、モデルがDBからとってきたデータをどうやってビューに渡すの?逆にビューで入力されたデータをどうやってDBに格納するの?という疑問が出てくる。
    その橋渡し的な役割をやってくれているのがコントローラクラスである。コントローラを調べると、「モデルとビューの制御」とかよく書かれているが、私は、「モデルとビューを繋げる為に頑張ってくれてる凄いやつ」くらいに理解したほうがすんなり頭に入り易かった。

    コントローラを作る
    やはり分かり辛いので、先程の掲示板の例で見ていこう。
    CakePHP の規約(とても重要なので100回くらい読むべし)によると、コントローラ名はモデル名の複数系+Controllerとなっている。
    よって、BoardsController.phpを、2系ならapp/Controllerの下、3系ならsrc/Controllerの下に配置する。

    f:id:withoneAdventCalendar:20171225132857p:plain
    で、こんなコードを書く。
    解説しよう。Boardsという名前のコントローラーなら、自動的にBoardという名前のモデルをクラス内で呼び出せるようになっている。これがCakeの規約だ。
    つまり、先程作ったBoardモデルのメソッドを呼び出せるということだ。
    8行目の$this->Board->find('all')というのはつまり、「このクラスでBoardモデルのfindメソッドを呼び出しますよ」という記述。
    で、findメソッドとは何なのかというと、DBから条件に沿ったデータを取得してくるメソッドだ。ここでは全件取ってきたいので、引数に’all’を指定する。
    findメソッドの戻り値は$postsという変数の中に格納する。
    これでモデルからコントローラにデータが渡った。
    ちなみに$postsの中身はこんな感じ。

    f:id:withoneAdventCalendar:20171225135043p:plain

    f:id:withoneAdventCalendar:20171214155033p:plain
    DBの中に入ってたデータが、ちゃんと配列の形式で取れているのがわかる。
    ちなみに、3系では配列ではなくオブジェクト型というよくわからない形で取ってくるが、foreachとかで回して表示する分には配列と同じように使えるので、あまり問題はない。
    12行目のsetメソッドは、「第二引数のデータを、第一引数の名前でビューに変数として渡しますよ」というメソッド。
    index()というアクション(コントローラークラス内に書いたメソッドのこと)の中でsetしていれば、Cakeが勝手に先程作ったindex.ctpに変数を渡してくれる。これも規約通りだ。
    これで、モデルのメソッドを使って取得したデータをビューに渡すという一連の流れを、コントローラがやってくれたことになる。橋渡し役という自分の理解も少し伝わったのではないだろうか。

    さて、この状態でブラウザから
    自分の環境のipアドレス/コントローラ名(Boards)/アクション名(index)にアクセスすると・・・
    まずコントローラに書いたアクションの処理が走り、それからビューを描画する。
    indexアクションの中では、モデルのメソッドを使ってデータを取得し、それをビューにセットしている。その処理後にindex.ctpが描画されるので・・・

    f:id:withoneAdventCalendar:20171225141617p:plain
    できた!!!!!!!!!!!!
    データ取得(モデル)→セット(コントローラ)→表示(ビュー)の流れとMVCの概念を、少しは理解して頂けただろうか。


  2. 逆方向の流れもやってみる
    せっかくなので、ビューでデータを入力してそれをDBにセーブするまでの流れも、先程の掲示板を使ってやってみる。要するに掲示板への書き込みも出来るようにする。
    まず、index.ctpと同じ階層に新しいビューファイル=新しいページを作ってみよう。データを追加するページなので、add.ctpという名前にする。中身はこんな感じ。

    f:id:withoneAdventCalendar:20171225155451p:plain
    ビューファイルなのにほとんどHTML書いてねえじゃねえか!!というツッコミが入るであろうが、実はこれ、Cakeに元々用意されていたメソッドを使ってHTMLを出力しているに過ぎない。
    例えば$this->Form->createやらendやらはformタグ(ここからここまで入力フォームですよ、というタグ)を出力しているだけだし、$this->Form->inputはinputタグ(サイトによくあるユーザーが文字とかを入力できる部分)、$this->Form->buttonはbuttonタグ(よくある送信ボタン)を出力している。
    要は、Formというクラスのメソッドで色んなHTMLのタグを出しているんですねー。
    間違えてはいけない箇所が2つ。$this->Form->createの引数はモデル名と同じにする。$this->Form->inputの第一引数はDBのカラム名(titleやらbodyやら)と同じにする。以上。
    こうすることで、「このモデルが接続しているDBの、このカラムにデータをセーブするぜ」的な入力項目が出来上がる。

    次、Boardsコントローラにも、indexアクションの下に新しくaddという名前のアクションを作る。

    f:id:withoneAdventCalendar:20171225161339p:plain
    解説しよう、17行目のif文は、「リクエストがPOSTの時(画面からsubmitボタンが押された時)だけ以下の処理を実行しますよ」というもの。つまり、先程のindexアクションとは違い、普通にURLを叩いてこのページに飛んだ時(GETリクエストという)は何も処理が走らず画面だけが描画されて、画面から「投稿」ボタンが押されたら下のセーブ処理が走る。
    20行目は、先程のfindと同じようにBoardモデルのメソッドを呼び出している。今回はDBにデータを登録したいので、saveメソッドを呼び出す。
    ちなみに投稿ボタンが押された時点で、画面から入力されたデータは$this->request->dataという変数の中に自動的に入ってきている。これをsaveメソッドの引数にすることで、モデルに渡してデータをセーブできる。
    つまり、先程の一覧表示とは逆方向の、ビューからデータを受け取りモデルに渡す、という橋渡しをコントローラがやっている。
    saveメソッドの戻り値は、セーブが正常に成功したらtrue、何らかの原因で失敗したらfalseが返るようになっている。つまり、20行目のif文の中には、セーブに成功した時に入る。
    んでもって23行目の記述は、セーブに成功したのにずっと投稿画面のままって何か変なので、redirectというメソッドを使って一覧表示画面(さっき作ったindexアクション→index.ctp描画の流れ)にワープさせているものである。必須ではないが、あるとサイトの挙動として綺麗になる。

    じゃあ登録やってみる
    先程と同じノリで、ブラウザから自分の環境のipアドレス/コントローラ名(Boards)/アクション名(add)にアクセス。

    f:id:withoneAdventCalendar:20171225170723p:plain
    入力フォーム的なやつができてる!!
    先程述べた通り、この段階ではURLを叩いただけなので、何も処理は走らず画面だけが描画されている。
    新しい投稿を入力してみる。

    f:id:withoneAdventCalendar:20171225170957p:plain
    下の「投稿」ボタン(見にくい)を押すと、画面に入力した文字列がコントローラのaddアクションに飛ぶ!この時はPOSTなので処理が開始される!
    addアクションのなかの$this->request->dataは次のようになる

    f:id:withoneAdventCalendar:20171225171240p:plain
    これをsaveメソッドの引数に渡しているので、セーブが成功すればDBに新しいデータが登録された後、indexアクションにリダイレクトされる。
    indexアクションでは新しく更新されたDBからデータを引っ張ってきて表示するので・・・

    f:id:withoneAdventCalendar:20171225171527p:plain
    新しい書き込みが増えている!!!!!!!!!


  3. まとめ
    1では、モデルがDBから引っ張ってきたデータをコントローラの変数に渡し、それをビューに渡すことでデータを表示する流れをやってみた。
    2では、ビューから入力されたデータがコントローラの変数の中に入り、それをモデルに渡してDBにデータを登録する流れをやってみた。

    と、いうわけで、CakeにおけるMVCとデータフローを私なりに図にしてみるとこんな感じになる。

    f:id:withoneAdventCalendar:20171215132324p:plain

    ※あくまで私なりの理解なので、多分、ググって出てくる図とは少し違う。
    オレンジの矢印が1でやったデータ取得の流れ、黄色の矢印が2でやったデータ登録の流れになる。
    勿論、DBの操作は取得、登録だけではない。掲示板なら、例えば過去の投稿を編集する機能(更新)も必要だろうし、不要な投稿を削除する機能も不可欠だろう。
    しかし、どんな処理であろうと、必ず上のオレンジか黄色の矢印のどちらかの流れに当てはまると自分は考えている。要は、DBから得られた情報でビューを描画するか、ビューで得られた情報でDBを操作するかの二択である。
    そして、モデルには他にも様々な手法でDBを操作するメソッドが多数用意されている。
    つまり、これから実装しようとしている処理では、データがどのような流れを描くのかということを理解し、その処理に即したモデルのメソッド(これはその場その場でググって覚えるしかない)を使えば、少なくともMVCフレームワークを用いたWebアプリケーションにおいて大局的に迷子になることはない。
    自分はそのような考え方で、半年間CakePHPを勉強してきた。この記事が、将来Cakeを学ぶ誰かの助けになることを切に願って、本稿の締めとさせて頂く。
                            ~fin~