CakePHP 新規プロジェクトの動作確認

公開日:2018-05-30

1. 概要

作成した CakePHP の新規プロジェクトを使って、試行錯誤をしながら CakePHP の動作確認を行います。

2. フォルダの確認

前回 Composer で作成したプロジェクトのフォルダを確認します。

1. bin

本家のサイトに書いてありますが、
以下のコマンドを実行すると、簡易Webサーバーが起動します。
コマンドプロンプト
cd C:\pleiades\xampp\htdocs\my_app\bin
cake server
起動後に http://localhost:8765/ にアクセスすると、my_app の最初の画面にアクセスできます。
my_app専用のため、他のアプリケーションにアクセスすることはできません。

2. config

  1. app.php
    公開ディレクトリ、DB、文字コード、タイムゾーンなどのアプリケーションの基本となる値を設定しています。

  2. paths.php
    各ディレクトリのパスを define で定義しています。

  3. requirements.php
    PHPのバージョンの確認と、intl と mbstring が有効になっているかをチェックしています。
    intl が反映されていない時のエラーは、ここが出していたようです。

  4. routes.php
    URL とコントローラーの関連付けを行っています。

3. src

config/paths.php で定義されていますが、src がアプリケーション用のフォルダのようです。
中には Controller、Model、View が入っています。
但し、View の中身はほぼ空で、実際に画面に表示されているのは、src/Template 配下のファイルのようです。
src/Template 配下には、最初の画面の home.ctp や、ヘッダーとフッターを含んだレイアウトファイルが入っています。

4. vendor

ライブラリがたくさん入っています。
CakePHP は、様々なライブラリを使用しているようです。

5. webroot

フォルダ名の通り、Web のルートになるフォルダです。
css、js、img が配置してあります。
最初に表示された画面のソースを表示して、css の読み込み部分を確認すると、
/my_app/css/style.css を見るようになっていました。
webroot がパスから消えていますが、
これは my_app/.htaccess で、webroot 配下を見るようにしています。

3. 最初の画面の確認

1. レイアウトとテンプレート

http://localhost/my_app/ にアクセスして表示される画面を変更してみます。
src/Template/Pages/home.ctp の先頭に、「Test」などを書いて表示してみます。

画面の先頭に「Test」と表示されました。
ソースを表示しても、先頭に出力されています。
src/Template/Layout/default.ctp にレイアウトファイルがありますが、
どうやらこのページはレイアウトを使っていないようです。

home.ctp を上から見ていくと、
ソース
$this->layout = false;
と書いてありました。これでレイアウトを未使用にしているようです。
試しに true にして画面を表示してみます。
以下のエラーが発生しました。
エラー
Error: The layout file Layout\1.ctp can not be found or does not exist.
Layout\default.ctp をコピーして 1.ctp を作成し、画面を再表示します。
今度は正常に表示されました。

ダメ元で、$this->layout = 'default'; にしてみます。
すると、1.ctp ではなく、default.ctp が使用されました。1.ctp を削除しても表示されます。

$this->layout には、
レイアウトを使う時はレイアウトファイル名を指定して、
レイアウトを使わない時は、false を設定すれば良さそうです。

$this->layout = false; に戻して、home.ctp の続きを見てみます。

2. ヘルパークラス

ソース
<?= $this->Html->meta('icon') ?>
<?= $this->Html->css('base.css') ?>
とありました。
ブラウザでソースを確認すると、
HTML
<link href="/my_app/favicon.ico" type="image/x-icon" rel="shortcut icon"/>
<link rel="stylesheet" href="/my_app/css/base.css"/>
となっていました。
css などはファイル名を指定するだけで簡単に読み込めるようです。

試しに、
ソース
<?= $this->Html->img('cake.icon.png') ?>
を追記してみます。

エラーが出ました。
エラー
Warning (512): Method Cake\View\Helper\HtmlHelper::img does not exist [CORE\src\View\Helper.php, line 139]
vendor/cakephp/cakephp/src/View/Helper/HtmlHelper を確認したところ、
どうやらここで変換を行っているようです。
また、img ではなく image のようです。
ソース
<?= $this->Html->image('cake.icon.png') ?>
再表示したところ、画像が表示されました。

3. コントローラー

src/Controller に、以下の3つのコントローラーが入っています。
・AppController.php
・ErrorController.php
・PagesController.php

PagesController と ErrorController は AppController を継承しています。
PagesController には display() だけ定義してあります。
ブラウザでアクセスすると、このメソッドが呼ばれるようです。
display() の先頭で、var_dump($path); を追加して画面を表示してみます。

実行結果
array(1) { [0]=> string(4) "home" }
と表示されました。
home と言う値がどこから出て来たのかわかりませんが、とりあえず display() が呼ばれていることと、
この home が、ビューの home.ctp になることが、なんとなくわかりました。

続いて見ていくと、次のソースが見つかります。
ソース
$this->set(compact('page', 'subpage'));
まず compact() で、$page と $subpage を連想配列に変換しています。
連想配列のキーは変数名で、値は変数の元の値が入ります。
ソース
$map = ["path" => $page, "subpage" => $subpage];
としたのと同じです。

次に生成された連想配列を $this->set() に渡しています。
このメソッドは、PagesController にないため、継承元である AppController を見てみます。
しかし、こちらにもありません。
次は、AppController の継承元の Cake\Controller\Controller を見てみます。
__set() はあるのですが、set() がありません。

ちなみに、
__set() は、マジックメソッドと呼ばれ、定義していないプロパティに値を設定した時に呼ばれます。
__get() は、定義していないプロパティの値を取得した時に呼ばれます。

Controller は extends していないため、親クラスはありません。
次に、trait を使っていないか確認します。
php には trait と言う仕組みがあり、クラスを継承せずにメソッドを追加することができます。
Controller の先頭を見ると、use EventDispatcherTrait; と書いてあり、名前にも Trait と付いています。
どうやら trait を使っているようです。

確認したところ、
vendor/cakephp/cakephp/src/View/ViewVarsTrait.php
で定義していました。
ソースのコメントに、テンプレート内で使用する変数を保存すると書いてあります。
set() は、コントローラーからビューに値を渡すためのメソッドのようです。

もう1度 PagesController の display() に戻ってソースを見ます。
最後に $this->render() としています。
ここでビューの表示を行っているようです。

PagesController は、アプリケーション側のフォルダに入っているため、
必要に応じて変更しても問題ありません。

試しに、PagesController を変更してみます。
ソース
public function display(...$path)
{
    $this->render(implode('/', $path));
}
画面を表示すると、今までと変わりなく表示されました。
implode('/', $path) の値を確認すると、home だったため、引数の値を展開して、
ソース
public function display(...$path)
{
    $this->render('home'));
}
として、もう一度実行してみます。
変数の値を展開しただけなので、問題なく表示されます。

次に、
ソース
public function display(...$path)
{
    $this->render('test'));
}
としてみます。
src\Template\Pages\test.ctp が見つからないと言うエラーが出ました。
この時、エラー画面の左側の「Cake\Controller\Controller->render」をクリックすると、
エラー箇所のソースが右側に表示されます。

src/Template/Pages\home.ctp をコピーして、test.ctp を作成します。
そして再表示すると、正常に表示されました。

次に、
ソース
public function display(...$path)
{
    $this->render('test/test');
}
として、Pages 配下に test フォルダを作成して、その中に先ほど作った test.ctp を移動します。
そして再表示すると、正常に表示されました。
render() には、src/Template/Pages からの相対パスで、拡張子なしのテンプレートファイルを渡せば良さそうです。

ソースを元に戻します。

4. URLとコントローラーの関連付け

最初にフォルダを確認した時に、config/routes.php で URL とコントローラーの関連付けを行っていたため、もう1度確認してみます。
routes.php 抜粋
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
$routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
最初に見た時はよくわかりませんでしたが、見覚えのある単語が出てきました。
routes.php のコメントでだいたいわかりますが、
念のため $routes->connect() の引数を確認してみます。
$routes は RouteBuilder のため、
vendor/cakephp/cakephp/src/Routing/RouteBuilder.php
を確認します。

connect() は以下のようになっていました。
RouteBuilder.php 抜粋
public function connect($route, $defaults = [], array $options = [])
ソースのコメントにサンプルも載っていました。
RouteBuilder.php 抜粋
$routes->connect('/:controller/:action/*');
$routes->connect('/home-page', ['controller' => 'Pages', 'action' => 'display', 'home']);
$routes->connect(
  '/:lang/:controller/:action/:id',
  [],
  ['id' => '[0-9]+', 'lang' => '[a-z]{3}']
$routes->connect('/tasks', ['controller' => 'Tasks', 'action' => 'index', '_method' => 'GET']);
1番目の引数にURL、
2番目の引数に連想配列でコントローラー名、アクション名(メソッド名)、ビュー、
3番目の引数にHTTPのメソッドなどのオプション、
を指定する。

つまり、
routes.php 抜粋
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
は、
http://localhost/my_app/ にアクセスされたら、
Pagesコントローラー の display() を実行して、
home と言うテンプレートを使って画面を表示する、
と言うことになりそうです。

2番目の引数の home だけキーがないため、home を先頭に移動させます。
routes.php 抜粋
$routes->connect('/', ['home', 'controller' => 'Pages', 'action' => 'display']);
正常に動作します。
キーが指定されていない要素をビューと認識しているようです。
試しに、キーがない要素を複数指定してみます。
routes.php 抜粋
$routes->connect('/', ['test', 'home', 'controller' => 'Pages', 'action' => 'display']);
エラーが出ました。
src/Template/Pages/test/home.ctp
のテンプレートを探しています。
どうやら、キーがない要素だけを action で指定されたメソッドの引数として渡しているようです。
PagesController() の display() の先頭で var_dump($path) をしたところ、
キーがない要素だけの連想配列が渡ってきました。
パスの結合は、display() の中で行っています。

次に、もう片方の connect() を確認します。
routes.php 抜粋
$routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
こちらには home のように、ビューが指定されていません。
/pages/ に反応するようなので、
http://localhost/my_app/pages/test/
にアクセスしてみます。

src/Template/Pages/test.ctp
がないと言うエラーが出ました。

test ではなく home にすると動きそうなため、試しに、
http://localhost/my_app/pages/home/
にアクセスしてみます。
正常に表示されました。

続いて、
http://localhost/my_app/pages/abc/test1/test2/
にアクセスしてみます。
src/Template/Pages/abc/test1/test2.ctp
がないと言われました。
「/pages/*」の「*」の部分が、引数になって、display() に渡っているようです。
var_dump($path)
array(3) {
  [0]=>
  string(3) "abc"
  [1]=>
  string(5) "test1"
  [2]=>
  string(5) "test2"
}

試しに、routes.php 側の連想配列に値を追加して、
routes.php 抜粋
$routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display', 'def1', 'def2']);
もう1度 http://localhost/my_app/pages/abc/test1/test2/ にアクセスしてみます。
var_dump($path)
array(5) {
  [0]=>
  string(4) "def1"
  [1]=>
  string(4) "def2"
  [2]=>
  string(3) "abc"
  [3]=>
  string(5) "test1"
  [4]=>
  string(5) "test2"
}
最初に定義された引数の後ろに、URLで指定した引数が追加されました。
今回は、テンプレートファイルのパスを作成するために引数を使用していますが、
データのIDの指定などにも利用できそうです。

5. コントローラーからビューに値を渡す

home.ctp を見ると $this が使われているため、
var_dump() で $this を出力してみます。
大量に値が出力されました。
下の方に見覚えのある page と subpage が出てきました。
これは PagesController で $this->set(compact('page', 'subpage')); した時の値です。
var_dump($this) 抜粋
  ["viewVars"]=>
  array(2) {
    ["page"]=>
    string(4) "home"
    ["subpage"]=>
    NULL
  }
var_dump($this->viewVars["page"]);
で、一応コントローラーで設定した値を取得することができました。
しかし、ちょっと不便です。
本家のサイト を確認したところ、
set() で指定した変数は、そのままビューでも使えるそうです。
home.ctp で $page を使ったところ、正しく値が表示されました。
また、home.ctp の先頭で var_dump(get_defined_vars()); を追加して、定義された全ての変数を確認してみます。
var_dump(get_defined_vars());
array(4) {
  ["viewFile"]=>
  string(59) "C:\pleiades\xampp\htdocs\my_app\src\Template\Pages\home.ctp"
  ["dataForView"]=>
  array(2) {
    ["page"]=>
    string(4) "home"
    ["subpage"]=>
    NULL
  }
  ["page"]=>
  string(4) "home"
  ["subpage"]=>
  NULL
}
基本的には、$this と set() で渡された変数以外は見えないようです。

念のため、PagesController を以下のようにして、
PagesController 抜粋
$test = "123";
$this->set(compact('page', 'subpage', 'test'));
home.ctp で $test を確認したところ、正常に値が渡ってきました。