Friction River Software

  • お問い合わせ

CakePHP5入門【CakePHP5応用編⑤】アクセスログ関係

B美

今回はアクセスログへの記録なんだけど、実行するのは「PostsController.php」の「add」メソッドで…ってことだからね

「logs」テーブルのModelモデルを利用するにはどうするんだったっけ?
はい、A子(指さし)

A子

あー、うん
憶えてるよ

えっと…、どうするんだったっけ?

C菜

CakePHP5基礎編③でやりましたよ~

class PostsController extends AppController
{
    private $Logs;

    public function initialize(): void
    {
        parent::initialize();

        $this->Logs = $this->fetchTable('Logs');
    }

で良いはずです~

B美

正解!

あとは前回のレコード新規作成(挿入)のやり方で分かるでしょ?

A子

それは大丈夫

$log = $this->Logs->newEmptyEntity();
$log->ip_address = (IPアドレス);
$log->post_id = (postsの主キー);

if ($this->Logs->save($log)) {

} else {

}

…ってことでしょ?

B美

おぉ、やるじゃん!

上記の中で(postsの主キー)については問題ないわよね
んで、(IPアドレス)の入手法なんだけど、実は「$this->request->clientIp()」メソッドを呼ぶだけなのよ
(このメソッドの戻り値がアクセス元のIPアドレスってわけ)

A子

んー
…ってことは

$log = $this->Logs->newEmptyEntity();
$log->ip_address = $this->request->clientIp();
$log->post_id = $new_id;

if ($this->Logs->save($log)) {

} else {

}

でOK?

B美

この場合、登録の成功・失敗の判断は別に要らないかな

C菜

分かりました~

だったら

$log = $this->Logs->newEmptyEntity();
$log->ip_address = $this->request->clientIp();
$log->post_id = $new_id;
$this->Logs->save($log);

という感じでしょうか~

A子

これを「add」メソッドに追加すれば良いんだから

if ($this->request->is('post')) {
    $title = $this->request->getData('title');
    $body = $this->request->getData('body');
    $upload = $this->request->getData('upload');

    $post = $this->Posts->newEmptyEntity();
    $post->title = $title;
    $post->body = $body;
    $post->filename = '';
    $post->filepath = '';
    $post->delete_flag = 0;

    if ($this->Posts->save($post)) {
        $original_filename = $upload->getClientFilename();

        if ($original_filename != '') {
            $new_id = $post->id;

            $extension = mb_strtolower(pathinfo($original_filename, PATHINFO_EXTENSION));
            $filepath = ".".DS."files".DS.$new_id.".".$extension;
            $upload->moveTo($filepath);

            $new_post = $this->Posts->get($new_id);
            $new_post->filename = $original_filename;
            $new_post->filepath = $filepath;
            if (!$this->Posts->save($new_post)) {
                $this->Flash->error('画像ファイル情報のデータベース登録に失敗しました。');
                return $this->redirect(['action' => 'index']);
            }
        }
    } else {
        $this->Flash->error('新規投稿のデータベース登録に失敗しました。');
        return $this->redirect(['action' => 'index']);
    }

    $log = $this->Logs->newEmptyEntity();
    $log->ip_address = $this->request->clientIp();
    $log->post_id = $new_id;
    $this->Logs->save($log);


    $this->Flash->success('新たな投稿を登録しました。');
    return $this->redirect(['action' => 'index']);
}

で良いのよね?

B美

微妙に惜しい!

画像ファイルをアップロードしないときって、「$new_id」の値はどうなるのかしら?

C菜

あ、取得できていません~

if ($this->request->is('post')) {
    $title = $this->request->getData('title');
    $body = $this->request->getData('body');
    $upload = $this->request->getData('upload');

    $post = $this->Posts->newEmptyEntity();
    $post->title = $title;
    $post->body = $body;
    $post->filename = '';
    $post->filepath = '';
    $post->delete_flag = 0;

    if ($this->Posts->save($post)) {
        $original_filename = $upload->getClientFilename();

        $new_id = $post->id;

        if ($original_filename != '') {
            $extension = mb_strtolower(pathinfo($original_filename, PATHINFO_EXTENSION));
            $filepath = ".".DS."files".DS.$new_id.".".$extension;
            $upload->moveTo($filepath);

            $new_post = $this->Posts->get($new_id);
            $new_post->filename = $original_filename;
            $new_post->filepath = $filepath;
            if (!$this->Posts->save($new_post)) {
                $this->Flash->error('画像ファイル情報のデータベース登録に失敗しました。');
                return $this->redirect(['action' => 'index']);
            }
        }
    } else {
        $this->Flash->error('新規投稿のデータベース登録に失敗しました。');
        return $this->redirect(['action' => 'index']);
    }

    $log = $this->Logs->newEmptyEntity();
    $log->ip_address = $this->request->clientIp();
    $log->post_id = $new_id;
    $this->Logs->save($log);


    $this->Flash->success('新たな投稿を登録しました。');
    return $this->redirect(['action' => 'index']);
}

…って感じで「$new_id = $post->id;」の行をずらしました~


A子

いや、よく考えたらさぁ

$log->post_id = $post->id;

でも良かったんじゃない?

あ、ともかくはC菜のコードでテストしてみたよ
(2件ほど投稿してみたわ)

注:IPアドレスが「192.168.1.24」なのは、Windowsからアクセスしたから…



B美

もちろんA子の案でも問題ないわ
プログラムにおける正解って、一つだけじゃないからね

さて、それじゃ続けて投稿の削除を実装してみましょうか

A子

削除したいレコードの「delete_flag」に「1」をセットすれば良いのよね

public function delete($id = null)
{
    $post = $this->Posts->get($id);
    $post->delete_flag = 1;
    if ($this->Posts->save($post)) {
        $this->Flash->success('投稿の削除に成功しました。');
    } else {
        $this->Flash->error('投稿の削除に失敗しました。');
    }
}

で、どうよ(ドヤ顔)

B美

ふむ

んじゃ、ブラウザでURLを「(IPアドレス)/bbsapp/posts/delete/5」という形でアクセスしてみてよ
(idが5のレコードを削除って意味ね)

A子

うわっ!
エラーだ

ん?エラーよね?

B美

その通り
エラーで間違いないわ

てか、あなた「MVC」の基本を忘れてんじゃないの?

C菜

あ、Viewビューファイルとしての「delete.php」がありませんよ~

そっか、分かりました~
リダイレクトすればいいんですね~

public function delete($id = null)
{
    $post = $this->Posts->get($id);
    $post->delete_flag = 1;
    if ($this->Posts->save($post)) {
        $this->Flash->success('投稿の削除に成功しました。');
    } else {
        $this->Flash->error('投稿の削除に失敗しました。');
    }
    return $this->redirect(['action' => 'index']);
}

A子

もう一度実行してみたよ

ブラウザ上には表示されないけど、データベース上にはちゃんと残っていることを確認できたわ



B美

それじゃ、それを管理者だけが実行できるようにしましょう

現状では、誰でも削除できちゃうからね

A子

べーしっく認証だっけ?

B美

そう、BASICベーシック認証

まずは「MATE端末」を開きます
そしたら下記を実行してね

cd html/bbsapp[Enter]
bin/cake bake middleware HttpBasicAuth[Enter]

あと、「bbsapp/config」の中の「const.php」に以下の定数を追加しておきましょう

define("ADMIN_ID", "(ここには管理者ID)");
define("ADMIN_PASS", "(ここにはパスワード)");


C菜

できましたぁ~

B美

ここからがちょっと面倒なところなんだけどさ
(まぁ、あとで下に書いたやつをコピペしても良いんだけど…)

修正しなきゃいけないのは、「bbsapp/src/Middleware」の中にある「HttpBasicAuthMiddleware.php」ね

B美

この「process」メソッドの中身を書き換えます

$params = $request->getAttribute('params');
$action = $params['action'] ?? '';

if ($action == 'delete') {
    if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']) || $_SERVER['PHP_AUTH_USER'] !== ADMIN_ID || $_SERVER['PHP_AUTH_PW'] !== ADMIN_PASS) {
        $response = (new Response())->withStatus(401)->withHeader('WWW-Authenticate', 'Basic realm="My Realm"');
        $response->getBody()->write('<span style="color: red;">認証に失敗しました。</span><br /><br /><a href="../../">【掲示板へ戻る】</a>');
        return $response;
    }
}

return $handler->handle($request);

独自色を出したいなら、writeメソッドの引数(認証エラー時のメッセージ)くらいかな
あとは、もう定型的な記述だと思っておけば良いよ(苦笑)

ちなみに、認証が必要なのは「delete」メソッドのみだからこういう書き方をしたけれど、もしも複数のメソッドを対象にしたいのなら対象のメソッド名を配列にすべきだと思う
(もしくは、このPostsController全てを対象にするなら、そもそも三行目のif判定が不要)

あ、そうそう「namespace」の下に以下の一文を追加するのを忘れずに!

use Cake\Http\Response;

C菜

これでもう動くんですか~?

B美

いいえ、まだよ
「PostsController.php」の「initialize」メソッドに以下の一文を追加してね

$this->middleware(new HttpBasicAuthMiddleware());

あと、それに伴って「namespace」の下に以下の一文を追加ね

use App\Middleware\HttpBasicAuthMiddleware;


A子

んじゃ、テストしてみよう

URLに「(IPアドレス)/bbsapp/posts/delete/6」を入れてっと…
(idが6のレコードを削除って意味ね)

C菜

こんな画面が出ました~

A子

管理者のIDとパスワードを入れてっと…

おぉ、削除されたよ
(正確に言えば、表示されなくなったよ)

B美

ちょっと一言…

IDとパスワードを入力する画面で「このサイトへの接続は安全ではありません」って出たよね?
あれって、プロトコルが「http」だから出たの

実際の運用時には「https」にするから安全になるんだけど、これは開発環境だからね
(「https」では暗号化されているから安全…ってわけ)

注:本番環境(Webサーバ)が「http」の場合、BASIC認証を使ってはいけません(絶対に!)

A子

認証失敗パターンもやっておこう

B美

必ず!いったんブラウザを閉じてね
(キャッシュが残っちゃってるから…)

A子

ん?
間違えたものを入力しても、何度でも「サインイン」を押せるんだね

…で、あきらめて「キャンセル」を押すと…

C菜

【掲示板へ戻る】をクリックすると、ちゃんとトップ画面になりましたよ~

A子

それにしても、やっぱり認証関係って難しいね…

B美先生がいなかったらと思うと、ぞっとするよ(苦笑)

B美

まぁ、一度経験しておけば、あとはほとんどコピペだから…

さて、今回はこのあたりで終わりにしましょうか
次回は「CAPTCHAキャプチャ」を実装します

C菜

楽しみです~