# ch05 — パス操作とディレクトリ走査

Source: https://php-school.pages.dev/topics/file-io/ch05-paths/
Site: AIと学ぶPHPの学校 (https://php-school.pages.dev)
Lesson: ファイル入出力
Level: L4

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

## 本文

# ch05 — パス操作とディレクトリ走査

## 概要

ファイルを扱う場面で常に付きまとうのが **パス** の問題。パスには **絶対パス** (`/Users/foo/data.txt` のように `/` から始まる) と **相対パス** (`./data.txt` のように現在地基準) があり、相対パスは **「どこから動かしているか」で結果が変わる** ため事故りやすい。「スクリプトから見て隣のファイル」を確実に指すために PHP には **`__DIR__`** (このファイルのディレクトリ絶対パス) と **`__FILE__`** (このファイル自身) という定数がある。

パスは文字列なので、分解する関数群がある。`dirname('/a/b/c.txt')` で `'/a/b'`、`basename(...)` で `'c.txt'`、**`pathinfo(...)`** で `dirname / basename / filename / extension` を **連想配列で一気に** 取れる。拡張子を抜き出したい `pathinfo($path, PATHINFO_EXTENSION)` は頻出イディオム。絶対パスへ正規化したいなら **`realpath()`** がシンボリックリンクや `..` を解決してくれる。

ディレクトリ内一覧は **`scandir($dir)`**。注意点は戻り値に必ず **`.` (カレント)・`..` (親) が混じる** こと。`array_diff($files, ['.', '..'])` で除く定型処理。順序は OS 依存なので揃えたいなら `sort($files)` を明示する。**`__DIR__` / `pathinfo` / `scandir` の 3 点セット** はファイル処理コードの「正しい型」。

## 学習目標

- `__DIR__` / `__FILE__` の違いを言える
- `dirname` / `basename` / `pathinfo` で パスを **分解** できる
- `scandir` でディレクトリ内のファイル一覧を取れる (`.` / `..` の除外も含む)
- `realpath` で **絶対パスに正規化** できると知っている

## 所要時間

スライド = 5 分 / ドリル = 約 20 分

## ドリル

| # | 内容 |
|---|---|
| `01-path-info/` | 与えられたパス文字列を分解して各部分 (dir / basename / filename / extension) を出力 |
| `02-list-dir/` | `tests/data/sample/` 配下のファイル名一覧 (`.` `..` を除く) をソートして出力 |

## 採点

```bash
php scripts/grade.php topics/file-io/ch05-paths/drill/01-path-info/
php scripts/grade.php topics/file-io/ch05-paths/drill/02-list-dir/
```

## 確認のコツ

- `pathinfo` は 連想配列 (`dirname` / `basename` / `filename` / `extension`) を返す
- `scandir` の戻り値には **`.` (カレント)・`..` (親)** が必ず混ざる。`array_diff($files, ['.', '..'])` で除く
- ファイル一覧を **アルファベット順** で揃えたいなら `sort($files)` を明示する (環境依存を消す)

## スライド

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

<!--
LLM_CONTEXT:
  Lesson 14 / Chapter 5
  目的: __DIR__ / __FILE__ / dirname / basename / pathinfo / realpath / scandir でパスを扱う
  扱わない: アップロード (ch06)
  読み上げ時間目安: 5 分
-->

# パス操作とディレクトリ走査

Lesson 14 / Chapter 5

---

## `__DIR__` と `__FILE__`

```php
// /Users/you/proj/app/index.php というファイル内で...

echo __FILE__;   // /Users/you/proj/app/index.php   (このファイル自身)
echo __DIR__;    // /Users/you/proj/app             (このファイルが入っているディレクトリ)
```

- **`__FILE__`** : 実行中の PHP ファイルの **絶対パス**
- **`__DIR__`** : それを `dirname` した結果 (= 親ディレクトリ)

→ どこから呼ばれても **「自分自身からの相対パス」** を組める。file-io トピックで何度も登場。

---

## パスを分解する 3 つ + α

```php
$path = '/var/www/app/config/db.yaml';

dirname($path);         // /var/www/app/config       (ディレクトリ部分)
basename($path);        // db.yaml                   (ファイル名 + 拡張子)
basename($path, '.yaml'); // db                      (拡張子を剥がす)
pathinfo($path);
// [
//   'dirname'   => '/var/www/app/config',
//   'basename'  => 'db.yaml',
//   'extension' => 'yaml',
//   'filename'  => 'db',
// ]
```

- **`dirname`** = 「親ディレクトリ」だけ
- **`basename`** = 「最後の `/` 以降」 (= ファイル名)
- **`pathinfo`** = 全部入りの **連想配列** (拡張子・拡張子無しの名前まで)

---

## `pathinfo` を実戦で使う

```php
<?php
$info = pathinfo('/var/www/logs/access.2026-05-19.log');

echo $info['dirname'];   // /var/www/logs
echo $info['basename'];  // access.2026-05-19.log
echo $info['filename'];  // access.2026-05-19
echo $info['extension']; // log
```

| キー | 中身 |
|---|---|
| `dirname` | ディレクトリ部分 |
| `basename` | ファイル名 (拡張子付き) |
| `filename` | ファイル名 (拡張子なし) |
| `extension` | 拡張子 (`.` なし) |

→ アップロードファイルの拡張子検証 (ch06) でも `pathinfo($name, PATHINFO_EXTENSION)` がよく出る。

---

## `realpath` で絶対パスに正規化

```php
echo realpath('./tests/../tests/data/hello.txt');
// /Users/you/proj/.../tests/data/hello.txt
//   ↑ ".." や "./" を解決し、シンボリックリンクも展開して 絶対パスを返す

echo realpath('does_not_exist.txt');   // false
```

- 相対パス / `./` / `../` / シンボリックリンクを **全部解決**
- 実在しないパスには **`false`** を返す (= **存在チェック代わり** にも使える)
- セキュリティ上重要: ユーザー入力のパスは `realpath` で正規化してから 許可ディレクトリ配下か検証する

---

## `scandir` でディレクトリ一覧

```php
<?php
$dir = __DIR__ . '/tests/data/sample';
$files = scandir($dir);

print_r($files);
// [
//   '.',           ← カレントディレクトリを示す特殊エントリ
//   '..',          ← 親ディレクトリを示す特殊エントリ
//   'a.txt',
//   'b.txt',
//   'c.txt',
// ]
```

- `scandir` は **`.` / `..` を含む** ことに必ず注意
- 普通は除いて使う: `array_diff($files, ['.', '..'])`
- 並び順は **デフォルトで昇順** (`SCANDIR_SORT_ASCENDING`)。明示的にソートしたければ `sort()` を後段で

---

## `scandir` + 除外 + ソートのテンプレ

```php
<?php
$dir = __DIR__ . '/tests/data/sample';

$files = array_values(array_diff(scandir($dir), ['.', '..']));
sort($files);   // 明示的にアルファベット順

foreach ($files as $f) {
    echo $f . "\n";
}
```

- **`array_diff`** で `.` `..` を除く (`array_values` でキーを 0 から振り直す)
- **`sort`** で明示ソート (環境差・PHP バージョン差を消す)
- 必要なら **`is_file`** / **`is_dir`** でファイルとサブディレクトリを切り分け

→ ログ集計バッチ・テンプレートエンジンの 一覧取得などで頻出パターン。

---

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

✅ `__DIR__` / `__FILE__` の違いを言える
✅ `dirname` / `basename` / `pathinfo` で パスを分解できる
✅ `pathinfo` の 4 つのキー (`dirname` / `basename` / `filename` / `extension`) を言える
✅ `realpath` の役割 (絶対パス化 + 存在チェック) を言える
✅ `scandir` の戻り値から **`.` / `..` を除いて** ソートする定型が書ける

→ ドリルへ
