CakePHP3のブックマークチュートリアルを触ってみる5 ビューの作成

CakePHP3のブックマークチュートリアルの続き。ビューの作成

チュートリアルのコードに従って、src/Template/Bookmarks/tags.ctpファイルを新規作成する。


<h1>
    Bookmarks tagged with
    <?= $this->Text->toList($tags) ?>
</h1>

<section>
<?php foreach ($bookmarks as $bookmark): ?>
    <article>
        <!-- Use the HtmlHelper to create a link -->
        <h4><?= $this->Html->link($bookmark->title, $bookmark->url) ?></h4>
        <small><?= h($bookmark->url) ?></small>

        <!-- Use the TextHelper to format text -->
        <?= $this->Text->autoParagraph($bookmark->description) ?>
    </article>
<?php endforeach; ?>
</section>

重要なことが書いてあった。
HTMLインジェクションを防ぐためにデータ出力時には、必ずhショートカット関数「h()」を使うこと。
ビューテンプレートのctpファイル名は、小文字でコントローラのアクション名を付けること。
コントローラのset()メソッドを用いて指定した変数は、ビューで利用可能。

http://localhost:8765/bookmarks/tagged/funnyにアクセスすると、以下のエラーが発生した。


Undefined variable: options [APP/Model\Table\BookmarksTable.php, line 105]

メッセージを見るに、APP/Model\Table\BookmarksTable.phpでoptionsが定義されていないらしい。
チュートリアルとソースコードを見直したら、手で打った部分が誤っていた。


public function findTagged(Query $query, array $options)
{
    // 間違いコード
    $bookmarks->innerJoinWith('Tags', function ($q) {
    // 正しいコード
    $bookmarks->innerJoinWith('Tags', function ($q) use ($options) {

コードを正しいコードに直してから、再度アクセスをするとfunnyタグが付いた本が表示される。
これで、ブックマークチュートリアルは終了。

CakePHP3のブックマークチュートリアルを触ってみる4 Finderメソッドの作成

CakePHP3のチュートリアル続き、Finderメソッドの作成

前回のエラーは、この部分を実施していなかったためにエラーが発生していた。
チュートリアルに従って、src/Model/Table/BookmarksTable.phpにカスタムFinderメソッドを作成する。


    public function findTagged(Query $query, array $options)
    {
        $bookmarks = $this->find()->select(['id', 'url', 'title', 'description']);
        
        if (empty($options['tags'])) {
            $bookmarks->leftJoinWith('Tags', function ($q) {
                return $q->where(['Tags.title IS ' => null]);
            });
        } else {
            $bookmarks->innerJoinWith('Tags', function ($q) {
                return $q->where(['Tags.title IN ' => $options['tags']]);
            });
        }
        return $bookmarks->group(['bookmarks.id']);
    }

カスタムFinderメソッドを追加したあと、ブラウザでhttp://localhost:8765/bookmarks/tagsにアクセスする。
結果、今度はMissingTemplateExceptionが発生した。
次回、ビューの作成で解決できる。

CakePHP3のブックマークチュートリアルを触ってみる3 タグを指定してブックマークを取得

CakePHP3のチュートリアルの続き、タグを指定してブックマークを取得

config/routes.phpを編集するので、コメントと空行を除いた結果をだしてみる。


connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
    $routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
    $routes->fallbacks(DashedRoute::class);
});
Plugin::routes();

チュートリアルの通りにconfig/routes.phpを編集する。編集後にブラウザからhttp://localhost:8765/bookmarks/taggedにアクセスする。
その結果、MissingActionExceptionが発生した。理由は、BookmarksControllerクラスにtaggedアクションが存在しないため。


Router::scope(
        '/bookmarks',
        ['controller' => 'Bookmarks'],
        function ($routes) {
            $routes->connect('/tagged/*', ['action' => 'tags']);
        }
        );

チュートリアルに従って、BookmarksControllerクラスにtags()を追加する。


    public function tags()
    {
        $tags = $this->request->params['pass'];
        $bookmarks = $this->Bookmarks->find('tagged', [
            'tags' => $tags
        ]);
        $this->set([
            'bookmarks' => $bookmarks,
            'tags' => $tags
        ]);
    }

追加後にブラウザからhttp://localhost:8765/bookmarks/taggedにアクセスする。
その結果、BadMethodCallExceptionが発生した。続きは次のチュートリアル、Finderメソッドの作成を実施してから確認してみる。

CakePHP3のブックマークチュートリアルを触ってみる2 Scaffold コードの生成

データベースのテーブル作成が終わったので、bakeを使って、Scaffold コードの生成を実施する。

チュートリアルの通り、usersテーブルに対してbakeコマンドを実施する。
bin\cake bake all users

その結果、以下の順番でphpファイル、ctpファイルを自動で作成する。なお、各フォルダに存在しているemptyファイルは、ここで削除される。
src\Model\Table\UsersTable.php
src\Model\Entity\User.php
tests\Fixture\UsersFixture.php
tests\TestCase\Model\Table\UsersTableTest.php
src\Controller\UsersController.php
tests\TestCase\Controller\UsersControllerTest.php
src\Template\Users\index.ctp
src\Template\Users\view.ctp
src\Template\Users\add.ctp
src\Template\Users\edit.ctp

残りのbookmarks、tagsを実行する前に、allがなんだか調べてみる。allを指定すると、MVCに必要なものが完全に自動作成される模様。

bin\cake bake -help
all Bake a complete MVC skeleton.

allと同様のことを、各サブコマンドで作成してみる。ヘルプの説明を見て必要そうなもの。
controller・・・Controllerクラスを作成する
fixture・・・Fixtureクラスを作成する
model・・・Model、Tableクラスを作成する
template・・・コントローラに対応するビューを作成する
test・・・Testクラスを作成する

usersテーブルに対応するクラスを作成するとき、順番があったので、以下の順番で作成してみる。

bin\cake bake model bookmarks
bin\cake bake fixture bookmarks
bin\cake bake controller bookmarks
bin\cake bake test bookmarks
bin\cake bake template bookmarks

結果
modelで以下のファイルを自動生成する。
src\Model\Table\BookmarksTable.php
src\Model\Entity\Bookmark.php
tests\Fixture\BookmarksFixture.php
tests\TestCase\Model\Table\BookmarksTableTest.php

fixtureで以下のファイルを自動生成する。しかし、modelで生成済み。
tests\Fixture\BookmarksFixture.php

controllerで以下のファイルを自動生成する。
src\Controller\BookmarksController.php
tests\TestCase\Controller\BookmarksControllerTest.php

testはエラーが出たのと、上記でTestクラスができているので飛ばす。

templateで以下のファイルを自動生成する。
src\Template\Bookmarks\index.ctp
src\Template\Bookmarks\view.ctp
src\Template\Bookmarks\add.ctp
src\Template\Bookmarks\edit.ctp

残りのtagsテーブルの自動生成はallで実施する。ここまで。

CakePHP3のブックマークチュートリアルを触ってみる1 データベースの作成

app.phpのデフォルト値でデータベースに接続できていることは確認した。
次にブックマークチュートリアルのデータベースの作成を実施してみる。

まずは、チュートリアル通りにmy_appデータベースに、4つのテーブルを作成する。
phpAdminから以下のSQLコマンドを実行する。


CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE bookmarks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(50),
    description TEXT,
    url TEXT,
    created DATETIME,
    modified DATETIME,
    FOREIGN KEY user_key (user_id) REFERENCES users(id)
);

CREATE TABLE tags (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    created DATETIME,
    modified DATETIME,
    UNIQUE KEY (title)
);

CREATE TABLE bookmarks_tags (
    bookmark_id INT NOT NULL,
    tag_id INT NOT NULL,
    PRIMARY KEY (bookmark_id, tag_id),
    FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
    FOREIGN KEY bookmark_key(bookmark_id) REFERENCES bookmarks(id)
);

テーブルの作成結果を確認する(phpAdminからでも確認できた。)。


MariaDB [my_app]> show tables;
+------------------+
| Tables_in_my_app |
+------------------+
| bookmarks        |
| bookmarks_tags   |
| tags             |
| users            |
+------------------+
4 rows in set (0.00 sec)

Option Explicitステートメントを使用して、宣言した変数のみ使用することを強制する。

VBAで変数を使用する時、DimありとDimなしを意識していなかった。本を読んでいて違いを把握したので残しておく。

VBAは、ある条件では、変数を使用する時に、Dimを使っても、使わなくても動作する。
例えば、Dimを使用しない以下のような「5と6を足し算した結果をメッセージボックスに表示する」コードも動作する。
しかし、以下のコードを実行するとメッセージボックスには11ではなく、5が表示される。
6を代入している変数はnum2だが、Totalに使用しているのは、num2ではなくて、スペル誤りのnum1だからだ。
つまり、変数のスペルが誤っていてもエラーとならないため、処理結果が誤っている時にバグを探すのが大変になることがある。


Sub testOptionExplicit()

    num = 5
    num2 = 6
    
    Total = num + num1
    MsgBox Total
End Sub

それを予防するために、Option Explicitステートメントを使用する。
このステートメントを使用すると、「Dimを用いて変数宣言していない変数は使用できない」という変数の宣言を強制することができる。
先に書いたコードを修正して実行すると、num1が変数宣言していないため、VBEがコンパイルエラーとしてメッセージを出力してくれる。
つまり、処理結果が出る前にバグに気づける。


Option Explicit
Sub testOptionExplicit()

    Dim num As Integer: num = 5
    Dim num2 As Integer: num2 = 6
    
    Total = num + num1
    MsgBox Total
End Sub

Option Explicitステートメントは、毎回記述するのは手間なので、VBEのオプションでデフォルトで設定しておくと便利。
やり方は、VBEの[ツール]-[オプション]を選択して、「編集」タブの「変数の宣言を強制する」チェックボックスにチェックを付ける。

VBA 7.1で動作確認。

CakePHPからMySQLへ接続してみる

CakePHPのインストールができたので、データベースへの接続のみ試してみる。
CakePHPのバージョ:3.3.12
MySQL(MariaDB)のバージョン:10.1.19

config/app.phpのDatasourcesの値は以下の通り。なので、ユーザにmy_app、ユーザパスワードはsecret、データベースはmy_appを作成してみる。


'Datasources' => [
    'default' => [
        'className' => 'Cake\Database\Connection',
        'driver' => 'Cake\Database\Driver\Mysql',
        'persistent' => false,
        'host' => 'localhost',
        'username' => 'my_app',
        'password' => 'secret',
        'database' => 'my_app',
        'encoding' => 'utf8',
        'timezone' => 'UTC',
        'flags' => [],
        'cacheMetadata' => true,
        'log' => false,
        'quoteIdentifiers' => false,
        'url' => env('DATABASE_URL', null),
    ],

phpAdminで、my_app’@’localhost’のユーザの作成と、my_appのデータベースを作成した。
その後、http://localhost:8765/にアクセスする。Databaseの欄を見ると、以下のメッセージのままで、まだデータベースに接続できていない。

CakePHP is NOT able to connect to the database.
Connection to database could not be established: SQLSTATE[HY000] [1045] Access denied for user 'my_app'@'localhost' (using password: YES)

コマンドプロンプトからmysqlへ接続を試みるも、エラーになってしまう。

mysql -u my_app -psecret
ERROR 1045 (28000): Access denied for user 'my_app'@'localhost' (using password: YES)

原因は、phpAdminでユーザを作成したときのパスワードが誤っていた。
パスワードをsecretに修正する。
http://localhost:8765/にアクセスする。Databaseの欄を見ると、以下のメッセージが出力されている。これでデータベースへの接続も完了した。

CakePHP is able to connect to the database.

CakePHP3.3.12のapp.phpのデフォルト設定値のみ抜き出す

CakePHP3.3.12のapp.phpのデフォルト設定値のみ抜き出してみた。
手作業は嫌なので、正規表現「^ *(\/|\*).*\n|^\n」を使って、コメントや空行は除外した。

以下、コメント、空行を除いたapp.phpのデフォルト設定値。


 filter_var(env('DEBUG', true), FILTER_VALIDATE_BOOLEAN),
    'App' => [
        'namespace' => 'App',
        'encoding' => env('APP_ENCODING', 'UTF-8'),
        'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'),
        'base' => false,
        'dir' => 'src',
        'webroot' => 'webroot',
        'wwwRoot' => WWW_ROOT,
        'fullBaseUrl' => false,
        'imageBaseUrl' => 'img/',
        'cssBaseUrl' => 'css/',
        'jsBaseUrl' => 'js/',
        'paths' => [
            'plugins' => [ROOT . DS . 'plugins' . DS],
            'templates' => [APP . 'Template' . DS],
            'locales' => [APP . 'Locale' . DS],
        ],
    ],
    'Security' => [
        'salt' => env('SECURITY_SALT', 'e949ac76260d7cb376f52108178f017244d457069ae0a7e91c2f0e501652e685'),
    ],
    'Asset' => [
    ],
    'Cache' => [
        'default' => [
            'className' => 'File',
            'path' => CACHE,
            'url' => env('CACHE_DEFAULT_URL', null),
        ],
        '_cake_core_' => [
            'className' => 'File',
            'prefix' => 'myapp_cake_core_',
            'path' => CACHE . 'persistent/',
            'serialize' => true,
            'duration' => '+1 years',
            'url' => env('CACHE_CAKECORE_URL', null),
        ],
        '_cake_model_' => [
            'className' => 'File',
            'prefix' => 'myapp_cake_model_',
            'path' => CACHE . 'models/',
            'serialize' => true,
            'duration' => '+1 years',
            'url' => env('CACHE_CAKEMODEL_URL', null),
        ],
    ],
    'Error' => [
        'errorLevel' => E_ALL,
        'exceptionRenderer' => 'Cake\Error\ExceptionRenderer',
        'skipLog' => [],
        'log' => true,
        'trace' => true,
    ],
    'EmailTransport' => [
        'default' => [
            'className' => 'Mail',
            'host' => 'localhost',
            'port' => 25,
            'timeout' => 30,
            'username' => 'user',
            'password' => 'secret',
            'client' => null,
            'tls' => null,
            'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
        ],
    ],
    'Email' => [
        'default' => [
            'transport' => 'default',
            'from' => 'you@localhost',
        ],
    ],
    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            'username' => 'my_app',
            'password' => 'secret',
            'database' => 'my_app',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'flags' => [],
            'cacheMetadata' => true,
            'log' => false,
            'quoteIdentifiers' => false,
            'url' => env('DATABASE_URL', null),
        ],
        'test' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            'username' => 'my_app',
            'password' => 'secret',
            'database' => 'test_myapp',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'cacheMetadata' => true,
            'quoteIdentifiers' => false,
            'log' => false,
            'url' => env('DATABASE_TEST_URL', null),
        ],
    ],
    'Log' => [
        'debug' => [
            'className' => 'Cake\Log\Engine\FileLog',
            'path' => LOGS,
            'file' => 'debug',
            'levels' => ['notice', 'info', 'debug'],
            'url' => env('LOG_DEBUG_URL', null),
        ],
        'error' => [
            'className' => 'Cake\Log\Engine\FileLog',
            'path' => LOGS,
            'file' => 'error',
            'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
            'url' => env('LOG_ERROR_URL', null),
        ],
    ],
    'Session' => [
        'defaults' => 'php',
    ],
];

phpMyAdminで「接続できません。設定が無効です。」のエラーが発生した

XAMPPをインストールしてから、MySQLとApacheを起動する。
ブラウザからhttp://localhost/phpmyadmin/にアクセスする。
そうしたところ、以下のエラー画面が発生した。
phpMyAdminのエラー画面

エラーメッセージは、以下の通り。
MySQL のメッセージ:接続できません。設定が無効です。
設定ファイルに定義されている管理ユーザ(controluser)での接続に失敗しました。
MySQL サーバに接続しようとしましたが拒否されました。config.inc.php のホスト、ユーザ名、パスワードが MySQL サーバの管理者から与えられた情報と一致するか確認してください。

phpMyAdminの設定ファイルconfig.inc.phpを見ると、controluserにpmaが設定されている。


/* User for advanced features */
$cfg['Servers'][$i]['controluser'] = 'pma';
$cfg['Servers'][$i]['controlpass'] = '';

コマンドプロンプトからpmaユーザでMySQLに接続すると、パスワードなしでも接続ができる。


mysql -u pma

原因がわからず、検索を続けると、以下のサイトに回答があった。
https://teratail.com/questions/29659

config.inc.phpの設定を以下の様に修正して、再度phpAdminにアクセスすると、アクセスできた。

/* 修正前 */
/*$cfg['Servers'][$i]['host'] = '127.0.0.1';
$cfg['Servers'][$i]['connect_type'] = 'tcp';*/
/* 修正後 */
$cfg['Servers'][$i]['host'] = 'localhost';
$cfg['Servers'][$i]['connect_type'] = 'socket';