# 共通ファイル コードリーディング解説
## 対象ファイル：functions.php / db_tenant.php / db_common.php / app.php

---

## ファイルの依存関係（読み込みの連鎖）

```
各画面（tokui_list.php など）
    │
    ├── require_once common/functions.php    ← ユーティリティ関数群
    ├── require_once common/auth.php         ← 認証関数
    └── require_once config/db_tenant.php   ← テナントDB接続関数
            │
            └── require_once config/db_common.php  ← 共通DB接続関数
                    │
                    └── require_once config/app.php ← 定数定義
```

---

# ■ app.php（定数定義）

このファイルの役割：アプリ全体で使う**定数**を一か所で定義する。

```php
<?php
// config/app.php

define('APP_NAME', 'rerent_web');
```

- `define('定数名', 値)` … 定数を定義する関数。
- 定数は `$` なしで使う。慣習的に**大文字スネークケース**（`APP_NAME`）で書く。
- 一度定義したら変更・上書きできない。
- C# の `const string APP_NAME = "rerent_web";` に相当。

```php
define('BASE_PATH', '/rerent_web');
```

- アプリがサブディレクトリで動く場合の**URLベースパス**。
- `redirect()` や `<link href="...">` のURL生成時に先頭に付ける。
- 例：`/rerent_web/master/tokui_list.php`

```php
// 共通DB接続情報
define('COMMON_DB_SERVER',   'VMSV2022-ANS\OBCINSTANCE4');
define('COMMON_DB_NAME',     'RERENT_COMMON');
define('COMMON_DB_USER',     'MutualUser1');
define('COMMON_DB_PASSWORD', 'mutual_sql_0');
```

- 共通DBの接続情報を定数で管理。
- **本番環境では `.env` ファイルや環境変数に移す**のがベストプラクティス
  （ソースコードに直接認証情報を書くことはセキュリティリスクになる）。
- C# の `connectionString` をConfigファイルで管理するのと同じ発想。

```php
define('SESSION_TIMEOUT_SECONDS', 60 * 60 * 8); // 8時間
```

- `60 * 60 * 8` → `28800`（秒）。計算式のまま書くことで意図が読みやすい。
- C# でも `TimeSpan.FromHours(8)` のように単位を明示するのと同じ考え方。

```php
define('LOG_DIR',        __DIR__ . '/../logs');
define('LOGIN_ERROR_LOG', LOG_DIR . '/login_error.log');
```

- `__DIR__` … このファイル（app.php）が存在するディレクトリの絶対パス。
- `LOG_DIR` を先に定義し、`LOGIN_ERROR_LOG` でそれを参照している（定数の連鎖）。
- C# の `Path.Combine(baseDir, "logs")` に相当。

---

# ■ functions.php（ユーティリティ関数群）

このファイルの役割：全画面で共通して使う汎用関数をまとめる。

---

## ① h()：HTMLエンコード（XSS対策）

```php
function h($value): string
{
    return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}
```

| 部分 | 説明 | C#との対比 |
|------|------|-----------|
| `function h($value)` | 関数定義。引数 `$value` は型指定なし（何でも受け付ける） | `string H(object value)` |
| `: string` | **戻り値の型宣言**。string を返すことを明示 | `: string` の戻り値型（C#と同じ構文） |
| `(string)$value` | 型キャスト。数値や null が来てもstring化してから処理 | `value.ToString()` |
| `htmlspecialchars(...)` | PHPの組み込み関数。HTML特殊文字をエスケープ | `HttpUtility.HtmlEncode()` |
| `ENT_QUOTES` | シングル・ダブル両方のクォートをエスケープする | （C#のHtmlEncodeはダブルのみ対応）|
| `'UTF-8'` | 文字エンコードを明示（日本語対応） | — |

変換される文字：

```
<  →  &lt;      （タグ開始記号）
>  →  &gt;      （タグ終了記号）
&  →  &amp;     （アンパサンド）
"  →  &quot;    （ダブルクォート）
'  →  &#039;    （シングルクォート）
```

なぜ必要か（XSSの例）：

```php
// $name に <script>alert('hack')</script> が入力されたとき

// ❌ エスケープなし → スクリプトが実行される
echo $name;
// 出力: <script>alert('hack')</script>  ← ブラウザが実行！

// ✅ h() でエスケープ → 文字列として表示されるだけ
echo h($name);
// 出力: &lt;script&gt;alert(&#039;hack&#039;)&lt;/script&gt;  ← 無害
```

---

## ② get_string()：$_GET の安全なラッパー

```php
function get_string(string $key, string $default = ''): string
{
    return trim((string)($_GET[$key] ?? $default));
}
```

| 部分 | 説明 | C#との対比 |
|------|------|-----------|
| `string $key` | 引数の型宣言（string のみ受け付ける） | `string key` |
| `string $default = ''` | デフォルト引数。省略時は空文字 | `string defaultVal = ""` |
| `$_GET[$key] ?? $default` | キーが存在しない場合はデフォルト値を使う | `QueryString[key] ?? defaultVal` |
| `(string)(...)` | null など非string型もstring化 | `.ToString()` |
| `trim(...)` | 前後の空白・改行を除去 | `.Trim()` |

3つの処理を1行で行っている：

```php
//  ①キーがなければデフォルト値  ②string化  ③前後の空白除去
return trim(          (string)(  $_GET[$key] ?? $default  ));
```

呼び出し例：

```php
// URL: tokui_list.php?tokui_manage=T001&tokui_name=

get_string('tokui_manage')         // → 'T001'
get_string('tokui_name')           // → ''（空文字。trimにより空白のみも空文字に）
get_string('view_kbn', '0')        // → '0'（キーがないのでデフォルト値）
get_string('存在しないキー')        // → ''（デフォルト値の空文字）
```

---

## ③ post_string()：$_POST の安全なラッパー

```php
function post_string(string $key, string $default = ''): string
{
    return trim((string)($_POST[$key] ?? $default));
}
```

`get_string()` と全く同じ構造で、`$_GET` が `$_POST` に変わっただけ。

---

## ④ redirect()：リダイレクト

```php
function redirect(string $path): void
{
    header('Location: ' . BASE_PATH . $path);
    exit;
}
```

| 部分 | 説明 | C#との対比 |
|------|------|-----------|
| `: void` | 戻り値なし（何も返さない）を明示 | `void` |
| `header('Location: ...')` | HTTPレスポンスヘッダーを送信してブラウザを転送 | `Response.Redirect()` |
| `BASE_PATH . $path` | `/rerent_web` + `/master/tokui_list.php` を結合 | `Path.Combine(basePath, path)` |
| `exit;` | PHPの実行を即時停止。**これがないと処理が継続してしまう** | `Response.End()` |

`exit` が必要な理由：

```php
function redirect(string $path): void
{
    header('Location: /list.php');
    // exit がないと↓が実行されてしまう！
    // DBの削除処理やSELECTが続いて動く → バグの原因
}
```

呼び出し例：

```php
redirect('/master/tokui_list.php?message=' . urlencode('削除しました。'));
// 実際に送信されるヘッダー:
// Location: /rerent_web/master/tokui_list.php?message=%E5%89%8A%E9%99%A4%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%E3%80%82
```

---

## ⑤ getClientIp() / getUserAgent()

```php
function getClientIp(): string
{
    return $_SERVER['REMOTE_ADDR'] ?? '';
}

function getUserAgent(): string
{
    return $_SERVER['HTTP_USER_AGENT'] ?? '';
}
```

- `$_SERVER['REMOTE_ADDR']` … 接続元のIPアドレス（例：`192.168.1.10`）。
- `$_SERVER['HTTP_USER_AGENT']` … ブラウザの種類・バージョン情報。
- どちらも主に**ログ記録**のために使われる。

---

## ⑥ writeLoginErrorLog()：ログイン失敗ログ出力

```php
function writeLoginErrorLog(string $message, array $context = []): void
{
    try {
        if (!defined('LOGIN_ERROR_LOG')) {
            return;
        }
```

- `array $context = []` … 連想配列の引数。デフォルトは空配列。
  - C# の `Dictionary<string, object> context = null` に相当。
- `defined('LOGIN_ERROR_LOG')` … 定数が定義済みかチェック。
  - 定義されていなければ `return` で早期終了（安全装置）。

```php
        $logDir = dirname(LOGIN_ERROR_LOG);

        if (!is_dir($logDir)) {
            mkdir($logDir, 0777, true);
        }
```

- `dirname()` … ファイルパスからディレクトリ部分を取得。
  - C# の `Path.GetDirectoryName()` に相当。
- `is_dir()` … ディレクトリが存在するか確認。C# の `Directory.Exists()` に相当。
- `mkdir($logDir, 0777, true)` … ディレクトリを作成。
  - `0777` … Linuxのパーミッション（全ユーザーに読み書き実行権限）。
  - 第3引数 `true` … 中間ディレクトリも含めて再帰的に作成。
  - C# の `Directory.CreateDirectory()` に相当。

```php
        $date = date('Y-m-d H:i:s');
```

- `date('Y-m-d H:i:s')` … 現在日時をフォーマット。
  - 例：`2025-04-01 09:30:00`
  - C# の `DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")` に相当。

```php
        $lines = [];
        $lines[] = '==============================';
        $lines[] = '[' . $date . '] LOGIN ERROR';
        $lines[] = 'MESSAGE: ' . $message;
        $lines[] = 'IP: ' . $ip;
        $lines[] = 'URI: ' . $uri;
        $lines[] = 'USER_AGENT: ' . $ua;
```

- ログの各行を配列に追加していくパターン（後で一気に結合するため）。
- C# の `List<string>` に `Add()` していくのと同じ発想。

```php
        if (!empty($context)) {
            foreach ($context as $key => $value) {
                if (is_array($value) || is_object($value)) {
                    $value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                }
                $lines[] = strtoupper((string)$key) . ': ' . (string)$value;
            }
        }
```

- `empty($context)` … 配列が空かチェック。C# の `context.Count == 0` に相当。
- `is_array()` / `is_object()` … 型チェック。C# の `is` 演算子に相当。
- `json_encode($value, ...)` … 配列・オブジェクトをJSON文字列に変換。
  - `JSON_UNESCAPED_UNICODE` … 日本語をエスケープせずそのまま出力（`\u5c71\u7530` でなく `山田` と出る）。
  - `JSON_UNESCAPED_SLASHES` … `/` をエスケープしない。
  - C# の `JsonSerializer.Serialize()` に相当。
- `strtoupper()` … 文字列を大文字化。C# の `.ToUpper()` に相当。

```php
        file_put_contents(
            LOGIN_ERROR_LOG,
            implode(PHP_EOL, $lines) . PHP_EOL,
            FILE_APPEND | LOCK_EX
        );
```

- `file_put_contents()` … ファイルに文字列を書き込む。
  - C# の `File.AppendAllText()` に相当。
- `implode(PHP_EOL, $lines)` … 配列を改行コードで結合して1つの文字列に。
  - C# の `String.Join(Environment.NewLine, lines)` に相当。
- `PHP_EOL` … 実行環境の改行コード定数（WindowsなのでCRLF = `\r\n`）。
  - C# の `Environment.NewLine` に相当。
- `FILE_APPEND` … 既存ファイルに追記（上書きしない）。
- `LOCK_EX` … 書き込み中に他のプロセスが同時書き込みしないよう排他ロック。
- `|` … ビットフラグのOR結合（C# でも `FileShare.None | FileShare.Write` のように使う）。

```php
    } catch (Throwable $e) {
        // ログ出力失敗で画面処理を止めない
    }
```

- ログ出力が失敗しても例外を握りつぶし、画面の処理を続行する。
- 「ログのために本処理を止めてはいけない」という設計方針。

---

# ■ db_common.php（共通DB接続）

このファイルの役割：**マルチテナント構成の「共通DB」への接続**を提供する。

```php
require_once __DIR__ . '/app.php';

function getCommonDb(): PDO
{
    $dsn = "sqlsrv:Server=" . COMMON_DB_SERVER . ";Database=" . COMMON_DB_NAME . ";TrustServerCertificate=true";

    return new PDO($dsn, COMMON_DB_USER, COMMON_DB_PASSWORD, [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::SQLSRV_ATTR_ENCODING    => PDO::SQLSRV_ENCODING_UTF8,
    ]);
}
```

### DSN（Data Source Name）とは

```
"sqlsrv:Server=VMSV2022-ANS\OBCINSTANCE4;Database=RERENT_COMMON;TrustServerCertificate=true"
  ↑ドライバ名  ↑サーバー名（インスタンス名付き）  ↑DB名           ↑自己署名証明書を許可
```

C# の接続文字列との対比：

```
C#:   "Server=VMSV2022-ANS\OBCINSTANCE4;Database=RERENT_COMMON;User Id=xxx;Password=xxx;"
PHP:  "sqlsrv:Server=VMSV2022-ANS\OBCINSTANCE4;Database=RERENT_COMMON;TrustServerCertificate=true"
      → ユーザー名・パスワードは DSN ではなく new PDO() の第2・3引数で渡す
```

### PDOオプション（第4引数の連想配列）

```php
[
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::SQLSRV_ATTR_ENCODING    => PDO::SQLSRV_ENCODING_UTF8,
]
```

| オプション | 設定値 | 意味 | C#との対比 |
|-----------|--------|------|-----------|
| `ATTR_ERRMODE` | `ERRMODE_EXCEPTION` | SQL失敗時に例外を投げる | SqlCommand はデフォルトで例外。PDOはデフォルトが無音 |
| `ATTR_DEFAULT_FETCH_MODE` | `FETCH_ASSOC` | fetchAll()の結果を連想配列で返す | DataRow を列名でアクセスするのと同じ |
| `SQLSRV_ATTR_ENCODING` | `SQLSRV_ENCODING_UTF8` | SQL Server との通信をUTF-8で行う | 日本語文字化け対策 |

---

# ■ db_tenant.php（テナントDB接続）

このファイルの役割：**ログイン中の会社のDB**に動的に接続する。

---

## システムの全体構造（マルチテナント）

このシステムは**1つのアプリが複数会社に対応する**マルチテナント構成です。

```
【ログインユーザー】 → 会社コード: 'CORP_A'
       ↓
【共通DB（RERENT_COMMON）】
  M_COMPANY テーブル
  ┌─────────────┬─────────────┬───────────────┬────────────────┐
  │COMPANY_CODE │COMPANY_NAME │DBSERVER        │DBNAME          │
  ├─────────────┼─────────────┼───────────────┼────────────────┤
  │CORP_A       │A株式会社    │SV001          │RERENT_CORP_A   │
  │CORP_B       │B株式会社    │SV002          │RERENT_CORP_B   │
  └─────────────┴─────────────┴───────────────┴────────────────┘
       ↓ CORP_A の行を取得
【テナントDB（RERENT_CORP_A）】に接続
  → M_TOKUI（得意先）、その他業務テーブルにアクセス
```

C# での近い実装：

```csharp
// 会社コードに応じて接続先を切り替える
var companyCode = Session["company_code"];
var company     = commonDb.Companies.First(c => c.CompanyCode == companyCode);
var tenantConn  = new SqlConnection(company.ConnectionString);
```

---

## getTenantDb() の詳細

```php
function getTenantDb(): PDO
{
    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
```

- `session_status()` … セッションの状態を確認する関数。
  - `PHP_SESSION_NONE` … セッションがまだ開始されていない状態。
  - `PHP_SESSION_ACTIVE` … session_start() 済みの状態。
- 二重 `session_start()` はエラーになるため、まず状態を確認してから呼ぶ。

```php
    if (empty($_SESSION['company_code'])) {
        throw new Exception('ログイン情報がありません。');
    }
```

- セッションに会社コードがなければ（未ログイン状態）、例外を投げて処理を止める。
- C# の `throw new Exception("...")` と全く同じ構文。

```php
    $commonDb = getCommonDb();

    $sql = "
        SELECT
            COMPANY_CODE, COMPANY_NAME,
            DBSERVER, DBNAME, DBID, DBPWD
        FROM M_COMPANY
        WHERE COMPANY_CODE = :COMPANY_CODE
          AND ISNULL(ACTIVE_FLG, 1) = 1
    ";

    $stmt = $commonDb->prepare($sql);
    $stmt->execute([':COMPANY_CODE' => $_SESSION['company_code']]);
```

- まず共通DBに接続して、ログイン中の会社の**接続先DB情報を取得する**。
- `execute(['キー' => 値])` … `bindValue()` を使わず配列で一括バインドする書き方。
  - 両方とも安全（SQLインジェクション対策済み）。シンプルな場合はこちらも使われる。

```php
    $company = $stmt->fetch();

    if (!$company) {
        throw new Exception('会社情報が見つかりません。');
    }
```

- `fetch()` … 1レコード取得。C# の `reader.Read()` + 1行分のデータ取得に相当。
- 結果がなければ（`false` が返る） → 例外を投げる。
- `!$company` … `$company` が `false` のとき `true`（`!false === true`）。

```php
    $dsn = "sqlsrv:Server={$company['DBSERVER']};Database={$company['DBNAME']};TrustServerCertificate=true";
```

- `{$company['DBSERVER']}` … 文字列の中に変数を埋め込む（文字列展開）。
  - C# の `$"Server={company.DbServer}"` (文字列補間) に相当。
- DBごとに異なる `DBSERVER` と `DBNAME` を使って DSN を動的に生成している。

```php
    return new PDO($dsn, $company['DBID'], $company['DBPWD'], [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::SQLSRV_ATTR_ENCODING    => PDO::SQLSRV_ENCODING_UTF8,
    ]);
}
```

- 取得したDB情報（サーバー・DB名・ユーザー・パスワード）で PDO を生成して返す。
- DBの認証情報（`DBID`・`DBPWD`）はコードに直書きせず、**DBのテーブルに保存する設計**。
  - 会社が増えてもコードを変更せずにテーブルに行を追加するだけでよい。

---

# ■ 全体まとめ：設計上の重要ポイント

## ① 共通関数でラップして安全性と統一性を確保

```
直接使用（危険・冗長）            ラッパー関数（安全・統一）
─────────────────────────         ────────────────────────────
$_GET['key'] ?? ''               get_string('key')
$_POST['key'] ?? ''              post_string('key')
htmlspecialchars($v, ENT_QUOTES) h($v)
header('Location: ...'); exit;   redirect('/path')
```

## ② 定数で設定値を一元管理

```
app.php で define() → 全ファイルで定数を使える
変更が必要になったら app.php 1か所を直すだけでよい
```

## ③ マルチテナント：会社ごとに異なるDBへ動的接続

```
共通DB（1つ）     テナントDB（会社の数だけある）
  M_COMPANY   →  各会社の業務DB（得意先・その他）
  （接続先情報）
```

## ④ ログ処理は画面処理に影響させない

```php
try {
    // ログ出力（失敗してもOK）
} catch (Throwable $e) {
    // 握りつぶす → 画面の処理は続く
}
```

## ⑤ C# との関数定義の対比まとめ

| PHP | C# |
|-----|-----|
| `function h($value): string` | `string H(object value)` |
| `string $key` | `string key` |
| `string $default = ''` | `string defaultVal = ""` |
| `: void` | `void` |
| `array $context = []` | `Dictionary<string, object> context = null` |
| `throw new Exception('msg')` | `throw new Exception("msg")` |
| `return new PDO(...)` | `return new SqlConnection(...)` |
