> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Session

> 使用 Laravel HTTP session 在請求之間保留資料的方式

## 什麼是 Session

HTTP 是無狀態的協定，本身沒有跨請求保留使用者資訊的機制。
Laravel 的 session 功能提供在多個請求之間保留使用者資料的方法。

登入狀態、購物車商品、表單輸入中的資料等，需在請求之間保留的資訊皆可存到 session。

## Session Driver 的設定

Session 設定寫在 `config/session.php`。
可用 `SESSION_DRIVER` 環境變數（`.env`）切換 driver。

```ini theme={null}
SESSION_DRIVER=database
```

Laravel 標準支援的 driver 如下：

| Driver     | 概述                                        |
| ---------- | ----------------------------------------- |
| `file`     | 存為 `storage/framework/sessions` 底下的檔案（預設） |
| `cookie`   | 以加密 cookie 儲存於瀏覽器                         |
| `database` | 儲存到資料庫的資料表                                |
| `redis`    | 儲存到 Redis（高速）                             |
| `array`    | 儲存到 PHP 陣列（測試用，不會跨請求保留）                   |

<Info>
  Laravel 預設專案已設定 `database` driver。使用 `database` driver 需要 `sessions` 資料表，但預設 migration 已包含，可直接使用。
</Info>

## 存取 Session 的方法

存取 session 有兩種方式：透過 `Request` 實例的方法，或使用全域 helper `session()`。

### 使用 Request 實例

在 controller 方法以 `Request` type-hint 接收，並用 `$request->session()` 存取 session。

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\View\View;

class DashboardController extends Controller
{
    public function index(Request $request): View
    {
        $username = $request->session()->get('username');

        return view('dashboard', ['username' => $username]);
    }
}
```

### 使用全域 helper `session()`

`session()` helper 函式可在 controller、view 等任何位置呼叫。

```php theme={null}
// 取得值
$value = session('key');

// 附預設值取得
$value = session('key', '預設值');

// 儲存值
session(['key' => 'value']);
```

<Tip>
  兩種方式在測試時都能用 `assertSessionHas` 方法驗證。在 controller 中使用 `$request->session()` 可讓依賴更清楚。
</Tip>

## Session 的操作

### 取得資料

```php theme={null}
// 以 key 取得
$value = $request->session()->get('key');

// 指定預設值
$value = $request->session()->get('key', 'default');

// 以 closure 提供預設值
$value = $request->session()->get('key', function () {
    return 'default';
});

// 取得所有 session 資料
$data = $request->session()->all();
```

### 檢查資料存在

```php theme={null}
// 值存在且非 null 時為 true
if ($request->session()->has('user_id')) {
    // ...
}

// 即使值為 null，只要存在就為 true
if ($request->session()->exists('user_id')) {
    // ...
}

// 不存在時為 true
if ($request->session()->missing('user_id')) {
    // ...
}
```

請注意 `has` 與 `exists` 的差異：

| 方法       | `null` 時   |
| -------- | ---------- |
| `has`    | 回傳 `false` |
| `exists` | 回傳 `true`  |

### 儲存資料

```php theme={null}
// 透過 Request 實例
$request->session()->put('user_name', '田中');

// 透過全域 helper
session(['user_name' => '田中']);

// 附加到陣列
$request->session()->push('cart.items', ['id' => 1, 'name' => '商品A']);
```

### 刪除資料

```php theme={null}
// 刪除特定 key
$request->session()->forget('user_name');

// 一次刪除多個 key
$request->session()->forget(['user_name', 'cart']);

// 刪除所有 session 資料
$request->session()->flush();
```

## Flash 資料

Flash 資料是**僅在下一次請求**有效的 session 資料。
用於表單送出完成訊息、錯誤訊息等只想顯示一次的資訊。

### 用 `flash()` 儲存資料

```php theme={null}
$request->session()->flash('status', '已儲存文章。');
```

此資料在下次請求可讀取，之後會自動刪除。

### 延長 flash 資料

```php theme={null}
// 將所有 flash 資料再延長 1 個請求
$request->session()->reflash();

// 只延長特定 key
$request->session()->keep(['status', 'message']);
```

### 在 Blade 顯示 flash 訊息

```blade theme={null}
{{-- resources/views/layouts/app.blade.php --}}

@if (session('success'))
    <div class="alert alert-success">
        {{ session('success') }}
    </div>
@endif

@if (session('error'))
    <div class="alert alert-danger">
        {{ session('error') }}
    </div>
@endif
```

<Tip>
  Flash 訊息的顯示寫在 layout 檔案（如 `layouts/app.blade.php`），在所有頁面都能一致顯示。
</Tip>

## 實用範例：表單送出後的訊息顯示

送出表單後將成功訊息存到 session 並 redirect，在下個頁面顯示，是 Web 應用的常見模式。

<Steps>
  <Step title="定義路由">
    ```php theme={null}
    use App\Http\Controllers\PostController;

    Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
    Route::get('/posts/create', [PostController::class, 'create'])->name('posts.create');
    Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
    ```
  </Step>

  <Step title="Controller 的實作">
    在接收表單送出的 `store` 方法中，儲存後附帶成功訊息 redirect。

    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

    use App\Models\Post;
    use Illuminate\Http\RedirectResponse;
    use Illuminate\Http\Request;
    use Illuminate\View\View;

    class PostController extends Controller
    {
        public function index(): View
        {
            $posts = Post::latest()->get();

            return view('posts.index', ['posts' => $posts]);
        }

        public function create(): View
        {
            return view('posts.create');
        }

        public function store(Request $request): RedirectResponse
        {
            $validated = $request->validate([
                'title' => 'required|string|max:255',
                'body'  => 'required|string',
            ]);

            Post::create($validated);

            return redirect()
                ->route('posts.index')
                ->with('success', '已建立文章。');
        }
    }
    ```

    `redirect()->with('success', '...')` 是將 flash 資料傳到 redirect 目的地的捷徑。
  </Step>

  <Step title="在 layout 顯示訊息">
    在 `resources/views/layouts/app.blade.php` 加入 flash 訊息的顯示部分。

    ```blade theme={null}
    <!DOCTYPE html>
    <html>
    <head>
        <title>My App</title>
    </head>
    <body>
        {{-- flash 訊息 --}}
        @if (session('success'))
            <div class="alert alert-success">
                {{ session('success') }}
            </div>
        @endif

        @if (session('error'))
            <div class="alert alert-danger">
                {{ session('error') }}
            </div>
        @endif

        @yield('content')
    </body>
    </html>
    ```
  </Step>

  <Step title="建立列表頁的 view">
    ```blade theme={null}
    {{-- resources/views/posts/index.blade.php --}}

    @extends('layouts.app')

    @section('content')
    <h1>文章列表</h1>

    @foreach ($posts as $post)
        <div>
            <h2>{{ $post->title }}</h2>
            <p>{{ $post->body }}</p>
        </div>
    @endforeach
    @endsection
    ```

    文章儲存並 redirect 後，此列表頁的上方會顯示一次「已建立文章。」訊息。
  </Step>
</Steps>

### 與驗證錯誤結合的模式

驗證失敗時搭配 `back()->withErrors()` 使用。
Laravel 於驗證失敗時會自動將錯誤保留為 flash 資料，Blade 中可使用 `$errors` 變數。

```php theme={null}
public function store(Request $request): RedirectResponse
{
    $validated = $request->validate([
        'title' => 'required|string|max:255',
        'body'  => 'required|string',
    ]);

    Post::create($validated);

    // 成功時：附帶 flash 訊息 redirect
    return redirect()
        ->route('posts.index')
        ->with('success', '已建立文章。');

    // 驗證失敗時 Laravel 會自動執行 back()->withErrors()->withInput()
}
```

```blade theme={null}
{{-- 表單頁 --}}

@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

<form method="POST" action="{{ route('posts.store') }}">
    @csrf

    <div>
        <label for="title">標題</label>
        <input
            id="title"
            type="text"
            name="title"
            value="{{ old('title') }}"
        />
        @error('title')
            <span>{{ $message }}</span>
        @enderror
    </div>

    <div>
        <label for="body">內文</label>
        <textarea id="body" name="body">{{ old('body') }}</textarea>
        @error('body')
            <span>{{ $message }}</span>
        @enderror
    </div>

    <button type="submit">送出</button>
</form>
```

<Warning>
  `flush()` 會刪除 session 中所有資料。登入狀態也會遺失，若只想刪除使用者資料，請以 `forget()` 指定特定 key。
</Warning>

## 後續步驟

<Card title="Validation" icon="check-circle" href="/zh-TW/validation">
  確認如何以 validation 驗證表單的輸入資料。
</Card>


## Related topics

- [Session 恢復](/zh-TW/packages/laravel-copilot-sdk/resume.md)
- [Session Hook](/zh-TW/packages/laravel-copilot-sdk/hooks.md)
- [Cloud Sessions](/zh-TW/packages/laravel-copilot-sdk/cloud-sessions.md)
- [Remote Sessions](/zh-TW/packages/laravel-copilot-sdk/remote-sessions.md)
- [SessionConfig](/zh-TW/packages/laravel-copilot-sdk/session-config.md)
