がるの健忘録

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

「そうだパスワードの持ち方、変えよう」

色々あっていろいろあったのに触発されました(笑
いや昔実際に実務で実装したこともあったんで、その辺を踏まえて。

大前提として
・ログインロジックに介入できる
・ユーザの「最終ログイン時間」が捕捉できる(ケースによる)
が必要になりますんでご注意を。
最終ログイン時間については「今からそのロジック追加」で間に合います。

次。
用語整理軽く。

Salt(ソルト)
hash時の味付け。詳しくは「salt hash」あたりでググってください。
この記事では勝手に「ユーザ個別のもの」をsaltって言っておきます(いちいち書くのが面倒なので)。
「共通のsalt」は、「共通salt」と明記しておきます。

Pepper(SecretSalt)(ペッパー、シークレットソルト)
これも「ペッパー hash」でググっていただくのが早いかと。

もういっちょ。
ログインのロジックの、以下に介入する前提です。だいたいイメージで察してください。
ベースになるコードイメージです。
端的には「idに対応するレコードがなければfalse、入力パスワードと保存パスワード比較して不一致ならfalse。idがokでパスワードもokならtrue」って感じ。
「そっくりそのまんま」とまでは言わんにしても、まぁ近似値な処理がきっとある、はず!!(笑

public function login認証処理メイン($id, $raw_password): bool  
{  
    // DBとかから「idに対応する、hash化されている(はずの)パスワード文字列」を取得  
    $hashed_pw = XXXX();  
    if ($hashed_pwが取れなかった(idが存在しない)) {  
        // 駄目ぽ  
        return false;  
    }  

    // hashの突合せ  
    if (hash値の比較($raw_password, $hashed_pw) === false) {  
        // 駄目ぽ  
        return false;  
    }  

    // OKだったぽいので認証通る  
    return true;  
}  

ほんでもって次。
パスワードを「どのように保存しているか」は、たぶん、こんなグラデーションなんではないかなぁ、と。
(ペッパーの有無はいったん省きます。リストが2倍になるの面倒なんで)

  • 平文
  • md5
  • 共通salt付きmd5(ストレッチあったりなかったり
  • 個別salt付き適当なhash(ストレッチあったりなかったり
  • Modular Crypt Formatで適切なやつ(古くなったhashアルゴリズム)
  • Modular Crypt Formatで適切なやつ(良い感じのhashアルゴリズム)

さて一通り出そろいましたので、入れ替えを見ていきましょう。

MCFなんだけどアルゴリズム古し

今が「Modular Crypt Formatで適切なやつ(良い感じのhashアルゴリズム)」なら別段なにもせんでえぇので。
1ランク下がりました「Modular Crypt Formatで適切なやつ(古くなったhashアルゴリズム)」である場合を考えてみましょう。

古いんで新しいのに差し替えたいです。
おおざっぱには

  • (古いのだったら)新しいアルゴリズムのに差し替える
  • 適当に時間がたったら「パスワードhashのアルゴリズム」確認して、差し替え前アルゴリズムなら、一度パスワードのレコードを空文字とかにする

ってな感じになります。

実装。
ちょいと丁寧にすると、だいたいまぁこんな感じか、と。
password_hash()使ってる前提です。

public function login認証処理メイン($id, $raw_password): bool  
{  
    // DBとかから「idに対応する、hash化されている(はずの)パスワード文字列」を取得  
    $hashed_pw = XXXX();  
    if ($hashed_pwが取れなかった(idが存在しない)) {  
        // 駄目ぽ  
        return false;  
    }  

    // hashの突合せ  
    if (password_verify($raw_password, $hashed_pw) === false) {  
        // 駄目ぽ  
        return false;  
    }  

    /* OKだったぽいので */  
    // hashのアルゴリズムが古かったら、新しいのに差し替える  
    $password_info = password_get_info($hashed_pw);  
    if ($password_info['algoName'] !== 意図する新しいhashアルゴリズム) {  
        update ユーザテーブル set password = password_hash($raw_password, 新しいアルゴリズムかDEFAULTか適当に, 必要ならオプション) WHERE userを特定する情報;  
    }  

    // 認証通る  
    return true;  
}  

こんな感じ。
これで「ログイン通過してhashが古い」人は、新しいhashに書き換わります。

……そこから幾星霜が経過しました(長い)。

そろそろ「古いhash、本格的に撲滅しねぇ?」って感じの頃合いでございます。それだけ時間がたったんだってば。
こんなbatchを組んでみましょう。

$users = userテーブルの全レコード;  
foreach ($users as $u) {  
    $hashed_pw = $uのpasswordhash取得();  
    // hashのアルゴリズムが古かったら、パスワードいったん消す  
    $password_info = password_get_info($hashed_pw);  
    if ($password_info['algoName'] !== 意図する新しいhashアルゴリズム) {  
        update ユーザテーブル set password = '' WHERE userを特定する情報($uにあるはず);  
        なんかログに残したり表示したり  
    }  
}  

これで「古いhashは撲滅」できます。
後は、ログのuser_idでもいいし、改めてDBからselectしてもいいし、のターゲットに対して
「しばらくログインがなかったからパスワード無効にしたのでパスワードリマインダで設定してから再ログインしてね」
とかって連絡をすれば、一段落。

MCFじゃなかったんだけどまぁそこまでクリティカルにマズい持ち方してない

次。
「個別salt付き適当なhash(ストレッチあったりなかったり」を想定してみましょう。
他よりはましなのですが、MCFではないので色々と面倒が付きまといます。
サクっと本体実装を見ていきましょう。
大まかには

  • MCFで認証通ったらそのまま
  • 古いhash方式で認証通ったら「MCFに置き換えつつ」認証OK
  • そうじゃなきゃNG

って感じですね。

public function login認証処理メイン($id, $raw_password): bool  
{  
    // DBとかから「idに対応する、hash化されている(はずの)パスワード文字列と個別のsalt」を取得。ストレッチ回数は「ここに入れてるけど多分どこかglobalな領域にあるんじゃね?」を想定  
    $hashed[hadhed_password, 個別のsalt, ストレッチ回数] = XXXX();  
    if ($hashedが取れなかった(idが存在しない)) {  
        // 駄目ぽ  
        return false;  
    }  

    /* MCFでのhashの突合せ */  
    if (password_verify($raw_password, $hashed['hadhed_password']) === true) {  
        // 新しい方式で認証通ったのでOK  
        return true;  
    }  

    /* 古い方式での認証突合せ */  
    // raw_passwordをhash化する  
    $in_hashed_password = salt付けてストレッチしてハッシュするメソッド($raw_password, $hashed[個別のsalt], $hashed[ストレッチ回数]);  
    // hashされた値の比較  
    if (hash_equals($in_hashed_password, $hashed['hadhed_password']) === true) {  
        // hashのアルゴリズムが古かったら、新しいのに差し替える  
        $password_info = password_get_info($hashed_pw);  
        if ($password_info['algoName'] !== 意図する新しいhashアルゴリズム) {  
            // 別に個別saltは空にしなくてもいいんだけど、一応  
            update ユーザテーブル set password = password_hash($raw_password, 新しいアルゴリズムかDEFAULTか適当に, 必要ならオプション), 個別salt='' WHERE userを特定する情報;  
        }  
        // 認証通る  
        return true;  
    }  

    // 認証NG  
    return false;  
}  

これでまたしばらくしたら「古い方式を無効にする」ので。
「幾星霜」よりもうちょっと短めのほうがいいかも、ではあるけど(早めにロジック掃除したいじゃん?)まぁ任意。
同じバッチを組めばOK。

$users = userテーブルの全レコード;  
foreach ($users as $u) {  
    $hashed_pw = $uのpasswordhash取得();  
    // hashのアルゴリズムが古かったら、パスワードいったん消す  
    $password_info = password_get_info($hashed_pw);  
    if ($password_info['algoName'] !== 意図する新しいhashアルゴリズム) {  
        update ユーザテーブル set password = '' WHERE userを特定する情報;  
        なんかログに残したり表示したり  
    }  
}  

$password_info['algoName']、たしか「unknown」だかなんだか、になったはず(うろ覚え)。
あと、このタイミングで「古いhashでの認証処理ロジックを削除」「個別saltのカラムを削除」「ストレッチ回数の情報の破棄」とかやってもよいですね。

MCFじゃなかったしクリティカルにマズい持ち方してる

さて、残るは

・平文
md5
・共通salt付きmd5(ストレッチあったりなかったり

というゴツ目なあたり。
いや身もふたもない「即時に片付く」方法が一応ありまして。

・ログインのロジック(とか周辺あちこち)を「MCFのパスワード」に置き換える(ステージングくらいまでで確認)
・メンテナンスに入る
・プログラムをデプロイ
・DBのパスワードカラムを全部「空文字」でupdateする
・メンテナンス終わり
・全ユーザに「ごめんパスワードリマインダからパスワード設定しなおして」の連絡をする

と、あら不思議「脆弱なパスワードがなくなります」(笑
いやマジなところ、上3つならコレやったほうがいいような気がするんですけどねぇ。
一度、マジで提案してみましょう。通ったらお互いに「少しだけ」幸せになれるwww

でも「ど~しても難しい」場合、以下の手順を踏むとまぁ「ギリ」かなぁ、と。
「明日、クラックされた」を前提にする分、ちょいと内部的には手間でございます。

「以下のコードに修正する」と「DBに細工をする」を同じタイミングで流す(メンテナンスのin-outで対応しましょう)

DB小細工。これによって「とりあえずパスワードを「MCFフォーマット(なので最悪漏れても時間稼ぎが出来る状態)」に置き換える

$users = userテーブルの全レコード;  
foreach ($users as $u) {  
    update ユーザテーブル set password = password_hash($u->password, DEFAULTなりお好きなアルゴリズムなり, オプションもお好みで) WHERE $u->user_id;  
}  

次、認証側のコード。
入っているパスワードが「平文」の場合、ある意味気楽で。
以下のコードでいけます。

public function login認証処理メイン($id, $raw_password): bool  
{  
    // DBとかから「idに対応する、hash化されている(はずの)パスワード文字列」を取得  
    $hashed_pw = XXXX();  
    if ($hashed_pwが取れなかった(idが存在しない)) {  
        return false;  
    }  

    // hashの突合せ  
    if (password_verify($raw_password, $hashed_pw) === false) {  
        // 駄目ぽ  
        return false;  
    }  

    // OKだったぽいので認証通る  
    return true;  
}  

md5だったり「共通salt付きmd5」だったりするときは、こんなふうにしましょう。

public function login認証処理メイン($id, $raw_password): bool  
{  
    // DBとかから「idに対応する、hash化されている(はずの)パスワード文字列」を取得  
    $hashed_pw = XXXX();  
    if ($hashedが取れなかった(idが存在しない)) {  
        // 駄目ぽ  
        return false;  
    }  

    // MCFでのhashの突合せ  
    if (password_verify($raw_password, $hashed_pw) === true) {  
        // 新しい方式で認証通ったのでOK  
        return true;  
    }  

    /* 古い方式での認証突合せ */  
    // raw_passwordを(以前のやり方で)hash化する(共通saltとストレッチ回数があるパターン。ない時は引数調整して)  
    $old_hashed_password = salt付けてストレッチしてハッシュするメソッド($raw_password, 共通salt, ストレッチ回数);  

    // 「古いやり方でhash化した」パスワードを「生パスワード」と仮定して、値の比較  
    if (password_verify($old_hashed_password, $hashed_pw) === true) {  
        // 本来の正しいのに置き換える  
        update ユーザテーブル set password = password_hash($raw_password, 新しいアルゴリズムかDEFAULTか適当に, 必要ならオプション) WHERE userを特定する情報;  
          
        // 認証通る  
        return true;  
    }  

    // 認証NG  
    return false;  
}  

これで多分そのうちなんとか。
最悪漏れてもこれなら「普通にちゃんとセキュリティ担保したパスワードとして有効な保持の仕方」なので、どうにかなるでしょ。

で……この方式のみ「切り替わってるんだか切り替わってないんだかわからない」ので。
もし「古いロジック残しておいても面倒だから適当なタイミングで"更新していないユーザのパスワード"リセットしたい」のであれば、最終ログイン時間を使って「上にあるバッチ」で「最終ログイン時間が一定以上過去の人のパスワードを空文字にする」でやればOK。

なんていう方法がありますよ、というおおざっぱな提案でした。
……事故が減るといいなぁ。
& 変な所とか怪しいところとか危ない所があったら、突っ込んでもらえれば速攻で直します!!

phinxを使ってみた(マイグレーション変)

とりあえずinstall

composer require robmorgan/phinx

initで初期化をするらしい。

vendor/bin/phinx init

phinx.php が生えた。

<?php

return
[
    'paths' => [
        'migrations' => '%%PHINX_CONFIG_DIR%%/db/migrations',
        'seeds' => '%%PHINX_CONFIG_DIR%%/db/seeds'
    ],
    'environments' => [
        'default_migration_table' => 'phinxlog',
        'default_environment' => 'development',
        'production' => [
            'adapter' => 'mysql',
            'host' => 'localhost',
            'name' => 'production_db',
            'user' => 'root',
            'pass' => '',
            'port' => '3306',
            'charset' => 'utf8',
        ],
        'development' => [
            'adapter' => 'mysql',
            'host' => 'localhost',
            'name' => 'development_db',
            'user' => 'root',
            'pass' => '',
            'port' => '3306',
            'charset' => 'utf8',
        ],
        'testing' => [
            'adapter' => 'mysql',
            'host' => 'localhost',
            'name' => 'testing_db',
            'user' => 'root',
            'pass' => '',
            'port' => '3306',
            'charset' => 'utf8',
        ]
    ],
    'version_order' => 'creation'
];

なるほど。
このフォーマットなら「他のFW(おいちゃんの想定はSlim)使ってて、そっちで(も)DBの接続設定がある」なんてケースでもちゃんとDRYに出来るねぇ!!
1点気になってたのが一瞬で氷塊したので、まぁ普通に「使う」前提でよいのではないかしらん???

見るに
・いったんのデフォはdevelopment
っぽいので、そのままテスト用のDBの設定をdevelopmentに書いて、追加で実験してみませう。

お次。
必要なディレクトリを作るぽ。

mkdir -p db/migrations db/seeds

これはまぁ実際には「このディレクトリもgitに登録しておく」とかで良さそうざます。
&
configの設定の「%%PHINX_CONFIG_DIR%%」がちょい気になるが、まぁなんかあったら後で出てくるでせう。
次。

眼目になるマイグレーションファイル作成。
本家サイトとか、アッパーキャメルなのがちょいと気になるなぁ……いったんヘビにしてみよう。

php vendor/bin/phinx create table_name

The migration class name "table_name" is invalid. Please use CamelCase format.

わをwww
キャメル固定なのかwww

では改めて。

php vendor/bin/phinx create TableName

Phinx by CakePHP - https://phinx.org.

using config file phinx.php
using config parser php
using migration paths
- /home/furu/PhinxTest/db/migrations
using seed paths
- /home/furu/PhinxTest/db/seeds
using migration base class Phinx\Migration\AbstractMigration
using default template
created db/migrations/20230819124347_table_name.php

ほむ……seedとmigrationsにファイルが出来てる? 確認。

seedには……ない。
なんじゃろ???
(後で分かった。ここ。seederを作る所なんだ)

migrationsにはファイルが出来てる。

<?php
declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class TableName extends AbstractMigration
{
    /**
     * Change Method.
     *
     * Write your reversible migrations using this method.
     *
     * More information on writing migrations is available here:
     * https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
     *
     * Remember to call "create()" or "update()" and NOT "save()" when working
     * with the Table class.
     */
    public function change(): void
    {

    }
}

簡素だなをいw
んで、指定したのは純粋に「クラス名」なのか。それ以外の処理、なんもないw

(RDB準備中...RDB準備中...)

軽く書いていこう。
書く場所は
・change() に書くと、(ある程度までは)マイグレーションを巻き戻す時、勝手に良い感じに巻き戻してくれる
・でなきゃ、おとなしく up() と donw() を書け
らしい。

で……ちょっと気になる記述も見かけたので、その辺の確認込みで、いくつか書いてみる。

    public function change(): void
    {
        $table = $this->table('test');
        $table->addColumn('name', 'string')
            ->addColumn('str', 'varchar', ['limit' => 128])
            ->addColumn('str2', 'varbinary', ['limit' => 128])
            ->addColumn('num', 'int', ['comment' => 'コメント'])
            ->addColumn('num2', 'bigint')
            ->addColumn('created_at', 'datetime')
            ->create();

    }

ドライランがあるとの事で、早速やってみる。
あと、 -e での環境指定を「あえて」省略してみる(configにデフォ値あったし)。

php vendor/bin/phinx migrate --dry-run

InvalidArgumentException: An invalid column type "varchar" was specified for column "str".

あそ。
他いくつか修正。

    public function change(): void
    {
        $table = $this->table('test');
        $table->addColumn('name', 'string')
            ->addColumn('str', 'string', ['limit' => 128])
            ->addColumn('str2', 'varbinary', ['limit' => 128])
            ->addColumn('num', 'integer', ['comment' => 'コメント'])
            ->addColumn('num2', 'biginteger')
            ->addColumn('created_at', 'datetime')
            ->create();

    }

CREATE TABLE `test` (
`id` INT(11) unsigned NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NULL,
`str` VARCHAR(128) NULL,
`str2` VARBINARY(128) NULL,
`num` INT(11) NULL COMMENT 'コメント',
`num2` BIGINT(20) NULL,
`created_at` DATETIME NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

あら?
うん前情報通り「勝手にID入れてくる」のは気になるが、VARBINARYに対応しているのはおいちゃん的に大分と高ポイントざますよ?
ほむ……ちょいと本気を出してみよう(実験用なんでテーブルレイアウトはきにすんな)。

    public function change(): void
    {
        $table = $this->table('test', [
            'id' => false,
            'primary_key' => 'test_id',
            'comment' => 'テーブルコメント',
        ]);
        $table
            ->addColumn('test_id', 'biginteger', ['null' => false, 'signed' => false, 'identity' => true,])
            ->addColumn('name', 'string')
            ->addColumn('str', 'string', ['limit' => 128, 'null' => false])
            ->addColumn('str2', 'varbinary', ['limit' => 128])
            ->addColumn('num', 'integer', ['comment' => 'コメント'])
            ->addColumn('num2', 'biginteger')
            ->addColumn('created_at', 'datetime', ['null' => false, 'default' => 'CURRENT_TIMESTAMP'])
            ->create();

        //
        $table = $this->table('test_2', [
            'id' => false,
            'primary_key' => ['test_id', 'str'],
            'comment' => 'テーブルコメント',
        ]);
        $table
            ->addColumn('test_id', 'biginteger', ['null' => false, 'signed' => false, 'identity' => true,])
            ->addColumn('str', 'string', ['limit' => 128, 'null' => false])
            ->create();
    }

CREATE TABLE `test` (
`test_id` BIGINT(20) unsigned NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NULL,
`str` VARCHAR(128) NOT NULL,
`str2` VARBINARY(128) NULL,
`num` INT(11) NULL COMMENT 'コメント',
`num2` BIGINT(20) NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`test_id`)
) ENGINE = InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='テーブルコメント';

CREATE TABLE `test_2` (
`test_id` BIGINT(20) unsigned NOT NULL AUTO_INCREMENT,
`str` VARCHAR(128) NOT NULL,
PRIMARY KEY (`test_id`,`str`)
) ENGINE = InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='テーブルコメント';

よし、よし。
ちょっと調べるのに時間がかかったけど、大体いけるっぽいなぁ。
よしちゃんと実行してみやう。

php vendor/bin/phinx migrate
mysql> show tables;
+----------------------+
| Tables_in_phinx_test |
+----------------------+
| phinxlog             |
| test                 |
| test_2               |
+----------------------+
3 rows in set (0.00 sec)

mysql> show create table test \G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `test_id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `str` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,
  `str2` varbinary(128) DEFAULT NULL,
  `num` int DEFAULT NULL COMMENT 'コメント',
  `num2` bigint DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`test_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='テーブルコメント'
1 row in set (0.00 sec)

mysql> show create table test_2 \G
*************************** 1. row ***************************
       Table: test_2
Create Table: CREATE TABLE `test_2` (
  `test_id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `str` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`test_id`,`str`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='テーブルコメント'
1 row in set (0.00 sec)

んむんむ、よきよき。
一応、rollback

php vendor/bin/phinx rollback
mysql> show tables;
+----------------------+
| Tables_in_phinx_test |
+----------------------+
| phinxlog             |
+----------------------+
1 row in set (0.01 sec)

よきよき。

一応メモ程度に残しておくと。

php vendor/bin/phinx migrate -e 環境名
php vendor/bin/phinx rollback -e 環境名

ってのが本来っぽい感じではあるので、一応。
ただまぁこれだと

・ステージングまでは普通に「コードupしてからマイグレーションする」
・本番は「先にマイグレーションしてからコードupする」

とかもできそうだなぁ……ちょいと運用も考えてみやう。

大雑把に
・「テーブル作る」とか「カラム追加する」くらいなら、change() に実装すればよさげ
・一応「SQLダイレクト発行」もある、みたいだけど、現状だとあんまりいらなさそうな雰囲気がある
・まぁ必要になったら「up()とdown()で実装しつつSQL書く」でよいんじゃなかろうか
って感じなので。
「便利な機能はあるけど簡単にoffれるから足かせにならないので気楽」って感じだなぁ。
うん、ちょいと使い込んでみてもよいかも。ちょっと癖があるから色々調べなきゃいかんけどw

次回はseederを確認してみませう。

「始めて」の○○の学び方 おいちゃん変

チラと拝見したツイート群……多分、この辺が発端かなぁ???

https://twitter.com/tokuhirom/status/1682531879990497280

周りのエンジニアに一歩差をつける方法として、フレームワークミドルウェアやライブラリのドキュメントを最初から最後までちゃんと読む、というのがあって、これはマジでコスパ抜群です。

概ね同意なんだけど、ちょろっと前提もあるので、その辺を書いていこうかなぁ、と。
つぶやこうかとも思ったんだけど、残しておきたかったので、こちらで書きます。
元は、学校で「ドラッカーの書籍とか紹介する時」に話をしてる内容なのですが、大体そのまま当てはまるので、まとめて(笑

初めは「初心者本」の系譜を読むといいです。
なんでかというと「簡単だから」(笑

「初心者に向けている」ので、多分「読みやすさ、わかりやすさ」には一定の配慮がある、はず、なので。
まずはその辺で「楽に」スタートを切るとよいです。

で、おいちゃんがオススメするのは「異なる著者で、できたら2~5冊」。
1冊だとどうしても偏りが出る可能性が否定できないので、数冊「異なる著者で」読むと、その辺がある程度、なだらかになる可能性が期待できます。

読み終わると「とりあえずなんとなく全体像のふわっとしたところが一部分くらいは脳内に残っている」事が期待されるので。
そうしたら「本丸」を責めます。
ドラッカー本なら「ドラッカー本人が書いた書籍」、フレームワークとかなら「公式のドキュメント」とか「公式のチュートリアル」。

そうすると「あぁ読んだ」から「え? 書いてなかった」とか「ん? 書いてあった事と違う気がするぞ?」まで、色々な知識が得られるんじゃなかろうか、と思います。
+
フレームワークだと、その後に「フレームワークソースコード」とか、いくつか読んでみるのもオススメですね。

あと、Twitterでちょいちょい拝見していたのが「読むよりまず実装」系のご意見で。
それはそれで「あり」なんですが、どこかで「一度は基礎を一通りおさらい」しないと「欠けている事に気づかない」とか、ちょいちょい拝見するので。
「初手」ではなくてもよいのですが、どこかで一度「一通り、公式に目を通す」はやっておいたほうがいいです。

ネタ的に興味深くて参戦してみたかったので、書いてみました。

紅茶 ディンブラ

こちらも ヒュッゲ(Hygge) さんのところの。
ブランド名的には「FIKADAGS(フィーカダグス)」で、よいのかしらん?
なんかこのディンブラって「買うところ気をつけないと結構難しい」って見かけてたんだけど、これはなんていうか「サラっとしててとても飲みやすい」感じ。
なにげに、がぶ飲みの大本命の1つになるかも? くらいに気に入ったですます。

紅茶 ウバ

またしてもウバなんだけど、今回は ヒュッゲ(Hygge) さんのところの。
ブランド名的には「FIKADAGS(フィーカダグス)」で、よいのかしらん?
落ち着いた好みのお味で、お値段も廉価なので「がぶ飲み」しやすいwwww

「オリエンタル霊異譚 幽冥鬼使」の確率計算

数学苦手なんで(……とも言ってられないんだが)、馬力で計算してみたw

厳密には「3以上の時は技能値(等)が足される」ので、その辺で少し加減されるんだけど。
いったん「ダイス目だけ」で、陰徳値無考慮で純粋に「ダイスの確率」で確認。
プログラムでざっくり書いてるんで端数誤差あるんだけど、その辺は気にせずに。

0 27.777778%
3 4.320988%
4 4.783951%
5 8.641975%
6 9.567901%
7 12.962963%
8 10.030864%
9 8.641975%
10 6.17284%
11 4.320988%
12 2.314815%
20 0.462963%

「n値以上」だと、こんな感じ。

0以上 100%
3以上 72.222223%
4以上 67.901235%
5以上 63.117284%
6以上 54.475309%
7以上 44.907408%
8以上 31.944445%
9以上 21.913581%
10以上 13.271606%
11以上 7.098766%
12以上 2.777778%
20以上 0.462963%

ここの判定と比較的近似と思われる、2d6の分布は大体こんな感じ。

2 2.78%
3 5.56%
4 8.33%
5 11.11%
6 13.89%
7 16.67%
8 13.89%
9 11.11%
10 8.33%
11 5.56%
12 2.78%

2 100.00%
3 97.22%
4 91.67%
5 83.33%
6 72.22%
7 58.33%
8 41.67%
9 27.78%
10 16.67%
11 8.33%
12 2.78%

比較っぽい表にすると、こんな感じ。

0 - 100%
2 100.00% -
3 97.22% 72.222223%
4 91.67% 67.901235%
5 83.33% 63.117284%
6 72.22% 54.475309%
7 58.33% 44.907408%
8 41.67% 31.944445%
9 27.78% 21.913581%
10 16.67% 13.271606%
11 8.33% 7.098766%
12 2.78% 2.777778%
20 - 0.462963%

一概には言いにくいんだけど、中程度の難易度を想定するときは「平目で、2d6の時より1くらい値を下げておく」をベースにすると、割と、近似するんじゃなかろうか? と。

なお、コード(デバッグ出力があちこちあんのは気にすんな)。

<?php

// 元ネタ
$base = [];
for($i = 1; $i <= 6; ++$i) {
    for($j = 1; $j <= 6; ++$j) {
        for($k = 1; $k <= 6; ++$k) {
            for($l = 1; $l <= 6; ++$l) {
                $base["{$i},{$j},{$k},{$l}"] = [$i, $j, $k, $l];
            }
        }
    }
}
// var_dump($base);

// 確認
$data = [];
foreach($base as $k => $v) {
    // いったん、サイコロの目をsort
    sort($v);
    
    // 先にクリティカル確認
    if ( ($v[0] === $v[1])&&($v[1] === $v[2])&&($v[2] === $v[3]) ) {
        echo "クリット: {$k}\n";
        $data[20] ??= 0;
        $data[20] ++;
        continue;
    }
    // 失敗を算出
    if ( ($v[0] != $v[1])&&($v[1] !== $v[2])&&($v[2] !== $v[3]) ) {
        echo "ファンブル: {$k}\n";
        $data[0] ??= 0;
        $data[0] ++;
        continue;
    }
    // 通常成功を算出(面倒だから馬力)
    if ( ($v[0] === $v[1]) ) {
        $key = $v[2] + $v[3];
        echo "{$k} => {$v[2]} + {$v[3]} = {$key} \n";
    } elseif (($v[1] === $v[2])) {
        $key = $v[0] + $v[3];
        echo "{$k} => {$v[0]} + {$v[3]} = {$key} \n";
    } elseif (($v[2] === $v[3])) {
        $key = $v[0] + $v[1];
        echo "{$k} => {$v[0]} + {$v[1]} = {$key} \n";
    }
    //
    $data[$key] ??= 0;
    $data[$key] ++;
}
//var_dump($data);

// sortして
ksort($data);
// 合計値出して
$total = array_sum($data);
// 各値の確率を出力
foreach($data as $k => $v) {
    printf("%d: %f%% \n", $k, $v/$total*100);
}

やれるところまでやってみよう 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