# ch04 — ログイン状態を `$_SESSION` で保つ

Source: https://php-school.pages.dev/topics/session-cookie/ch04-login-flow/
Site: AIと学ぶPHPの学校 (https://php-school.pages.dev)
Lesson: セッション・Cookie・ログイン
Level: L4

Reference (official): https://www.php.net/manual/ja/book.session.php

## 本文

# ch04 — ログイン状態を `$_SESSION` で保つ

## 概要

Web アプリの **ログイン** はしくみだけ見ると驚くほどシンプル。「ユーザーがフォームから ID とパスワードを送る → サーバーで照合 → 合っていたら `$_SESSION['user_id'] = 42` のように記録」これだけ。以降のリクエストは「`$_SESSION['user_id']` があるか?」を見るだけでログイン判定。ログアウトは `session_destroy()` か `$_SESSION = []`。

ここで **絶対にやってはいけない** のがパスワードを **平文で保存すること**。DB に `password = 'taro123'` と入れるのは事故待ち。PHP 標準の **`password_hash($password, PASSWORD_DEFAULT)`** で必ずハッシュ化し、ログイン時は **`password_verify($input, $hash)`** で照合する。bcrypt / argon2 など現代的なアルゴリズムを PHP が自動で選ぶ神関数。

ログインは本来 **2 リクエスト分の動き** (フォーム送信 → セッション保存 → 別ページで確認) なので、ドリルでは 1 ファイル内で連続実行して擬似化する。ログイン直後の **`session_regenerate_id(true)`** (次の ch06 で深掘り) もここから仕込むのが定石。`php -S` で動かせば `PHPSESSID` Cookie が本物の Session ID として機能する様子を確認できる。

## 学習目標

- 「ログイン」= `$_SESSION` にユーザー情報を入れる、と言える
- 「ログアウト」= `session_destroy()` または `$_SESSION = []`、と言える
- 「2 リクエスト分の動き」を 1 つの PHP で **連続実行** して擬似化できる

## 所要時間

スライド 5 分 + ドリル 2 問 = **30 分**

## ドリル

| no | 内容 |
|---|---|
| 01 | 「ログイン → マイページ表示」を 1 スクリプトで擬似化 |
| 02 | 「ログイン → ログアウト → 状態確認」を 1 スクリプトで擬似化 |

## 本物のセッション動作で確認したい場合

```bash
cd topics/session-cookie/ch04-login-flow/drill/01-login/
php -S localhost:8000 answer.php
```

ブラウザで `http://localhost:8000/?name=太郎` を開くと「ようこそ、太郎さん」が出る。
リロードすると Session が保たれて同じ表示。

## スライド

---
marp: true
theme: dojo
paginate: true
size: 16:9
---

<!--
LLM_CONTEXT:
  Lesson 13 / Chapter 4
  目的: $_SESSION でログイン状態を保つ / session_destroy でログアウト
  扱わない: CSRF (ch05) / regenerate_id (ch06) / setcookie (ch03)
  読み上げ時間目安: 4 分半〜5 分
-->

# ログイン状態を `$_SESSION` で保つ

Lesson 13 / Chapter 4

---

## ログインとは

「**ログインしている**」を、コード上どう表現するか:

```php
<?php
session_start();

// ログイン処理
$_SESSION['user'] = [
    'id'   => 42,
    'name' => '太郎',
];
```

→ **`$_SESSION['user']` に値が入っていれば「ログイン中」**、空なら「未ログイン」。

ただそれだけ。「ログイン状態」という特別な型・関数があるわけではない。

---

## マイページ側の判定

```php
<?php
session_start();

// ログイン判定: Session に user が入っているか
if (!isset($_SESSION['user'])) {
    echo "未ログインです\n";
    exit;
}

$user = $_SESSION['user'];
echo "ようこそ、{$user['name']}さん\n";
```

- `isset($_SESSION['user'])` で「ログイン中か」を判定
- 未ログインなら早期 `exit` で弾く (Web ではリダイレクトに置き換える)
- ログイン中なら `$_SESSION['user']` から名前などを取り出して表示

---

## ログアウトとは

```php
<?php
session_start();

// 方法 A: $_SESSION を空にする
$_SESSION = [];

// 方法 B: Session 自体を破棄する (より確実)
session_destroy();
```

| 方法 | 効果 |
|---|---|
| `$_SESSION = []` | 連想配列を空に。Session ID は残る |
| `session_destroy()` | サーバー側の Session データを **完全に削除** |

→ 本番では両方やる + Cookie の PHPSESSID も `setcookie(..., '', 0)` で消すのが定番。

---

## 採点用: 2 リクエストを 1 スクリプトで擬似化

本来 「ログイン」「マイページ」は **別リクエスト**。CLI 採点は 1 プロセスしか動かないので、**両方を 1 つのスクリプトで連続実行** する:

```php
<?php
// (採点用スタブ ... session の擬似化)

// === リクエスト 1: ログイン ===
$_SESSION['user'] = ['name' => '太郎'];

// === リクエスト 2: マイページ ===
if (isset($_SESSION['user'])) {
    echo "ようこそ、{$_SESSION['user']['name']}さん\n";
} else {
    echo "未ログイン\n";
}
```

これで「Session を介して 2 リクエスト間で状態が引き継がれる」流れを擬似的に体験できる。本物の Web では PHP が Cookie 経由で自動的にやってくれる。

---

## ログアウトの擬似化

```php
<?php
// === リクエスト 1: ログイン ===
$_SESSION['user'] = ['name' => '太郎'];

// === リクエスト 2: ログアウト ===
$_SESSION = [];
session_destroy();

// === リクエスト 3: もう一度マイページ ===
@session_start();   // destroy 後は再 start が必要
if (isset($_SESSION['user'])) {
    echo "ようこそ、{$_SESSION['user']['name']}さん\n";
} else {
    echo "ログアウト済\n";
}
```

ログアウト後は `$_SESSION` が空になるので、マイページ側の `isset` チェックが false に倒れる。

---

## このチャプターでできるようになること

✅ ログイン = `$_SESSION` に user を入れる、と言える
✅ ログアウト = `$_SESSION = []` + `session_destroy()`、と言える
✅ `isset($_SESSION['user'])` でログイン判定を書ける
✅ 「2 リクエスト分の動き」を 1 スクリプトで連続擬似化できる

→ ドリルへ
