がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

やれるところまでやってみよう nginx config

前提

http {

    # デバッグ用
    log_format debug "[DEBUG][$time_local] $debug_data";

を使うとデバッグしやすので、nginx.conf に仕込んでおく。

/home/gallu/test
ってディレクトリを用意して、ここを起点にする(あとで追加するけど)。

既知部分の確認

閲覧できるようにする

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    root /home/gallu/test/;
set $debug_data "";
#access_log /var/log/nginx/debug.log debug;
}

うんまぁ普通に見れる。

location でくくっておく

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    location / {
        root /home/gallu/test/;
    }
set $debug_data "";
#access_log /var/log/nginx/debug.log debug;
}

OK。

ディレクトリによっては「別のDocumentRootを見る」ようにする

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    location /foo/ {
        alias /home/gallu/test2/;
    }

    #
    location / {
        root /home/gallu/test/;
    }
set $debug_data "";
#access_log /var/log/nginx/debug.log debug;
}

これもOK。
なお、 /foo/ の所、rootにすると上手くいかないのでaliasにするとよいです。
細かいあたりは "nginx root alias" あたりでググると色々出てくるので省略。

ちょっとこの辺りで多少なりデバッグログ出してみよう。
setの位置間違えると色々齟齬るっぽいので、少しその辺修正。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    location /foo/ {
        alias /home/gallu/test2/;
set $debug_data "$document_root $request_uri $uri";
    }

    #
    location / {
        root /home/gallu/test/;
set $debug_data "$document_root $request_uri $uri";
    }

#
access_log /var/log/nginx/debug.log debug;
}

これで

[DEBUG][24/Feb/2023:16:43:22 +0900] /home/gallu/test / /index.html
[DEBUG][24/Feb/2023:16:43:30 +0900] /home/gallu/test2/ /foo/ /foo/index.html

って出てきたので、まぁ大体意図通り。
index.htmlが勝手に付与されているのがうっすら気になるので一応軽く調べてみる。

http://mogile.web.fc2.com/nginx/http/ngx_http_index_module.html#index
……うん「デフォルトがindex.html」になってるっぽいので、んじゃ納得。

ここで
・rootとかaliasとかで、意図通りのdocument_rootになっている
・$request_uriには「リクエスト時のURI(のpath以降部分)」が入ってる
・$uriには「(今回は、indexで補完された)path名が入っている」
あたりまでが担保できてる感じ。

一応、変数のことも調べておく。
http://nginx.org/en/docs/http/ngx_http_core_module.html

$document_root root or alias directive’s value for the current request
$request_uri full original request URI (with arguments)
$uri current URI in request, normalized

うん大体認識一致。
uriはnormalizedされてる」から、indexで補完されてるんだね。

怪しい目な既知部分の確認

さて。まずは単純に「PHPを動かす」から。
なんとなくざっくり書いてみる。
fastcgi_passでphp-fpmの指定、includeで「必要な諸々のデータ」の設定、fastcgi_indexでは「pathがディレクトリだけの時のデフォルトのindexファイル名」、fastcgi_param SCRIPT_FILENAMEで「SCRIPT_FILENAMEの設定の上書き(fastcgi.confにも書いてあるから)」。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    location /foo/ {
        alias /home/gallu/test2/;
set $debug_data "foo) $document_root $request_uri $uri";
    }

    #
    location / {
        root /home/gallu/test/;
set $debug_data "/) $document_root $request_uri $uri";
    }

    #
    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000;
        include        fastcgi.conf;
        fastcgi_index  index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
set $debug_data "/ php) $document_root $request_uri $uri $fastcgi_script_name";
    }

#
access_log /var/log/nginx/debug.log debug;
}

で……駄目。

[DEBUG][24/Feb/2023:18:16:17 +0900] / php) /usr/share/nginx/html /t.php /t.php /t.php

ほむ……document_rootが、意図している location / じゃなくて、デフォルト向いてるねぇ。
locationの設定、なんとなく「1つしか」選ばれない、んだとすると、location / の中に入れないとかしらん?

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    location /foo/ {
        alias /home/gallu/test2/;
set $debug_data "foo) $document_root $request_uri $uri";
    }

    #
    location / {
        root /home/gallu/test/;
set $debug_data "/) $document_root $request_uri $uri";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
set $debug_data "/ php) $document_root $request_uri $uri $fastcgi_script_name";
        }
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:18:18:01 +0900] / php) /home/gallu/test /t.php /t.php /t.php

動いた。当たりっぽい。

んじゃtest2も同じように。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    location /foo/ {
        alias /home/gallu/test2/;
set $debug_data "foo) $document_root $request_uri $uri";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name";
        }
    }

    #
    location / {
        root /home/gallu/test/;
set $debug_data "/) $document_root $request_uri $uri";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
set $debug_data "/ php) $document_root $request_uri $uri $fastcgi_script_name";
        }
    }

#
access_log /var/log/nginx/debug.log debug;
}

「File not found.」ありゃ?

[DEBUG][24/Feb/2023:18:19:48 +0900] foo php) /home/gallu/test2/ /foo/t.php /foo/t.php /foo/t.php

む。
これだと、SCRIPT_FILENAME に /home/gallu/test2//foo/t.php って入ってる。fooが邪魔やな。

調べると $request_filename ってのがあるらしい。
http://nginx.org/en/docs/http/ngx_http_core_module.html

$request_filename
file path for the current request, based on the root or alias directives, and the request URI

使えるか?

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    location /foo/ {
        alias /home/gallu/test2/;
set $debug_data "foo) $document_root $request_uri $uri";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name $request_filename";
        }
    }

    #
    location / {
        root /home/gallu/test/;
set $debug_data "/) $document_root $request_uri $uri";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
set $debug_data "/ php) $document_root $request_uri $uri $fastcgi_script_name";
        }
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:18:22:21 +0900] foo php) /home/gallu/test2/ /foo/t.php /foo/t.php /foo/t.php /home/gallu/test2/t.php

よし、乗った。

……ってことは、 location / のほうもこっちでいいんぢゃね?

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    location /foo/ {
        alias /home/gallu/test2/;
set $debug_data "foo) $document_root $request_uri $uri";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name $request_filename";
        }
    }

    #
    location / {
        root /home/gallu/test/;
set $debug_data "/) $document_root $request_uri $uri";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "/ php) $document_root $request_uri $uri $fastcgi_script_name $request_filename";
        }
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:18:23:49 +0900] / php) /home/gallu/test /t.php /t.php /t.php /home/gallu/test/t.php

いけたいけた。
よしとりあえずこれを1つの起点にしよう。
フレームワークでルーティングとかしない系なら、大体これでいけそうだ。
……とはいえ世間的には「フレームワーク? 使わない理由ないよね?」くらいの勢いも多いので、ここからさらに魔窟に踏み込んでみよう。

未知部分への踏み込み

try_files を使った内部的な移動

鍵になるのは try_files というヤツですね。
http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files

Checks the existence of files in the specified order and uses the first found file for request processing; the processing is performed in the current context. The path to a file is constructed from the file parameter according to the root and alias directives. It is possible to check directory’s existence by specifying a slash at the end of a name, e.g. “$uri/”. If none of the files were found, an internal redirect to the uri specified in the last parameter is made.

大雑把には「ファイルの存在をざっくりナメていって、なかったら一番最後の引数に従う」的な感じだと思われます。
ちょうどtest.htmlがあるので、「なかったらtest.htmlにする」って感じのを書いてみましょう。
ほぼまっさらな所から、改めて書いてみます。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    root /home/gallu/test/;
    #
    try_files $uri $uri/ /test.html ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename";

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:18:38:50 +0900] /) /home/gallu/test / /index.html /index.html /home/gallu/test/index.html
[DEBUG][24/Feb/2023:18:38:58 +0900] /) /home/gallu/test /test.html /test.html /test.html /home/gallu/test/test.html
[DEBUG][24/Feb/2023:18:39:14 +0900] /) /home/gallu/test /foo/ /test.html /test.html /home/gallu/test/test.html
[DEBUG][24/Feb/2023:18:39:58 +0900] /) /home/gallu/test /foo/hoge /test.html /test.html /home/gallu/test/test.html

うん、意図通り。
見る限りでは「request_uriは変わらず。urifastcgi_script_nameとrequest_filenameは書き換わる」感じなんだ。

あと一応、HTMLの時点で「URLに付けるパラメタ」あたりを確認しておこう。
変数的には以下があるぽい。

$is_args
“?” if a request line has arguments, or an empty string otherwise

$args
arguments in the request line

$query_string
same as $args

ざっとconfigに書いて、値を確認。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    root /home/gallu/test/;
    #
    try_files $uri $uri/ /test.html ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args is:$is_args (fin";

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:18:47:38 +0900] /) /home/gallu/test /index.html /index.html /index.html /home/gallu/test/index.html ar: is: (fin
[DEBUG][24/Feb/2023:18:47:47 +0900] /) /home/gallu/test /index.html?hoge=hoge /index.html /index.html /home/gallu/test/index.html ar:hoge=hoge is:? (fin
[DEBUG][24/Feb/2023:18:48:06 +0900] /) /home/gallu/test /index.html?hoge=hoge&foo=foo /index.html /index.html /home/gallu/test/index.html ar:hoge=hoge&foo=foo is:? (fin
[DEBUG][24/Feb/2023:18:48:27 +0900] /) /home/gallu/test /foo/hoge=hoge&foo=foo /test.html /test.html /home/gallu/test/test.html ar: is: (fin

ほむ。……あぁtry_filesで書き換わるから、最後のやつ、パラメタ引き継いでないのか。
みると「is_argsで?の有無、argsで(あったら)パラメタ」って感じだなぁ。
修正。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;

    #
    root /home/gallu/test/;
    #
    try_files $uri $uri/ /test.html$is_args$args ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args is:$is_args (fin";

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:18:50:54 +0900] /) /home/gallu/test /foo/?hoge=hoge&foo=foo /test.html /test.html /home/gallu/test/test.html ar:hoge=hoge&foo=foo is:? (fin

try_files でPHPを絡めてみる

パラメタ付きでちゃんと動くか、まずはざっくり思いつきで。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;


    location / {
        root /home/gallu/test/;
        try_files $uri $uri/ /test.php$is_args$args ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args is:$is_args (fin";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "/ php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args is:$is_args (fin";
        }
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:18:56:02 +0900] / php) /home/gallu/test /foo/?hoge=hoge&foo=foo /test.php /test.php /home/gallu/test/test.php ar:hoge=hoge&foo=foo is:? (fin

あ、動いた。
うん……なんとなくわかってはきたなぁ。
&
PHP側で確認をするに、「$_SERVER['REQUEST_URI']」でちゃんと「元々のcallしているURL(とパラメタ)」はとれるので。
多分、フレームワーク側の処理はそんなに問題ないんじゃなかろうか? と予想してみる感じ。

未知の本番

ルート直下にLaravelを入れてみる

composer create-project laravel/laravel 

でLaravelをざっくりinstall。
rootを少し書き換えて、まずは動かしてみる。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;


    location / {
        root /home/gallu/test/public/ ;
        try_files $uri $uri/ /index.php$is_args$args ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "/ php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:20:46:49 +0900] /) /home/gallu/test/public / / / /home/gallu/test/public/ ar:

あれ? 403になる……あぁ index 入れないとだめか。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;


    location / {
        root /home/gallu/test/public/ ;
        index index.php index.html;
        try_files $uri $uri/ /index.php$is_args$args ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "/ php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:20:48:46 +0900] / php) /home/gallu/test/public / /index.php /index.php /home/gallu/test/public/index.php ar:

よしOK。
(Laravelの設定なんもやってないからエラー出てるけどその辺は範疇外なので除外)

念のために、雑に

Route::get('/hoge', function () {
    return 'hoge';
});

のルーティングを追加して確認……挙動OK。

サブディレクトリに「別のフレームワーク」をぶちこんでみる

さてメインの本題のど真ん中。
まずは準備。
手持ちのSlim-Skeletonを入れる。

composer create-project gallu/slim4-skeleton test2

今までの前提を込めて、まずはベタっと書いてみよう。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;


    location /foo/ {
        alias /home/gallu/test2/public/ ;
        index index.php index.html;
        try_files $uri $uri/ /index.php$is_args$args ;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

    location / {
        root /home/gallu/test/public/ ;
        index index.php index.html;
        try_files $uri $uri/ /index.php$is_args$args ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "/ php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

#
access_log /var/log/nginx/debug.log debug;
}

あと、PHP側で

$app->setBasePath('/foo');

を書いてサブディレクトリを指定。

[DEBUG][24/Feb/2023:21:02:54 +0900] foo php) /home/gallu/test2/public/ /foo/ /foo/index.php /foo/index.php /home/gallu/test2/public/index.php ar:

よしOK。

……ただここからパラメタを渡すと

[DEBUG][24/Feb/2023:21:04:01 +0900] / php) /home/gallu/test/public /foo/sample /index.php /index.php /home/gallu/test/public/index.php ar:

ってなる。うん元々の話に戻った。location /foo/ に入ってこずに location / に入っていってしまっているから、DocumentRootが「向いて欲しい所とは別に」なる。

try_filesから先を一足飛びにしすぎたなぁ。
ちょっと戻そう。

location + try_files をもうちょっと深掘りする

いったんFWを外してまたhtmlだけのシンプルな状態にしてテスト。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;


    location /foo/ {
        alias /home/gallu/test2/ ;
        index index.php index.html;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
    }

    location / {
        root /home/gallu/test/ ;
        index index.php index.html;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:21:13:34 +0900] /) /home/gallu/test / /index.html /index.html /home/gallu/test/index.html ar:
[DEBUG][24/Feb/2023:21:13:36 +0900] foo) /home/gallu/test2/ /foo/ /foo/index.html /foo/index.html /home/gallu/test2/index.html ar:

うん動いてる。
ここからこのまま、まずは / のほうだけ try_files を追加して動かしてみよう。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;


    location /foo/ {
        alias /home/gallu/test2/ ;
        index index.php index.html;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
    }

    location / {
        root /home/gallu/test/ ;
        index index.php index.html;
        try_files $uri $uri/ /test.html ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:21:15:46 +0900] /) /home/gallu/test / /index.html /index.html /home/gallu/test/index.html ar:
[DEBUG][24/Feb/2023:21:15:53 +0900] /) /home/gallu/test /test.html /test.html /test.html /home/gallu/test/test.html ar:
[DEBUG][24/Feb/2023:21:15:58 +0900] /) /home/gallu/test /hogera /test.html /test.html /home/gallu/test/test.html ar:

うん、ここまでは想定通り。
さて……fooにも同じように適用してみよう。多分、ここが鍵。

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;


    location /foo/ {
        alias /home/gallu/test2/ ;
        index index.php index.html;
        try_files $uri $uri/ /test.html ;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
    }

    location / {
        root /home/gallu/test/ ;
        index index.php index.html;
        try_files $uri $uri/ /test.html ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:21:17:26 +0900] foo) /home/gallu/test2/ /foo/ /foo/index.html /foo/index.html /home/gallu/test2/index.html ar:
[DEBUG][24/Feb/2023:21:17:31 +0900] foo) /home/gallu/test2/ /foo/test.html /foo/test.html /foo/test.html /home/gallu/test2/test.html ar:
[DEBUG][24/Feb/2023:21:17:38 +0900] /) /home/gallu/test /foo/hogera /test.html /test.html /home/gallu/test/test.html ar:

うん、ここだ。
/foo/hogera のアクセスの時に、 location /foo/ に入らずに location / に入ってるんだ。ここが元凶。

……あぁ違う、/foo/ の中のtry_filesの書き方か?

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;


    location ^~ /foo/ {
        alias /home/gallu/test2/ ;
        index index.php index.html;
        try_files $uri $uri/ /foo/test.html ;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
    }

    location / {
        root /home/gallu/test/ ;
        index index.php index.html;
        try_files $uri $uri/ /test.html ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:21:24:04 +0900] foo) /home/gallu/test2/ /foo/hogera /foo/test.html /foo/test.html /home/gallu/test2/test.html ar:
[DEBUG][24/Feb/2023:21:24:06 +0900] foo) /home/gallu/test2/ /foo/ /foo/index.html /foo/index.html /home/gallu/test2/index.html ar:
[DEBUG][24/Feb/2023:21:24:10 +0900] foo) /home/gallu/test2/ /foo/test.html /foo/test.html /foo/test.html /home/gallu/test2/test.html ar:

よし!!
念のため。

[DEBUG][24/Feb/2023:21:24:28 +0900] /) /home/gallu/test / /index.html /index.html /home/gallu/test/index.html ar:
[DEBUG][24/Feb/2023:21:24:37 +0900] /) /home/gallu/test /test.html /test.html /test.html /home/gallu/test/test.html ar:
[DEBUG][24/Feb/2023:21:24:42 +0900] /) /home/gallu/test /hogera /test.html /test.html /home/gallu/test/test.html ar:

いけた。

改めてフレームワークで確認

server {
    listen       80;
    server_name  dev.example.net;
    access_log /var/log/nginx/dev.example.net.access.log  main;
    error_log /var/log/nginx/dev.example.net.error.log;


    location /foo/ {
        alias /home/gallu/test2/public/ ;
        index index.php index.html;
        try_files $uri $uri/ /foo/index.php$is_args$args ;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

    location / {
        root /home/gallu/test/public/ ;
        index index.php index.html;
        try_files $uri $uri/ /index.php$is_args$args ;
set $debug_data "/) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "/ php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

#
access_log /var/log/nginx/debug.log debug;
}

[DEBUG][24/Feb/2023:21:26:38 +0900] / php) /home/gallu/test/public / /index.php /index.php /home/gallu/test/public/index.php ar:
[DEBUG][24/Feb/2023:21:27:01 +0900] / php) /home/gallu/test/public /hoge /index.php /index.php /home/gallu/test/public/index.php ar:


[DEBUG][24/Feb/2023:21:27:09 +0900] foo php) /home/gallu/test2/public/ /foo/ /foo/index.php /foo/index.php /home/gallu/test2/public/index.php ar:
[DEBUG][24/Feb/2023:21:27:14 +0900] foo) /home/gallu/test2/public/ /foo/sample /foo/sample /foo/sample /home/gallu/test2/public/sample ar:

サブディレクトリ側。とりあえずlocationはちゃんと入ったので、もうちょいだなぁ。

[DEBUG][24/Feb/2023:21:29:03 +0900] foo) /home/gallu/test2/public/ /foo/css/dummy /foo/css/dummy /foo/css/dummy /home/gallu/test2/public/css/dummy ar:

うん、存在しているファイルに対しては問題なく動くんだ。
なので「存在していないファイルの時にtry_filesが適用されて、request_filename が index.phpになる」ところさえ担保できればよい感じだなぁ。

なんだろ……とりあえず「上手くいってる」パターンで書き換えてみよう。

    location /foo/ {
        alias /home/gallu/test2/public/ ;
        index index.php index.html;
        #try_files $uri $uri/ /foo/index.php$is_args$args ;
        try_files $uri $uri/ /foo/test.html ;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

[DEBUG][24/Feb/2023:21:37:26 +0900] foo) /home/gallu/test2/public/ /foo/aaa /foo/test.html /foo/test.html /home/gallu/test2/public/test.html ar:

うまくいくんかい……なんぞ???
ちょっとずつ進めていこう。
とりあえず「test.php」とかってやったらどうなるのかしらん?

    location /foo/ {
        alias /home/gallu/test2/public/ ;
        index index.php index.html;
        #try_files $uri $uri/ /foo/index.php$is_args$args ;
        #try_files $uri $uri/ /foo/test.html ;
        try_files $uri $uri/ /foo/test.php ;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

[DEBUG][24/Feb/2023:21:40:08 +0900] foo php) /home/gallu/test2/public/ /foo/aaa /foo/test.php /foo/test.php /home/gallu/test2/public/test.php ar:

うごくやん……次。

    location /foo/ {
        alias /home/gallu/test2/public/ ;
        index index.php index.html;
        #try_files $uri $uri/ /foo/index.php$is_args$args ;
        #try_files $uri $uri/ /foo/test.html ;
        #try_files $uri $uri/ /foo/test.php ;
        try_files $uri $uri/ /foo/index.php ;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

[DEBUG][24/Feb/2023:21:41:52 +0900] foo php) /home/gallu/test2/public/ /foo/aaa /foo/index.php /foo/index.php /home/gallu/test2/public/index.php ar:

あれ? 動く?
……戻してみよう。

    location /foo/ {
        alias /home/gallu/test2/public/ ;
        index index.php index.html;
        #try_files $uri $uri/ /foo/index.php$is_args$args ;
        #try_files $uri $uri/ /foo/test.html ;
        try_files $uri $uri/ /foo/test.php ;
        #try_files $uri $uri/ /foo/index.php ;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

[DEBUG][24/Feb/2023:21:45:42 +0900] foo) /home/gallu/test2/public/ /foo/sample /foo/sample /foo/sample /home/gallu/test2/public/sample ar:

うん戻った……$is_args$args が悪さしてる? パラメタないのに……これ、付けないとパラメタ渡らないんかなぁ???
試してみよう。

    location /foo/ {
        alias /home/gallu/test2/public/ ;
        index index.php index.html;
        #try_files $uri $uri/ /foo/index.php$is_args$args ;
        #try_files $uri $uri/ /foo/test.html ;
        try_files $uri $uri/ /foo/test.php ;
        #try_files $uri $uri/ /foo/index.php ;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

結果……うん$_SERVER['REQUEST_URI']には入ってるんだけど、$_GETには入っていかないねぇ……念のため追試。

    location /foo/ {
        alias /home/gallu/test2/public/ ;
        index index.php index.html;
        #try_files $uri $uri/ /foo/index.php$is_args$args ;
        #try_files $uri $uri/ /foo/test.html ;
        #try_files $uri $uri/ /foo/test.php ;
        try_files $uri $uri/ /foo/test.php$is_args$args ;
        #try_files $uri $uri/ /foo/index.php ;
set $debug_data "foo) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";

        #
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            include        fastcgi.conf;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
set $debug_data "foo php) $document_root $request_uri $uri $fastcgi_script_name $request_filename ar:$args";
        }
    }

>||
[DEBUG][24/Feb/2023:21:50:50 +0900] foo) /home/gallu/test2/public/ /foo/aaa?a=b&c=d /foo/aaa /foo/aaa /home/gallu/test2/public/aaa ar:a=b&c=d

うん、駄目だ orz


「もうちょい」なんだが、そこで煮詰まったなぁ……ちょっと放置。
後で見直したらなんか気づくかもしれないし誰かが突っ込んでくれるかもしれないので、いったん、寝かせよう orz

「validationをValue Objectで行う」を考えてみた

なんか「ふと思いついた」程度のお話ではあるのですが。

まず前提として「Value Objectとは、一意性がなく交換可能なもの」としておきます。おいちゃんの好み的に「イミュータブル」であって欲しいと思ってますが、その辺はまぁ余談。
あと、ちょろっと出てくる「Entity」については「一意性がある、データの塊」くらいに把握しておいてもらえればとりあえず会話は成り立つかなぁ、と思ってます。

超大雑把に、ですが。
例えば「User」っていうentityがあって、このUser entityの中には「user_idというValue Objectと姓名というValue ObjectとemailというValue Objectと誕生日というValue Object」があって、みたいな感じをイメージしていただけるとよろしかろうか、と思います。

このとき。https://gallu.hatenadiary.jp/entry/2019/05/16/225759 で書きましたが、少なくとも「Controller的な所で、入ってくる時にvalidateする」のをおいちゃんはあまり好まないので。
上述の場合はentityにvalidateを実装していた、のですが。
ふと「あれ? 各Value Objectでvalidateしてもいいんぢゃね?」とか思ったんですね。

emailは、それが「userの中にあろうとも」「ほかのナニカの中にあろうとも」基本的には「email用のvalidate」をしたいだろうし。
誕生日も同様だろうし、姓名も同様だと思うんですね。
だとすると「各validate処理自体は各Value Objectでやって、entityはそれをとりまとめて依頼して結果を拾い集めるだけ」ってやると、なんか割とうまくいくんぢゃね? とか思ったですます。

超大雑把にコードっぽいものを書いてみると。
以前は、entityに

$validate = [
    'user_id' => 'required|int', // 文字列で指定する書き方
    'email' => ['required', 'email'], // 配列で指定する書き方
    'birthday' => ['date'],
];

とかって書いてたイメージなのですが。
これを、まずentityのほうでは

$type_cast = [
    'user_id' => ValueObject\UserId::class,
    'email' => ValueObject\Email::class,
    'birthday' => ValueObject\Birthday::class,
];

って感じで「データが入ってくる時にこの型(クラス)にキャストするよ」って宣言しておいて、あとは各Value Object側で適宜validateしておくれやす、的な。
Value Objectのvaludateは多分「コンストラクタで値を取り込んだ時にvalidate、駄目なら例外投げるのをentityが個々にキャッチ」って感じじゃないかなぁ???

まぁ「完全にValue Objectのみに実装」だと、例えば「BBSとかのentityで"投稿されたメッセージ"までValue Object作るん? 面倒くね?」とか「こっちのAというValue Objectがこの値の時はBというValue Objectがこうなってて欲しい、的に相互が関連するもの」もあるので、その辺はまぁ「entityへの実装も許容する」ほうが現実問題として楽だろう、と思うのですが。

そすると、Enity的には、超雑ですがこんなイメージかなぁ、と(カラムとか適当に追加してます)。

// 各カラムのキャスト型の指定
$type_cast = [
    'user_id' => ValueObject\UserId::class,
    'email' => ValueObject\Email::class,
    'birthday' => ValueObject\Birthday::class,
    'memo' => 'string', // これは単純に「文字列型にする」くらいのイメージの指定(スカラ型はこんな感じでいいかなぁ、と思ってる)
];
// (ValueObjectでは行われない追加の)valudate指定
$validate = [
    'hoge' => ValidateRule\HogeRule::class, // validateルールをクラスで指定しておく(ちょっと複雑な子もこれで安心)
    'memo' => ['required', 'min_length:10', 'max_length:100'], // 従来のvalidateもここでちゃんと書ける
];
// 「1カラムでは片付かない」valudateの追加実装
protected function validate(): bool
{
    // 元々のvalidateの実装をcall
    $r = parent::validate();

    // 追加のvalidate
    $r2 = {AがXXの時はBがYYであること、的な実装が書いてあるとおもいねぇ};

    // 親のvalidateと追加のvalidateをがっちゃんこしてreturn
    return $r && $r2;
}

(おいちゃんが作ってるSlimLittleToolsだと、validation、厳密には「insertのみで適用する用」と「updateのみで適用する用」も書けるので、もうちょっとだけ複雑にはなりますが、大枠はこんなもんかなぁ)

そうすると、 https://twitter.com/mpyw/status/1619951659047858181 に書かれているような感じの「validateのルールをクラス名で渡す」も一緒にあわせて実装すると、まぁ少し「方法が多様になる」にしても、気をつければ割ときれいだったり楽だったりする実装になるんじゃないかなぁ? と。
(多分、ValidateRuleとかって名前とValueObjectValidationとかって名前のインタフェースとか切るんだろうなぁ、と、もわもわ妄想中)

なんかちょっと突飛だけどどうかなぁ?
……と思ったら、思ったよりあちこちで考察されてましたよ。ですよね~w

https://qiita.com/kotauchisunsun/items/e319e4c4b093d6add74b#valueobject%E5%9E%8B
https://qiita.com/j5ik2o/items/bd77f2da6d445ee4268a

なんか「ふと思いついた」だけなので、どこかで検証コードというかアプリケーションざっくり実装してみたいなぁ。
「検証用に使うための汎用の仕様」とか作って、なんか色々な方法で実装してみようかしらん? とか、思ってみたりもしたりはする。

いったん今回は「脳内妄想垂れ流し」なんで、コード書いたらまた追記します ノ

queue:work の --memory 引数が……ちょっと……

php artisan queue:work には、色々な引数があるようです。
バージョンにもよるんだろうなぁ、と思うのですが、とりあえず手元の 8.83.27 のバージョンでお話を進めます。

とりあえず引数の一覧は、vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php によれば

    protected $signature = 'queue:work
                            {connection? : The name of the queue connection to work}
                            {--name=default : The name of the worker}
                            {--queue= : The names of the queues to work}
                            {--daemon : Run the worker in daemon mode (Deprecated)}
                            {--once : Only process the next job on the queue}
                            {--stop-when-empty : Stop when the queue is empty}
                            {--delay=0 : The number of seconds to delay failed jobs (Deprecated)}
                            {--backoff=0 : The number of seconds to wait before retrying a job that encountered an uncaught exception}
                            {--max-jobs=0 : The number of jobs to process before stopping}
                            {--max-time=0 : The maximum number of seconds the worker should run}
                            {--force : Force the worker to run even in maintenance mode}
                            {--memory=128 : The memory limit in megabytes}
                            {--sleep=3 : Number of seconds to sleep when no job is available}
                            {--rest=0 : Number of seconds to rest between jobs}
                            {--timeout=60 : The number of seconds a child process can run}
                            {--tries=1 : Number of times to attempt a job before logging it failed}';

こんな感じのようです。
うんまぁ例えばsleepとかtimeoutとか、triesとかもまぁバッチ的には「あるし必要だしわかるし」って感じなのですが。

memoryが、ぶっちゃけまぁ「直感的ではないんじゃないかねぇ?」とか思うわけでございます。
「The memory limit in megabytes(メガバイト単位のメモリ制限)」とかあるわけ、なのですが、ちょっとこれ語弊があるんじゃないかねぇ? とか思ったりします。

先に答えをゲロると
・「ここで指定したメモリ以上のメモリを食うとバッチを止める」実装はある
PHPのmemory_limitを引き上げるような動作はしない
・--onceが指定されたら、メモリチェックはしない
となるようです。

1番目はまぁそりゃそうなのですが、多分引っかかりそうなのが2番目の「PHPのmemory_limitを引き上げるような動作はしない」。3番目は……そもこの引数「使われてるのかね?」とか思うんですが、使ってるとすると、もしかしたらちょろっと気になるかも。
なんか普通にこの引数を考えると「この引数まではメモリを確保してくれる」ような動作を期待したいような気がするのですが、実際には「やってないと思われます」。

コードを雑に追いかけている限り

vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php

    public function handle()
    {
        if ($this->downForMaintenance() && $this->option('once')) {
            return $this->worker->sleep($this->option('sleep'));
        }

        // We'll listen to the processed and failed events so we can write information
        // to the console as jobs are processed, which will let the developer watch
        // which jobs are coming through a queue and be informed on its progress.
        $this->listenForEvents();

        $connection = $this->argument('connection')
                        ?: $this->laravel['config']['queue.default'];

        // We need to get the right queue for the connection which is set in the queue
        // configuration file for the application. We will pull it based on the set
        // connection being run for the queue operation currently being executed.
        $queue = $this->getQueue($connection);

        return $this->runWorker(
            $connection, $queue
        );
    }

から

    protected function runWorker($connection, $queue)
    {
        return $this->worker->setName($this->option('name'))
                     ->setCache($this->cache)
                     ->{$this->option('once') ? 'runNextJob' : 'daemon'}(
            $connection, $queue, $this->gatherWorkerOptions()
        );
    }

から
vendor/laravel/framework/src/Illuminate/Queue/Worker.php

    public function daemon($connectionName, $queue, WorkerOptions $options)
    {
        if ($supportsAsyncSignals = $this->supportsAsyncSignals()) {
            $this->listenForSignals();
        }
(中略)
            // Finally, we will check to see if we have exceeded our memory limits or if
            // the queue should restart based on other indications. If so, we'll stop
            // this worker and let whatever is "monitoring" it restart the process.
            $status = $this->stopIfNecessary(
                $options, $lastRestart, $startTime, $jobsProcessed, $job
            );

            if (! is_null($status)) {
                return $this->stop($status);
            }
        }
    }

から

    protected function stopIfNecessary(WorkerOptions $options, $lastRestart, $startTime = 0, $jobsProcessed = 0, $job = null)
    {
        if ($this->shouldQuit) {
            return static::EXIT_SUCCESS;
        } elseif ($this->memoryExceeded($options->memory)) {
            return static::EXIT_MEMORY_LIMIT;
        } elseif ($this->queueShouldRestart($lastRestart)) {
            return static::EXIT_SUCCESS;
        } elseif ($options->stopWhenEmpty && is_null($job)) {
            return static::EXIT_SUCCESS;
        } elseif ($options->maxTime && hrtime(true) / 1e9 - $startTime >= $options->maxTime) {
            return static::EXIT_SUCCESS;
        } elseif ($options->maxJobs && $jobsProcessed >= $options->maxJobs) {
            return static::EXIT_SUCCESS;
        }
    }

から

    public function memoryExceeded($memoryLimit)
    {
        return (memory_get_usage(true) / 1024 / 1024) >= $memoryLimit;
    }

ってな感じぽいです。
ちな、コードほじくってて気づいたのですが、
vendor/laravel/framework/src/Illuminate/Queue/Listener.php

    public function runProcess(Process $process, $memory)
    {
        $process->run(function ($type, $line) {
            $this->handleWorkerOutput($type, $line);
        });

        // Once we have run the job we'll go check if the memory limit has been exceeded
        // for the script. If it has, we will kill this script so the process manager
        // will restart this with a clean slate of memory automatically on exiting.
        if ($this->memoryExceeded($memory)) {
            $this->stop();
        }
    }
(略)

    public function memoryExceeded($memoryLimit)
    {
        return (memory_get_usage(true) / 1024 / 1024) >= $memoryLimit;
    }

ってコードもあるんですよねぇ……まぁおいといて。

なので。
多分なんとなく「PHPががっつり落としてくる前に手前でコントロールできている間にバッチを止める」ってのを意図している一方で「メモリの上限は特に上げてくれない」っぽいんですよねぇ。
あと、細かい所で「明示的に --once の時は多分、上述のメモリチェックもしない」感じですねぇおそらく。

おそらくなんですが、意図としては「バッチがdaemonのようにグルグル動き続ける時に、PHPメモリリークとか含めて"メモリ大量消費"されるのを防ぎたい」って感じ、なんじゃないんですかねぇおそらく???
ただまぁ、だとしたらもうちょっと命名を変えたほうがいいような気がせんでもないのですが……まぁ、まぁ。

軽くググってみた限りだと、意外に記事がなかったぽいので、ちょっくら一筆。

ログ/履歴の類いはマスタテーブルとjoinしないしFKも張らない

いやまぁそのまんまなのですが。
おいちゃんの今までの経験的に

・ログとか履歴とか明細とかそーゆー類いのDBは「その1テーブル(群)で情報が完結する」ようにしておいたほうが圧倒的によい

と思っているので、その辺について少しかみ砕いて。

色々と痛いものは拝見しているのですが……記憶にある限りで割と「最悪」に近かったのは……どれだろう……うんあの「レンタル系」のを取り出してみよう(案件がバレないように、適当に情報を操作隠蔽しています)。
システムの大雑把な概要としては「とあるもの」のレンタルの管理です。
「量産品で数があるもの」ではなくて、割と「個々に比較的ユニークな」物品ですね。

レンタルなので

レンタル対象の物品のテーブル
 レンタル対象の物品id
 対象物の名前
 諸々の情報
 1単位時間あたりのレンタル費

的な、いわゆるレンタル対象物品のマスターテーブルがあります(量産品のレンタル「ではない」ので。まぁ量産品のレンタルでも論旨は一緒なんですが)。
んで、レンタルすると

レンタルテーブル
 id
 レンタル対象の物品id FK
 ユーザID FK
 レンタルfrom_date
 レンタルto_date nullable
 その他情報

とかってテーブルで管理して、まぁ大雑把には「レンタルto_dateがnullならレンタル中」とかなんとか。
レンタルテーブルは、ようは「履歴テーブル」ですね。「レンタルの履歴」。厳密には「現在レンタル中の情報+レンタルが終わったものの履歴」。
なのでまぁ気になる人は

・レンタル中テーブル
・レンタル履歴テーブル

とかやってもよいか、と(個人的にはこっちのほうが好み。「1テーブルで複数state」は基本あまり好まないので)。

この場合「レンタル中テーブルにはレンタルto_dateがない」「レンタル履歴テーブルのレンタルto_dateはnullableではない」ってくらいの差異で以下のお話にはやっぱり影響しないのでどっちでも好きな方で。
これで「誰がいつ何をレンタルしたか」が、大体わかりますので、「x年度の、例えばカテゴリ別とかの売り上げ」とかの集計も(物品テーブルからカテゴリがわかる前提ですが)大体一発です。

……ここで気づいた人はおとなしくしているように。




さて。

書いた通りですが、この手のビジネスはn年とか1年とか半期とか四半期とか一定の期間で「どの物品がおいくらほど売り上げたか」とかほにゃららとかありますので、売上金額の計算をします。
レンタルテーブルを左側にして、LEFT JOINでレンタル対象の物品のテーブルくっつけてレンタル費とか引っかけてきてあとは適当にGROUP BYして(書いてないけど「カテゴリ」とか商品にあるだろうし)集計すれば、比較的簡単に適当な表の元ネタができあがります。

んでもって。
「この手順で合計をSUMする」と、端的には「アウトでダウト」になります。

端的には「途中でレンタル費が上がったり下がったりしたら?」
レンタル対象の物品のテーブルはいわゆる「マスタテーブル」なのですが、マスタは基本「現在の情報」なので、「現在の情報」で、例えば「(値上がり前の)3年前の集計」とかやろうとすると、数値が狂い得ます。
なお実際に「n年とn+1年に、同じx年度の集計を出したんだけど出力結果が違うんだけど」という事象に遭遇しましたよ orz

なので個人的には「基本、マスタテーブルのレコードは大体まるっと一通りデータをcopyしておく」ほうがよいと思ってます(明らかにど~でもいい created_at とかは削るにしても)。
別解として「マスタテーブル側をごにょごにょする(具体的には「INSERTのみでUPDATEもDELETEもしない(日付で最新情報と過去の情報を把握)」ってやり方)」もあるのですが、それはそれで割と手間がかかるので、まぁお好みで。

例えばECなんかの「売り上げテーブル + 売り上げ明細テーブル」なんかも、おいちゃん的には「履歴テーブル」だと思っているので、同様。
なお「売り上げ明細と売り上げのJOIN」は「履歴と履歴のJOIN」なので、これは、あり。

なのでまぁ、基本おいちゃんは「履歴テーブルには"全情報を入れる"」って方向に倒すことが多いです。
っていうか「マスタテーブルは時系列を持っていない事が多い」ので、「時系列情報を確保する」か「履歴に全情報を入れる」か、どっちかしないと事故ります*1

……ってあたり、割とちょいちょいお話をする事があるので、一度整理したかったので、ざっくり書いてみました。

*1:ほかの方法がある可能性を否定はしないんだけど、おいちゃんは知らない

名前付き引数とcall_user_func_array

別件で調査をしていて、ふと「あれ? call_user_func_array() にhash配列渡したら、名前付き引数的にいい感じに処理してくれるんぢゃね?」って思ったので、早速実験。

名前付き引数(PHP8.0から)
https://www.php.net/manual/ja/functions.arguments.php#functions.named-arguments

まず前提になる「今までの」コード。

<?php
declare(strict_types=1);
error_reporting(-1);

//
function test($a, $b, $c) {
    var_dump($a, $b, $c);
}

//
call_user_func_array('test', ['aaa', 'bbb', 'ccc']);

string(3) "aaa"
string(3) "bbb"
string(3) "ccc"

うん。
では、まず「すなおな」hash配列。

call_user_func_array('test', ['a' => 'aaa', 'b' => 'bbb', 'c' => 'ccc']);

string(3) "aaa"
string(3) "bbb"
string(3) "ccc"

まぁここまでは。
ちなみにPHP7.4系で動かしても結果は同じでした。

次に「順番を入れ替え」。

call_user_func_array('test', ['b' => 'bbb', 'c' => 'ccc', 'a' => 'aaa']);

string(3) "aaa"
string(3) "bbb"
string(3) "ccc"

あぁ、予想通り。
ちなみにPHP7.4系だと以下の通り。

string(3) "bbb"
string(3) "ccc"
string(3) "aaa"

ではここから少し意地悪系。
まずは「追加でNGがある」ケース。

call_user_func_array('test', ['b' => 'bbb', 'c' => 'ccc', 'a' => 'aaa', 'd' => 'ddd']);

Fatal error: Uncaught Error: Unknown named parameter $d in ...

うんまぁそうだろうなぁ的な。
ちなPHP7.4系だと「エラーは出ないで動く」。まぁパラメタが多い時に「(自作関数だと)何も言ってくれない」からなぁ……。
したら一応、足りない系も。

call_user_func_array('test', ['b' => 'bbb', 'c' => 'ccc']);

Fatal error: Uncaught ArgumentCountError: test(): Argument #1 ($a) not passed in ...

で~す~よ~ね~。
予想通りの動きでなにより。

うっすらと懸念がないわけではない、にしても、基本的には「hashのほうがわかりやすい」ケースが増えてくるんじゃないかしらん???
興味深かったので、メモかねて。

追伸
なお、元々調査していて期待していた「phpunitのdataProvider」では使えませんでした orz
array_values() とかやってる感じなんかなぁ?(なんかそんな雰囲気の挙動)

「TRPGとは?」っていう壮大な疑問への1回答を書いてみる

どっちかってぇと「TRPGをすでにやっている人たちと議論をするための土台」というよりは「まだ一度もやったことがない人に向けての説明」って切り口を想定してますんで、そんな感じで。

イメージとしてベースにあると楽なのが、コンピュータのPRG(ドラクエとかFFとか)、あるいはオンラインゲームのMMORPG(FF14とか)あたり。
あれらは「人間が(ゲーム内の自分の分身である)キャラクターを動かして」「コンピュータが色々な処理(戦闘で攻撃が当たったかどうか?の判断とか)をする」ような感じですが。
この「コンピュータが色々な処理をする」の部分が人間になり(往々にして、マスターとかキーパーとかステラーとか呼ばれます)、「キャラクターを動かす人間(プレイヤー、と言う事が多いです)が複数人になる(あたりが、MMOっぽい)」感じです。

これを、往々にして「複数人でテーブル(卓)を囲んで」「言葉を使って会話をしながら」RPGを楽しむので「テーブルトークRPG(TRPG)」って呼称される……ってな説明が多いかと思います。
なので、(まぁコンピュータの補助があるかもしれないけど、基本的には)人間が処理をして人間がキャラクタを動かして遊ぶ、ので、がっつり「人間と人間のゲーム」になります。ボードゲームとかウォーシミュレーションとかと一緒ですねその辺は。

人間がルールの処理をするので、創意工夫や提案などを盛り込む余地がある事も多く、人によって色々な楽しみ方ができるのは、魅力の一つだと思います。

また、最近は、YouTubeなどで動画配信もあるようなので(その辺、おいちゃんは詳しくないのであまり深く言及ができないのですが)。
「楽しみ方は色々なので、そのうちの1バリエーションである」前提ではありますが、雰囲気などを知るために動画を見てみるのも楽しいと思います。

さて。 TRPGは、大まかに
・システム(ルール+世界観)
サプリメント
・シナリオ
・「TRPGの楽しみ方」のターゲット
によって色々なバリエーションが出てくるかなぁ、と思っています。
以下、簡単にかみ砕いてみます。

システム(ルール+世界観)

TRPGは「ゲーム」なので、ゲームのルールが記載されているもの(本)があります。これが「システム」と呼ばれるものです。
有名どころは……書くと偏りがあるので色々と言われそうではありますが、「クトゥルフ神話TRPG」「シノビガミ」「ソード・ワールド」「シャドウラン」「ダブルクロス」「パラノイア」「ログ・ホライズン」「マギカロギア」「ダンジョンズ&ドラゴンズ」などがあります(ほかにもまだいっぱい)。
また、多くのシステムは大体「世界観」を持っていて、それは例えばファンタジーだったりSFだったり現代社会だったりします(たまに「世界観を持たない」システムもありますが、その場合はサプリメント(後述)で追加される事が多いです)。
その世界観によって、例えば「いわゆるファンタジー世界なので魔法があって核とか家電とかはない」「現代社会なんでネットとコンピュータはあるけど魔法も超能力もない。なおこの世界では日本でも銃器を持てるし使える」なんていう「そのシステム(世界観)の中での"常識"」が、大雑把だったり細かくだったりしますが、定義されていきます。

なので「システム」が決まると「ゲームを行う上でのルール」と「基本的な世界観」が(ある程度)決まります。
(マスター側としては「やりたいこと」から「それにマッチする世界観やルール(=システム)を選択する」事も多いです)

サプリメント

TRPGサプリメント(とかエキスパンションとか拡張ルールとか)と書かれると、それは「ルールや世界観に追加を与えるもの」になります。
これによって、「新しい世界観が追加」されたり「今まで謎だった**が明らかに」なったり「新しい武器や道具や能力やその他のルールが増えたり足されたり」します。
たまに「以前のルールで、明らかに問題があったものの修正」が入る事もありますね。

シナリオ

基本的には「マスターが用意するもの」です(自作か他作かはともかく)。
TRPGでは、1回分のゲームを「セッション」と言いますが。「今日のセッションはどんな感じの流れなのか」が、大雑把だったり詳細だったりしますが、書いているのがシナリオです。
たまに「このシステムの本来の世界観はAだけど、今回のシナリオはBの世界観でやります」といった感じで世界観が変わったりする事もあります、ので、「今日のシナリオはどんな感じなのか」はちゃんと聞いておくとよいでしょう。

TRPGの楽しみ方」のターゲット

善し悪しではないのですが、TRPGは人によって「色々な楽しみ方ができる」ので、「なにを楽しみたいのか」を、大枠くらいではすりあわせておいたほうがよいと思います。
「何を重視してプレイするか」で……

TRPGは元々シミュレーションから発生しているので「戦闘などを、ルールに厳密に則って処理する」楽しみ方があります。
・扱っているキャラクタの「役割」や「性格」を重視して、そのキャラクター(の性格、または役割)を「演じる」楽しみ方があります。
・シナリオと(大抵は複数の)キャラクタがセッションをしていくと、それはさながら「一つの物語」のようになる事もあります。そんな「物語を紡ぐ」楽しみ方があります。
・シナリオの多くは「謎の解明」や「ダンジョンのクリア」などわかりやすい目標がある事も多く、そういった「出された問題をクリアする」楽しみ方があります。
 → この「クリア」も、「パーティでクリアする」楽しみや「他の人を出し抜いて自分が利益をたくさん得る」楽しみ方などがあります。
・また、最近は動画で配信する事もあるので、「外に魅せるように演技したり台詞回しを工夫したりして、配信の反応などを楽しむ」楽しみ方があります。

上述は必ずしも排他ではないのですが。 例えば「どちらかというと現実っぽいシビアさとルールの厳格さを楽しむ」傾向と「どちらかというとドラマチックで劇的で主人公補正を楽しむ」傾向は、比較的混ざりにくいので。
「どんな楽しみ方をしたいのか」のすりあわせは、相応に重要だと思います。
また併せて「自分が好まない楽しみ方」は、あくまでも「Not for meなだけ」であることも踏まえておくとよいでしょう。

最後に

身も蓋もない話なのですが、「人間同士の遊び」なので、相性も相応に重要になってきます。
相性のよいマスターさんやプレイヤーさん、というのがある一方で、相性が悪いマスターさんやプレイヤーさんもいるか、と思いますので。
一つには「第一印象を大切に」しつつ、なんどか遊んでみるとよいと思います。

また、初めてTRPGをやる時。オフラインのセッション(コンベンション、というものが定期的に、有志によって開かれています)の時は。
最低限「筆記用具(シャープペンシルと消しゴム)」を持参するとよいでしょう。
通常、サイコロ(ダイス)も必要になるのですが、少し特殊なサイコロが必要な事もあるので、大体の場合は「マスター(か親切なプレイヤー)が貸してくれる」ので、あんまり気にしなくてよいと思います。

プレイするときは「どんな事をしたいのか」をできるだけ明確にして、それをマスターに伝えてみると、事故が減ると思います。
「どんな事をしたいか」はそんなに大上段に構えなくてもよいので。マスターの説明などを聞いて「あ、これ面白そう」と思ったらそれを伝えてみる、くらいから始めてみてもよいかと思います。

そうして、なんどかプレイして楽しかったら、好きなシステムで使うサイコロを買ったり、システムを(購入可能なシステムなら)購入してみるとよいでしょう。

May the "Nice TRPG" be with you.

神聖魔法と信仰魔術

「神聖魔法」と「信仰魔術」。
どちらもいわゆる「神々の信奉者」が使うものだが、両者には明確な区分がある。

神聖魔法は「聖なるものの存在が"ある"ことを前提にした魔法」であり、それ以外の魔法と同様に「"魔力を扱うための法則"の1形態」である、と捉えることができる、ために神聖"魔法"と呼称される。

信仰魔術は「信仰対象となる"何者か"が組み上げた"魔法を扱う術"である魔術」を、他者が(資格などを貸与されて)使う形態である。ために信仰"魔術"と呼称される。

神聖魔法はその成り立ちから原理原則的には「"法則領域に起因する不得手な範囲"を除いて、大体のことができる」。
一方で、相応の学識や難易度、技術や適正などが求められるため、「誰にでも使える」わけではない。また厳密には「魔法の才能次第」なところがあるため、「それ以外をもって上下関係を構築したい」組織においては、時として面倒があるケースも存在する。

信仰魔術は極論「資格さえ貸与されていれば」おおよそ誰でも使うことができるが、その術式に「明示されていない効果(魔力の奉納など)が付随する」ことがあり、かつそれは原則として不可分であるため、非効率な術式も少なからず存在する。

比較的多くの寺院神殿においては信仰魔術が用いられ、その術式には「魔力の奉納」が含まれる(その魔力は、大抵の場合「寺院神殿の保護/運営」に使われる)。
資格付与は「信者として洗礼/得度すること」を起点に、寺院神殿内でのランク上昇に伴ってある程度までは段階的に付与されることが多い。
そのため、「高位の司祭ほど、高度な魔術を使うことができる」が、だいたい、成り立ちうる。

一部の寺院神殿においては、「ある程度高位になった(≒魔術に熟達した)司祭」は、別途、神聖魔法の手ほどきを受けることがある。
神聖魔法は(知識や技術さえあれば)信仰魔術より高度なことを為し得るため、信仰魔術と比較して「より柔軟」であったり「より高度」であったりする魔法を駆使することが、可能性としては、ありうる。
神聖魔法においては「資格の貸与」は存在しないが、「必要な学識の開示」によって資格の貸与を擬似的に模倣していることが多いため(一部の天才は例外)、「高位の司祭ほど、高度な魔術を使うことができる」は、結果的に引き続き成り立つ、ことが多い。

さらにごく一部の寺院神殿においては「信仰魔術は原則として存在せず、扱う術はすべて神聖魔法」というケースもある。
ただこれは例外に近いため「一般的には」で語るほどの数は存在しない。