PHP
目次
主要項目のみを表示しています。詳細な小見出しは本文内で確認できます。
- 概要
- 1. PHPとは何か
- 2. 実行環境とSAPI
- 3. 型と変数
- 4. 演算子と比較
- 5. 制御フローとmatch
- 6. 関数と引数
- 7. 配列
- 8. 文字列
- 9. クラスとオブジェクト
- 10. 継承・interface・trait
- 11. enum(PHP 8.1+)
- 12. readonlyとproperties(PHP 8.1+)
- 13. Attribute(PHP 8+)
- 14. 名前空間とautoload
- 15. Composerとパッケージ
- 16. 例外処理
- 17. ジェネレータとIterator
- 18. 非同期とFiber(PHP 8.1+)
- 19. JITと性能
- 20. テスト(PHPUnit)
- 21. Laravel / Symfonyの世界
- 22. PHP 8.0〜8.3の進化
- 23. よくある落とし穴FAQ
- 24. 学習ロードマップ(30日)
- 25. 用語集
- 発展: PHP 8とOOP
- 26. PHP 8系の主要機能
- 27. クラスとOOP完全版
- 28. 配列の詳細
- 29. 文字列詳細
- 30. 例外と例外チェイン
- 実践: Webアプリケーション開発
- 応用: 設計と運用
- 補遺: 実例集
- Composer とパッケージ管理
- PHP 8.0+ の型安全性とパフォーマンス
- PHP フレームワークのアーキテクチャ
- まとめ
- 参考文献
概要
まず、この章の中心構造を図で確認します。細部に入る前に、どの概念がどこへつながるかをつかむための地図です。
コード例は、そのまま写すためだけのものではありません。直前の本文で「何を確かめる例か」を押さえ、直後の説明で「どの性質が見えるか」を確認してください。実務では、ここに入力の境界、失敗時の挙動、依存する実行環境を足して読むと判断しやすくなります。
PHPは、Webアプリケーション開発の現場で進化してきた、実用性と後方互換性を重視する言語です。
このページでは、実行環境、型、配列、クラス、Composer、例外、PHP 8系の機能を、Web開発とのつながりを意識して整理します。
1. PHPとは何か
PHPは、「Webに特化したサーバサイドスクリプト言語」。1994年から存在し、世界のWebサイトの7割以上で使われ続けている言語です。WordPress、Wikipedia、Facebook(初期)、Slack(API)、Etsy ─ あらゆるWebサービスの裏で動いています。
近年は PHP 8で言語が劇的にモダン化し、型システムが強化、JITが導入され、JavaやC# に近いプロフェッショナルな言語に進化しています。
主な用途:
- Webバックエンド: Laravel、Symfony、CakePHP、CodeIgniter
- CMS: WordPress(Web全体の40%+)、Drupal、Joomla
- EC: Magento、PrestaShop
- コミュニケーション: SlackのAPI、Discord(一部)
- 企業サイト・ブログ: 中小企業の標準
1-1. PHPの歴史
Personal Home Page(1994)
1994年、Rasmus Lerdorfが個人のホームページ管理用に PHP/FI(Personal Home Page / Forms Interpreter) を開発。当初は単なるPerlベースのスクリプト集でしたが、急速に普及。
1994 PHP/FI 1.0
1997 PHP 3.0(Zendエンジンなし、新文法)
2000 PHP 4.0(Zend Engine 1.0)
2004 PHP 5.0(OOP、Zend Engine 2.0)
2009 PHP 5.3(名前空間、クロージャ)
2014 PHP 5.5〜5.6(generator、finally)
2015 PHP 7.0(性能2倍、スカラ型宣言、null合体)
2020 PHP 8.0(JIT、union types、attributes、named args)
2021 PHP 8.1(readonly、enum、Fiber、never type)
2022 PHP 8.2(readonly class、DNF types)
2023 PHP 8.3(typed class constants、json_validate)
PHP 7の革命(2015)
PHP 7は 「過去最大級の性能改善」をもたらしました。Zend Engineの刷新により、PHP 5.6比2倍速 に。これにより当時懸念されていた 「PHPは遅くて時代遅れ」のイメージが大きく改善されました。
PHP 8のモダン化(2020〜)
PHP 8では JIT・union types・named arguments・attributes・match式・readonly・enumが次々と導入され、型安全性・性能・表現力が飛躍的に向上しました。
「WordPressのための言語」というイメージから、Laravel/Symfonyで書く現代的なWebフレームワークとしての地位を確立しています。
1-2. PHPの特殊性
Webに特化した実行モデル
PHPは 「リクエストごとに実行が完結し、メモリがリセットされる」モデル。これは他の言語と大きく違う点です。
リクエスト到着
↓
PHPプロセスが起動 / 既存ワーカーが受け取る
↓
スクリプト実行(数ms 〜 数百ms)
↓
レスポンス返却 → メモリ解放
これにより:
- メモリリークが事実上ない(リクエスト終了で全て解放)
- 状態を持ちにくい(セッションやキャッシュは外部に置く)
- デプロイが楽(FTPでファイルアップロードでも動く)
- マルチスレッド設計を考えなくていい
「サーバプロセスの状態を意識しなくていい」のがPHPの独特の体験。
HTMLとの混在
<!DOCTYPE html>
<html>
<body>
<h1>Hello, <?= htmlspecialchars($name) ?>!</h1>
</body>
</html>
<?php ... ?> でHTMLにPHPコードを埋め込める。これがWebページ生成と相性が良く、初期のPHPの急速な普及を支えました。
ただし現代では MVCフレームワーク で「ロジックとビューを分離」するのが主流。
1-3. このセクションのまとめ
- 1994年Rasmus LerdorfによるWebスクリプト
- PHP 7 (2015) で性能2倍
- PHP 8 (2020) で型・JIT・モダン構文
- リクエスト単位で完結する独特の実行モデル
- WordPress (Webの40%+) とLaravelが二大エコシステム
2. 実行環境とSAPI
PHPは SAPI(Server Application Programming Interface) によって複数の実行モードを持ちます。
2-1. SAPIの種類
mod_php Apacheの中でPHPインタプリタが動く(伝統的)
PHP-FPM FastCGIプロセスマネージャ(現代の標準)
CLIコマンドラインでphp script.php
PHP CGI古いCGI実装
embed他言語に組み込み
swoole/RoadRunner常駐プロセスとして動く(モダン)
PHP-FPM(現代の標準)
nginx → FastCGI経由 → PHP-FPMプールワーカー → スクリプト実行
ワーカープロセスをプール化して、リクエストごとに割り当てる。Apache mod_phpより柔軟・高速で、現代の本番標準。
CLI
php script.php
php -a # 対話モード(REPL)
php -S localhost:8080 # 簡易Webサーバ
CLIモードではWebサーバ不要。バッチ処理や開発用に。
Swoole / RoadRunner(常駐型)
「リクエストごとに起動・終了」のオーバーヘッドを避け、プロセスを常駐させてWebサーバ風に動かす。Goのgoroutine風の並行処理も可能で、マイクロサービス向け。
2-2. opcodeキャッシュ(OPcache)
PHPはスクリプトファイルを オペコード(仮想マシン命令列)にコンパイルして実行します。OPcache がコンパイル結果をキャッシュし、毎リクエストの再コンパイルを回避。
; php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=10000
OPcacheは PHP 5.5+ で標準同梱。本番では必ず有効化。これだけでPHPの速度は数倍違います。
2-3. JIT(PHP 8+)
PHP 8で JITコンパイラが導入されました。OPcacheの後段で、ホットなオペコードを機械語に変換します。
opcache.jit_buffer_size=100M
opcache.jit=tracing
JITの効果
Webリクエスト処理: ほぼ変わらない(IOバウンドのため)
CPUバウンドな計算: 数倍〜数十倍速くなることも
「Webでは大きく速くならないが、画像処理・暗号・機械学習などで効く」のが現実。
2-4. このセクションのまとめ
- SAPIが複数: mod_php / PHP-FPM / CLI / Swoole
- 現代の標準はnginx + PHP-FPM
- OPcacheでオペコードをキャッシュ(必須)
- JITはCPUバウンドで効く(Webでは限定的)
3. 型と変数
PHPは 動的型付け だが、PHP 7+ で型宣言が大幅に強化された言語。現代では 静的型付けに近い書き方が可能です。
3-1. プリミティブ型
int整数(プラットフォーム依存、通常64bit)
float倍精度浮動小数点
bool true / false
string文字列(バイト列、エンコーディングは自分で管理)
array連想配列・リスト両対応
objectオブジェクト
callable呼び出し可能(関数・メソッド・クロージャ)
iterable配列またはTraversable
null値の不在
mixed何でも(PHP 8+)
never決して返らない(PHP 8.1+)
void返り値なし
整数と浮動小数点
$x = 42; // int
$y = 3.14; // float
$z = 1_000_000; // 桁区切り(PHP 7.4+)
3-2. 変数の宣言
$x = 10; // $ で始まる
$name = "Alice"; // 動的型
$x = "now string"; // 型が変わってもOK
unset($x); // 削除
isset($x); // 存在確認
empty($x); // 空判定
3-3. 型宣言(PHP 7+)
function add(int $a, int $b): int {
return $a + $b;
}
function greet(?string $name = null): string {
return "Hello, " . ($name ?? "world");
}
?Type で nullable、Type|null でも同じ。
Union types(PHP 8+)
function process(int|string $value): bool { ... }
Intersection types(PHP 8.1+)
function logger(Countable&Iterator $iter) { ... }
DNF types(PHP 8.2+)
function complex((Countable&Iterator)|null $arg) { ... }
3-4. 型キャスト
$x = (int)"42"; // 42
$x = (string)42; // "42"
$x = (bool)1; // true
$x = (array)$obj; // 配列に変換
$x = intval("42abc"); // 42(先頭の整数だけ取る)
$x = floatval("3.14");
3-5. このセクションのまとめ
- 動的型付け、変数は $ プレフィックス
- PHP 7+ で型宣言(int / string / array / Type|null)
- PHP 8+ union types / mixed / never
- PHP 8.1+ intersection / readonly
- 厳格モードdeclare(strict_types=1) 推奨
4. 演算子と比較
PHPの比較演算子は 歴史的に複雑。== と === の違いを理解するのが鍵です。
4-1. 算術と論理
1 + 2 // 3
10 / 3 // 3.333...(自動的にfloatに)
intdiv(10, 3) // 3(整数除算)
10 % 3 // 1
2 ** 10 // 1024(べき乗)
true && false // false
true || false // true
!true // false
true xor true // false
4-2. == と === の違い
PHP最大の罠ポイント。
"0" == false // true!(型変換)
"abc" == 0 // PHP 8以降はfalse(PHP 7まではtrue!)
0 == null // true
"" == null // true
"42" == 42 // true(型変換)
"42" === 42 // false(型が違う)
"42" === "42" // true(厳密一致)
「== を使ってはいけない、=== を使う」のが現代の鉄則。
4-3. 文字列結合
$s = "hello" . " " . "world"; // . で結合(+ ではない)
$name = "Alice";
$msg = "Hello, $name!"; // 変数展開("" で)
$msg = 'Hello, $name!'; // ' では展開されない
$msg = "Total: {$user->name}"; // {} で複雑な式
4-4. null合体演算子
$name = $input ?? "anonymous"; // nullなら "anonymous"
$config["timeout"] ??= 30; // nullなら代入(PHP 7.4+)
$user?->profile?->name // null safe(PHP 8+)
4-5. スプレッド・分割
$arr = [1, 2, 3];
$result = [...$arr, 4, 5]; // [1, 2, 3, 4, 5]
[$a, $b, $c] = $arr; // 分割代入
["a" => $x, "b" => $y] = ["a" => 1, "b" => 2]; // キー指定
4-6. このセクションのまとめ
- 文字列結合は . (+ではない!)
- == は型変換あり、=== を使う
- ?? null合体、??= null代入、?-> null safe
- スプレッド ... と分割代入
5. 制御フローとmatch
5-1. if / elseif / else
if ($x > 0) {
echo "positive";
} elseif ($x < 0) {
echo "negative";
} else {
echo "zero";
}
elseif か else if、両方OK。elseif が慣習。
5-2. switchとmatch
switch(伝統的)
switch ($status) {
case 200:
$msg = "OK";
break;
case 404:
$msg = "Not Found";
break;
default:
$msg = "Unknown";
}
break 忘れでfall-throughする伝統的な罠あり。
match式(PHP 8+)
$msg = match ($status) {
200, 201 => "OK",
400, 401 => "Client Error",
500, 502, 503 => "Server Error",
default => "Unknown",
};
match は === 比較(厳密)、fall-throughなし、式として値を返す、default必須または網羅必須。switchより安全で推奨。
5-3. for / foreach / while
for ($i = 0; $i < 10; $i++) { ... }
foreach ($items as $item) { ... }
foreach ($items as $key => $value) { ... }
while ($cond) { ... }
do { ... } while ($cond);
foreachの参照
foreach ($items as &$item) {
$item = $item * 2; // 配列の中身を変更
}
unset($item); // 参照を切る(重要!)
& で参照ループ。ループ後に unset($item) を忘れると次のコードでバグるので注意。
5-4. このセクションのまとめ
- if / elseif / else
- switch(古い)vs match(PHP 8+、推奨)
- for / foreach / while / do-while
- foreachの参照ループはunsetで締める
6. 関数と引数
6-1. 関数定義
function add(int $a, int $b): int {
return $a + $b;
}
// アロー関数(PHP 7.4+)
$double = fn($x) => $x * 2;
// クロージャ
$counter = function() use (&$count) {
return ++$count;
};
fn は アロー関数で、外側の変数を 自動的にキャプチャ(値)。複数文は書けない。
function は明示的に use でキャプチャを書く(参照は &)。
6-2. 引数の種類
function f(
int $required, // 必須
int $optional = 10, // デフォルト
int ...$rest // 可変長
): int { ... }
// PHP 8+ 名前付き引数
greet(name: "Alice", age: 30);
// 引数のデフォルト値とパラメータの関係
function process(
int $count,
bool $verbose = false, // デフォルトは後ろに
) { ... }
6-3. 参照渡し
function increment(int &$x): void {
$x++;
}
$n = 5;
increment($n);
echo $n; // 6
& で参照渡し。ただしPHPでは オブジェクトはデフォルトで参照的な挙動なので、明示的な参照渡しはあまり使われない。
6-4. 戻り値の型
function findUser(int $id): ?User { ... } // null可
function logMessage(string $msg): void { ... } // 戻り値なし
function fail(string $msg): never { throw new Exception($msg); } // 決して戻らない
never(PHP 8.1+)は 「決して正常リターンしない」。例外を投げる、exit() するなど。
6-5. このセクションのまとめ
- function name(...) で定義、戻り値の型を : で
- アロー関数fn() => 式(PHP 7.4+)
- クロージャはfunction () use ($var) { ... }
- 名前付き引数(PHP 8+)
- 戻り値型: void / never / nullable / union
7. 配列
PHPの array は 「整数キー配列と連想配列が同じ型」という独特の設計。
7-1. 配列の作成
$list = [1, 2, 3]; // インデックス配列
$map = ["a" => 1, "b" => 2]; // 連想配列
$mixed = [0 => "a", "b" => 2, 1 => "c"]; // 混在も可
$list[] = 4; // 末尾追加
$map["c"] = 3; // 追加・更新
unset($list[0]); // 削除(インデックスは詰められない!)
7-2. 主要な配列関数
count($arr);
array_keys($arr);
array_values($arr);
array_map(fn($x) => $x * 2, $arr);
array_filter($arr, fn($x) => $x > 0);
array_reduce($arr, fn($acc, $x) => $acc + $x, 0);
array_merge($a, $b);
array_combine($keys, $values);
in_array("foo", $arr);
array_key_exists("key", $arr);
sort($arr); // 値でソート(参照変更)
ksort($arr); // キーでソート
usort($arr, $cmp); // カスタム比較
7-3. 落とし穴
unset後のキー
$arr = [1, 2, 3];
unset($arr[1]);
print_r($arr); // [0 => 1, 2 => 3](キー1が抜ける、詰めない)
$arr = array_values($arr); // [0 => 1, 1 => 3](再採番)
連想配列とインデックス配列の混同
$arr = ["a" => 1, "b" => 2];
foreach ($arr as $value) {
echo $value; // キーは取れない
}
foreach ($arr as $key => $value) { ... } // 推奨
7-4. このセクションのまとめ
- arrayは連想 + インデックスの兼用型
- count / array_map / array_filterが標準
- unsetするとキーが抜ける(array_valuesで再採番)
- foreachはas $key => $value形式が安全
8. 文字列
PHPの文字列は 「バイト列」。エンコーディング情報は持ちません。
8-1. 文字列の作成
$s = "Hello, $name"; // 二重引用符: 変数展開、エスケープ
$s = 'Hello, $name'; // 一重引用符: 展開しない
$s = "Hello, ".$name;
$s = "Total: {$user->name}";
// ヒアドキュメント
$html = <<<HTML
<div>$name</div>
HTML;
// nowdoc(変数展開なし)
$sql = <<<'SQL'
SELECT * FROM users WHERE id = $id
SQL;
8-2. 主要関数
strlen($s); // バイト長
mb_strlen($s); // マルチバイト文字数
strtoupper($s);
strtolower($s);
trim($s);
str_replace("a", "b", $s);
substr($s, 0, 3);
explode(",", "a,b,c"); // ["a", "b", "c"]
implode(",", ["a", "b"]); // "a,b"
sprintf("%d %s", 1, "a");
str_contains($s, "needle"); // PHP 8+
str_starts_with($s, "He"); // PHP 8+
str_ends_with($s, "lo"); // PHP 8+
マルチバイト
strlen などはバイト数を返す。日本語の文字数を数えるには mb_strlen などの mb_ 系関数*を使う。
strlen("こんにちは"); // 15(バイト)
mb_strlen("こんにちは"); // 5(文字)
mb_internal_encoding('UTF-8') を最初に設定するのが慣習。
8-3. このセクションのまとめ
- "" で展開、'' で展開なし
- ヒアドキュメント (<<<TAG) とnowdoc (<<<'TAG')
- str_contains / str_starts_with(PHP 8+)
- マルチバイト文字にはmb_* 系
- sprintf / number_formatが定番
9. クラスとオブジェクト
PHP 5+ で本格的なOOPに。PHP 8で constructor property promotion、readonly などモダン化。
9-1. クラスの基本
class Person {
public string $name;
public int $age;
public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
public function greet(): string {
return "Hi, I'm {$this->name}";
}
}
$p = new Person("Alice", 30);
$p->greet(); // -> でアクセス
9-2. Constructor property promotion(PHP 8+)
class Person {
public function __construct(
public string $name,
public int $age,
private ?string $email = null,
) {}
}
$p = new Person("Alice", 30);
$p->name;
「コンストラクタ引数とフィールド宣言を一行で」できる、Kotlin風の書き方。boilerplateが大幅減。
9-3. アクセス修飾子
public: どこからでも
protected: クラス・サブクラス内
private: クラス内のみ
9-4. static
class Counter {
private static int $count = 0;
public static function increment(): int {
return ++self::$count;
}
}
Counter::increment(); // :: でクラスメソッド呼び出し
self:: でクラス自身、static:: で 遅延静的束縛(サブクラスから呼ばれたとき正しく解決)。
9-5. 抽象クラスとfinal
abstract class Shape {
abstract public function area(): float;
}
final class Circle extends Shape { // final: 継承禁止
public function __construct(private float $radius) {}
public function area(): float {
return M_PI * $this->radius ** 2;
}
}
9-6. マジックメソッド
class MyClass {
public function __get($name) { ... } // 未定義プロパティアクセス
public function __set($name, $value) { ... }
public function __call($name, $args) { ... } // 未定義メソッド
public function __toString(): string { ... } // (string) キャスト時
public function __invoke(...$args) { ... } // $obj() で呼べる
public function __serialize(): array { ... } // PHP 7.4+
}
特殊な動作をフックする __ 始まりのメソッド群。
9-7. このセクションのまとめ
- class / new / -> でアクセス
- public / protected / private
- Constructor property promotion(PHP 8+)
- self:: / static:: / parent::
- abstract / final
- __get / __set / __call / __toStringなどマジック
10. 継承・interface・trait
10-1. 継承
class Animal {
public function speak(): string { return "..."; }
}
class Dog extends Animal {
public function speak(): string { return "Woof!"; }
}
PHPは 単一継承。
10-2. interface
interface Greetable {
public function greet(): string;
}
class Person implements Greetable {
public function __construct(public string $name) {}
public function greet(): string { return "Hi, {$this->name}"; }
}
// 複数interface実装
class A implements Greetable, Countable { ... }
10-3. trait(多重継承の代替)
PHPは単一継承ですが、trait で「複数のクラスにメソッドを混ぜ込む」ことができます。
trait Loggable {
public function log(string $msg): void {
echo "[" . static::class . "] $msg\n";
}
}
class User {
use Loggable;
}
class Product {
use Loggable;
}
(new User)->log("hello");
Rubyのincludeに相当。コードの再利用パターンとしてPHPで広く使われます。
10-4. abstract / final
abstract class Shape {
abstract public function area(): float;
public function describe(): string {
return sprintf("Area: %.2f", $this->area());
}
}
final class Circle extends Shape { // 継承禁止
public function __construct(private float $r) {}
public function area(): float { return M_PI * $this->r ** 2; }
}
10-5. このセクションのまとめ
- 単一継承 + 複数interface実装
- traitで「メソッドの集合」を混ぜ込み
- abstract(実装必須)、final(継承禁止)
- interfaceにdefault実装はない(trait使う)
11. enum(PHP 8.1+)
PHP 8.1で enumが公式導入。それまで const で代用していたものが、型安全に。
11-1. 基本
enum Status {
case Pending;
case Active;
case Banned;
}
$s = Status::Active;
match ($s) {
Status::Pending => "保留中",
Status::Active => "有効",
Status::Banned => "停止",
};
11-2. Backed enum(値付き)
enum Status: string {
case Pending = "pending";
case Active = "active";
case Banned = "banned";
}
Status::Active->value; // "active"
Status::from("active"); // Status::Active
Status::tryFrom("foo"); // null
11-3. メソッド付きenum
enum Priority: int {
case Low = 1;
case Medium = 5;
case High = 10;
public function label(): string {
return match ($this) {
self::Low => "Low",
self::Medium => "Medium",
self::High => "High",
};
}
}
Priority::High->label(); // "High"
12. readonlyとproperties(PHP 8.1+)
12-1. readonlyプロパティ
class Point {
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
}
$p = new Point(1, 2);
$p->x = 100; // Error!readonlyは再代入不可
イミュータブルなデータクラスを簡潔に書ける。
12-2. readonly class(PHP 8.2+)
readonly class Point {
public function __construct(
public float $x,
public float $y,
) {}
}
クラス全体をreadonlyに。すべてのプロパティが自動的にreadonly。
12-3. typed class constants(PHP 8.3+)
class Config {
const string VERSION = "1.0.0";
const int MAX_USERS = 100;
}
クラス定数にも型を付けられるようになりました。
13. Attribute(PHP 8+)
Java/C# のアノテーション・属性に相当。メタデータをコードに埋め込む仕組み。
#[Route('/users/{id}')]
#[Method('GET')]
public function show(int $id): User { ... }
それまでPHPDocコメント(/** @param int $id */)に書かれていた情報を、言語レベルの構文で表現できます。
自作attribute
#[Attribute]
class Cache {
public function __construct(public int $ttl) {}
}
#[Cache(ttl: 60)]
public function expensive(): array { ... }
リフレクションAPIで読み取って活用。Symfony・Laravel・Doctrineがフル活用しています。
14. 名前空間とautoload
14-1. 名前空間
// src/User/Service.php
namespace App\User;
class Service {
public function find(int $id): ?User { ... }
}
// 別ファイル
use App\User\Service;
$svc = new Service();
// または別名
use App\User\Service as UserService;
14-2. autoload(PSR-4)
namespaceと ファイルパスを対応させる慣習:
App\User\Service → src/User/Service.php
Composerが PSR-4 autoload を生成し、require を書かずに自動でクラスをロードします。
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
composer dump-autoload
15. Composerとパッケージ
PHPの事実上の標準パッケージマネージャ。RubyのBundler、JSのnpmに相当。
composer init # 初期化
composer require monolog/monolog # 依存追加
composer install # composer.lockから復元
composer update # 最新版へ更新
composer dump-autoload # autoload再生成
composer require --dev phpunit/phpunit # 開発用依存
composer.json
{
"name": "myorg/myapp",
"require": {
"php": "^8.2",
"symfony/console": "^6.0",
"monolog/monolog": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": { "App\\": "src/" }
}
}
composer.lock を コミット(アプリの場合)。Packagistが中央リポジトリ。
16. 例外処理
try {
risky();
} catch (\InvalidArgumentException $e) {
logger()->error($e->getMessage());
} catch (\RuntimeException $e) {
// ...
} catch (\Throwable $e) {
// すべての例外
} finally {
cleanup();
}
throw new \RuntimeException("oops", previous: $cause);
\Throwable がすべての例外の基底(Exception と Error の両方を含む)。
multi-catch(PHP 8+)
} catch (TypeError | ValueError $e) {
...
}
例外チェイン
catch (\PDOException $e) {
throw new DatabaseException("DB failed", previous: $e);
}
getPrevious() で元の例外をたどれる。
17. ジェネレータとIterator
function naturals(): Generator {
$n = 1;
while (true) {
yield $n;
$n++;
}
}
foreach (naturals() as $n) {
if ($n > 10) break;
echo $n;
}
yield で 遅延評価のシーケンスを作る。Pythonのgeneratorと同じ。
// キー付き
function mapping(): Generator {
yield "a" => 1;
yield "b" => 2;
}
// 受け取る
function consumer(): Generator {
while (true) {
$value = yield;
echo $value;
}
}
18. 非同期とFiber(PHP 8.1+)
PHPは伝統的に同期実行ですが、PHP 8.1で Fiber(協調的並行)が追加されました。
$fiber = new Fiber(function () {
Fiber::suspend();
echo "resumed";
});
$fiber->start();
$fiber->resume(); // "resumed"
Fiber自体は低レベル機構で、ReactPHP / Amphp のようなasyncランタイムが上に構築されます。Laravel Octane、Swoole、RoadRunnerで活用。
Fiberは「PHPを自動的に速くする機能」ではない。I/O待ちをうまく切り替えるための低レベルな土台であり、イベントループ、ソケット、タイマー、キャンセル、例外処理をどう扱うかはランタイム側が決める。通常のPHP-FPM中心のWebアプリでは、まずDBクエリ、キャッシュ、N+1、OPcacheを整えるほうが効果が大きい。
19. JITと性能
第2章2-3で触れたJIT。Webでは効果が薄いですが、CPUバウンド処理では有効。
性能のベストプラクティス
1. OPcacheを必ず有効化
2. realpath_cacheを大きめに
3. PHP-FPMのワーカー数をCPUコア数 × 2-4
4. JITを有効化(CPUバウンド処理がある場合)
5. データベースクエリを最小化(N+1問題)
JITはPHP 8の象徴的な機能だが、Webアプリの多くではボトルネックがPHPの計算ではなく、DB、ネットワーク、テンプレート、シリアライズにある。したがって、JITを有効にする前にOPcache、autoload最適化、DBインデックス、HTTPキャッシュ、キュー化を確認する。
数値計算、画像処理、パーサ、長時間動くCLIではJITが効く可能性がある。効果を見るときは、ベンチマークだけでなく本番に近いデータ量で計測する。JIT設定を変えたら、メモリ使用量とウォームアップ時間も合わせて確認する。
20. テスト(PHPUnit)
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase {
public function testAdd(): void {
$this->assertEquals(3, 1 + 2);
}
/**
* @dataProvider additionProvider
*/
public function testAddProvider(int $a, int $b, int $expected): void {
$this->assertEquals($expected, $a + $b);
}
public static function additionProvider(): array {
return [
[1, 1, 2],
[2, 3, 5],
];
}
}
vendor/bin/phpunit
PHPUnitが事実上の標準。Pest というDSLベースの選択肢も人気。
21. Laravel / Symfonyの世界
PHPの現代エコシステムの中心。
Laravel
DHHのRailsに強くインスパイアされた、「PHP開発者の大半が使う」フルスタックフレームワーク。
Route::get('/users/{id}', function ($id) {
return User::findOrFail($id);
});
class User extends Model {
use HasFactory;
protected $fillable = ['name', 'email'];
}
Eloquent ORM、Bladeテンプレート、Artisan CLI、Queue、Mailなどすべて統合。
Symfony
Laravelが「意見の強い」「バッテリ込み」フレームワークなのに対し、Symfonyは 「コンポーネントの寄せ集め」「柔軟」。Drupal、phpBB、その他多くのPHPプロジェクトの土台。
実は Laravel自身もSymfonyコンポーネントを多用しています。
22. PHP 8.0〜8.3の進化
8.0 (2020): JIT、union types、named args、attributes、match、constructor改善
8.1 (2021): enum、readonly、never type、Fiber、first-class callable syntax
8.2 (2022): readonly class、DNF types、true/false/null型、定数のdeprecation
8.3 (2023): typed class constants、json_validate、override属性、Randomizer
23. よくある落とし穴FAQ
Q1. == と === どっち?
=== を常に使う。== は型変換で予期しない結果("0" == false がtrueなど)。
Q2. $thisとselfの違い
$this->method() はインスタンスメソッド、self::method() はクラスメソッドやstatic呼び出し。
Q3. requireとinclude
requireは失敗でfatal、includeはwarning。require_once でファイルを1回だけ読む(autoloadで不要に)。
Q4. 配列のキーが消える
unset($arr[1]) してもキーは詰められない。array_values() で再採番。
Q5. foreach後の参照変数
foreach ($arr as &$v) { ... }
// 必ずunset($v); で参照を切る
Q6. varとletは?
PHPにはJSのvar/letはない。$ で始まる変数だけ。スコープは関数単位。
Q7. オブジェクトのコピー
$b = $a; は参照(厳密には「同じオブジェクトを指す変数の代入」)。複製は clone $a。
Q8. なぜstringで + 連結できない?
+ は数値専用。文字列は . 演算子。理由は歴史的(数値文字列との曖昧性回避)。
Q9. array vs Collection
PHPの array は強力だが、Laravelの Collection(fluentな操作)が現代的。
Q10. PHPは遅い?
PHP 7以降は十分速い。Web用途では多くの場合IOバウンドで言語の差は出にくい。OPcache + JIT + 適切なPHP-FPM設定で実用十分。
24. 学習ロードマップ(30日)
Week 1: 基礎
- 環境(Docker/Laravel Sail)、php-fpm、composer
- 型・変数・配列・文字列
- 制御フロー、match
- 関数・アロー関数
Week 2: OOP
- class / interface / trait / abstract / final
- Constructor property promotion
- enum・readonly
- attribute
Week 3: 実装
Week 4: フレームワーク
25. 用語集
- SAPI: Server API、PHPの実行モード
- PHP-FPM: FastCGI Process Manager
- OPcache: オペコードキャッシュ
- PSR: PHP Standards Recommendations
- PSR-4: 名前空間とファイルパスの対応規約
- Composer: パッケージマネージャ
- Packagist: 公式パッケージリポジトリ
- Eloquent: LaravelのORM
- Blade: Laravelのテンプレートエンジン
- Artisan: LaravelのCLI
- Trait: メソッドの集合を混ぜ込む仕組み
- Fiber: 協調的並行(PHP 8.1+)
- PHPUnit: 標準テストフレームワーク
発展: PHP 8とOOP
ここからはPHPの各機能と現代エコシステムを 実例とともに深掘りします。Laravel、Symfony、テスト、デプロイまで実用に直結する内容を網羅。
26. PHP 8系の主要機能
PHP 8.0〜8.3で追加された機能を すべて整理します。
26-1. PHP 8.0の革命
named arguments
function createUser(string $name, int $age = 0, bool $admin = false): User {
// ...
}
createUser(name: "Alice", admin: true); // age省略、admin指定
引数の順序を気にせず、名前で指定。可読性向上。
union types
function format(int|string $value): string {
return (string)$value;
}
複数の型を許容。
attributes
#[Route('/users/{id}', methods: ['GET'])]
public function show(int $id) { }
それまでPHPDocに書いていたメタデータが言語レベルに。
constructor property promotion
class Person {
public function __construct(
public readonly string $name,
public readonly int $age,
private ?string $email = null,
) {}
}
コンストラクタ引数とフィールド宣言を一度に。
match式
$result = match($status) {
200, 201 => "OK",
404 => "Not Found",
500, 502, 503 => "Server Error",
default => "Unknown",
};
switchより厳密 (=== 比較)、fall-throughなし、式として値を返す。
nullsafe
$city = $user?->profile?->address?->city;
途中でnullなら全体がnull。Kotlinの ?.、JSの ?. と同じ。
named exceptions catch
try { ... } catch (TypeError) { ... } // $e不要
JIT
opcache.jit_buffer_size=100M
opcache.jit=tracing
実行時のCPUバウンドな処理が高速化。
26-2. PHP 8.1の革命
readonlyプロパティ
class Point {
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
}
$p = new Point(1, 2);
$p->x = 100; // Error
enum
enum Status: string {
case Pending = "pending";
case Active = "active";
case Banned = "banned";
public function label(): string {
return match ($this) {
Status::Pending => "保留中",
Status::Active => "有効",
Status::Banned => "停止",
};
}
}
never type
function fail(string $msg): never {
throw new \RuntimeException($msg);
}
「正常リターンしない」と明示。
Fiber
$fiber = new Fiber(function () {
$value = Fiber::suspend('first');
Fiber::suspend("got: $value");
});
$result1 = $fiber->start(); // "first"
$result2 = $fiber->resume("data"); // "got: data"
協調的並行性のプリミティブ。
first-class callable syntax
$strlen = strlen(...); // 関数をcallableに
$method = $obj->method(...); // メソッドをcallableに
$static = MyClass::staticMethod(...);
それまで [SomeClass::class, 'method'] のような書き方が必要だったのが、(...) で簡潔に。
intersection types
function process(Countable&Iterator $iter): int { ... }
複数の型をすべて満たす。
new in initializer
class Service {
public function __construct(
private Logger $logger = new ConsoleLogger(),
) {}
}
それまでデフォルト引数で new できなかった。
26-3. PHP 8.2
readonly class
readonly class Point {
public function __construct(
public float $x,
public float $y,
) {}
}
クラス全体がreadonly。すべてのpropertiesが自動readonly。
DNF types
function complex((Countable&Iterator)|null $arg) { ... }
Disjunctive Normal Form: unionとintersectionの組み合わせ。
true / false / null型
function alwaysTrue(): true { return true; }
function neverNull(): null { return null; }
リテラル値そのものが型として表現できる。
deprecated動的プロパティ
class Foo { }
$foo = new Foo();
$foo->bar = 1; // Deprecated警告(PHP 8.2+)
未定義プロパティの作成がdeprecated。
26-4. PHP 8.3
typed class constants
class Config {
const string VERSION = "1.0.0";
const int MAX_USERS = 100;
}
json_validate
if (json_validate($input)) {
$data = json_decode($input, true);
}
JSONがvalidかを デコードせずに確認できる。
#[\Override]
class Child extends Parent {
#[\Override]
public function method(): void { ... }
}
「親のメソッドをオーバーライドしているはず」をコンパイラに伝える。タイポ防止。
Randomizerの拡張
$random = new \Random\Randomizer();
$random->getInt(1, 100);
$random->shuffleArray([1, 2, 3]);
$random->pickArrayKeys($arr, 5);
質の高い乱数API。
26-5. このセクションのまとめ
8.0: named args / union / attributes / promotion / match / nullsafe / JIT
8.1: enum / readonly / never / Fiber / first-class callable / intersection / new in initializer
8.2: readonly class / DNF / true|false|null types / deprecated dynamic properties
8.3: typed class constants / json_validate / #[\Override] / Randomizer
27. クラスとOOP完全版
27-1. クラスの完全な機能
abstract class Shape {
abstract public function area(): float;
public function describe(): string {
return sprintf("Area: %.2f", $this->area());
}
}
interface Drawable {
public function draw(): void;
}
trait Loggable {
public function log(string $msg): void {
echo "[" . static::class . "] $msg\n";
}
}
final readonly class Circle extends Shape implements Drawable {
use Loggable;
public function __construct(
public float $radius,
) {
if ($radius < 0) {
throw new \InvalidArgumentException("radius must be >= 0");
}
}
public function area(): float {
return M_PI * $this->radius ** 2;
}
public function draw(): void {
$this->log("drawing circle with radius {$this->radius}");
}
}
$c = new Circle(5);
$c->describe(); // "Area: 78.54"
$c->draw(); // "[Circle] drawing circle ..."
27-2. Late Static Binding
class Base {
public static function create(): static {
return new static();
}
}
class Child extends Base { }
$x = Child::create(); // Childのインスタンス
self:: だと Base のインスタンスになるが、static:: だと 呼び出した時点のクラスを解決。
27-3. クラス定数とアクセス
class Config {
const VERSION = "1.0";
public const string NAME = "myapp"; // 8.3+
private const SECRET = "...";
}
Config::VERSION;
Config::NAME;
echo Config::class; // "Config"
// 動的アクセス
$class = "Config";
echo $class::VERSION;
27-4. 抽象クラスvs interface vs trait
abstract class:
状態を持てる
単一継承
共通実装
interface:
契約のみ(PHP 8でdefaultなし)
複数実装可
状態はconstのみ
trait:
メソッドの集合
クラスに混ぜ込み(use)
状態(プロパティ)も持てる
Rubyのinclude相当
27-5. traitの高度な機能
trait Sayable {
abstract public function sound(): string;
public function say(): void {
echo $this->sound();
}
}
class Dog {
use Sayable;
public function sound(): string { return "Woof"; }
}
// 複数trait
class A {
use Trait1, Trait2 {
Trait1::method insteadof Trait2; // 競合解決
Trait2::method as method2; // 別名
}
}
traitは単純なcopy-pasteではなく、抽象メソッド要求や 競合解決もできる。
27-6. このセクションのまとめ
- abstract / final / readonlyのクラス修飾子
- enum / interface / traitの使い分け
- Late Static Binding(static::)
- traitにabstract method可能、衝突はinsteadofで解決
- typed class constants(8.3+)
28. 配列の詳細
PHPの array は他言語と挙動が異なる独特の存在。徹底解説します。
28-1. 内部実装
PHPの array は 「順序付き連想配列」。実装は ハッシュテーブル + 双方向リンクリスト。
$arr = [];
$arr["a"] = 1;
$arr[5] = 2; // 整数キー
$arr["b"] = 3;
foreach ($arr as $k => $v) { ... } // 挿入順で反復
「整数も文字列もキーにできる、挿入順を保つ」のがPHPの独特な配列。
28-2. キーの型変換
$arr["1"] = "a"; // "1" は整数1に自動変換される!
$arr[1] = "b"; // 上書き!
$arr[true] = "c"; // 整数1に変換 → 上書き!
$arr[null] = "d"; // 空文字 "" に変換
$arr[1.5] = "e"; // 整数1に変換 → 上書き
これが嵌りどころ。整数文字列のキーは整数キーになる。
28-3. 主要関数(再整理)
// 基本
count($arr);
in_array($value, $arr);
array_key_exists($key, $arr);
isset($arr[$key]); // nullは除く
empty($arr);
// 操作
array_push($arr, ...); // 末尾追加($arr[] = ... の方が速い)
array_pop($arr);
array_shift($arr); // 先頭削除(O(n))
array_unshift($arr, ...); // 先頭追加
array_splice($arr, $offset, $length, $replacement);
// 変形
array_map(fn($x) => $x * 2, $arr);
array_filter($arr, fn($x) => $x > 0);
array_reduce($arr, fn($acc, $x) => $acc + $x, 0);
array_walk($arr, function (&$v, $k) { $v = $v * 2; }); // in-place
// 集約
array_sum($arr);
array_product($arr);
array_count_values($arr);
// 集合
array_merge($a, $b);
array_replace($a, $b);
array_combine($keys, $values);
array_unique($arr);
array_intersect($a, $b);
array_diff($a, $b);
// キー・値
array_keys($arr);
array_values($arr);
array_flip($arr);
array_search($value, $arr);
// 並び替え
sort($arr); // 値で昇順、in-place
usort($arr, $cmp); // カスタム比較
ksort($arr); // キーで
asort($arr); // 値でソート、キー保持
28-4. ジェネレータベースのトラバース
function lines(string $file) {
$f = fopen($file, "r");
try {
while (($line = fgets($f)) !== false) {
yield rtrim($line);
}
} finally {
fclose($f);
}
}
foreach (lines("huge.txt") as $line) {
process($line);
} // メモリは1行分だけ
巨大データを メモリ効率良く処理。
28-5. このセクションのまとめ
- arrayは順序付き連想配列(hash + linked list)
- キー型変換: "1" → 1、true → 1、null → ""
- foreachで挿入順
- array_map / filter / reduceで関数型
- yieldでジェネレータ
29. 文字列詳細
29-1. リテラルとエスケープ
$s = "Hello\n$name"; // 補間あり、エスケープあり
$s = 'Hello\n$name'; // 補間なし、エスケープ最小
$s = <<<TEXT // ヒアドキュメント(補間あり)
Hello $name
TEXT;
$s = <<<'TEXT' // nowdoc(補間なし、SQL/JSONテンプレートに)
SELECT * FROM users WHERE id = $id
TEXT;
29-2. 操作関数(網羅)
strlen($s); // バイト長
mb_strlen($s); // 文字数
strtoupper($s);
strtolower($s);
ucfirst($s);
ucwords($s);
trim($s); // 両端空白除去
ltrim($s);
rtrim($s);
substr($s, $start, $length);
mb_substr($s, $start, $length);
str_replace("a", "b", $s);
preg_replace('/\d+/', 'N', $s);
explode(",", "a,b,c");
implode(",", ["a", "b"]);
str_split($s, 1); // 1文字ずつ配列に
str_word_count($s);
str_pad($s, 10, "0", STR_PAD_LEFT);
str_repeat("ab", 3); // "ababab"
strpos($s, "needle"); // 位置(falseなら無し)
str_contains($s, "needle"); // bool(PHP 8+)
str_starts_with($s, "He");
str_ends_with($s, "lo");
sprintf("%05d %.2f", 42, 3.14);
htmlspecialchars($s); // HTMLエスケープ
strip_tags($s); // HTMLタグ除去
nl2br($s); // \nを <br> に
base64_encode($s);
base64_decode($s);
urlencode($s);
urldecode($s);
hash("sha256", $s);
md5($s); // セキュリティ用途では使わない
29-3. 正規表現(PCRE)
preg_match('/(\d+)/', $s, $matches);
$matches[0]; // 全マッチ
$matches[1]; // 第1キャプチャ
preg_match_all('/\d+/', $s, $matches);
preg_replace('/\d+/', 'N', $s);
preg_replace_callback('/\d+/', fn($m) => $m[0] * 2, $s);
preg_split('/\s+/', $s);
PCRE互換。/u 修飾子でUTF-8サポート。
29-4. このセクションのまとめ
- "" 補間、'' なし、ヒアドキュメント、nowdoc
- strlenはバイト、mb_strlenは文字
- str_contains / starts_with / ends_with(PHP 8+)
- preg_* で正規表現
- htmlspecialcharsでエスケープ(XSS対策)
30. 例外と例外チェイン
30-1. 階層
\Throwable (interface)
├── \Exception
│ ├── \LogicException
│ │ ├── \InvalidArgumentException
│ │ ├── \DomainException
│ │ ├── \LengthException
│ │ └── \OutOfRangeException
│ ├── \RuntimeException
│ │ ├── \UnexpectedValueException
│ │ ├── \OutOfBoundsException
│ │ └── \OverflowException
│ └── ... (PDOException, JsonExceptionなど)
└── \Error
├── \TypeError
├── \ValueError (PHP 8+)
├── \ArgumentCountError
├── \DivisionByZeroError
└── ... (CompileError, ParseErrorなど)
PHP 7+ で すべてのthrow objectは \Throwable を実装。\Error は それ以前は致命的エラーだったものをcatch可能に。
30-2. catchのパターン
try {
risky();
} catch (\InvalidArgumentException $e) {
// 具体的に
} catch (\RuntimeException | \LogicException $e) {
// multi-catch
} catch (\Throwable $e) {
// すべて
} finally {
cleanup();
}
30-3. 例外チェイン
try {
parse();
} catch (\PDOException $e) {
throw new DataAccessException("query failed", previous: $e);
}
// チェインを辿る
$e = new ParentException("outer", previous: $cause);
$cause = $e->getPrevious();
30-4. カスタム例外の設計
abstract class AppException extends \RuntimeException {
public function __construct(
string $message,
public readonly array $context = [],
?\Throwable $previous = null,
) {
parent::__construct($message, previous: $previous);
}
}
final class UserNotFoundException extends AppException {
public function __construct(int $userId, ?\Throwable $previous = null) {
parent::__construct(
"user $userId not found",
["user_id" => $userId],
$previous
);
}
}
contextual dataを持たせる現代的パターン。
30-5. このセクションのまとめ
- \Throwable / \Exception / \Error
- multi-catch(A | B)
- previousで例外チェイン
- カスタム例外にcontext持たせる
31. Composer詳細
PHPのパッケージマネージャ。業界標準で、PHP開発の必須スキル。
31-1. composer.json
{
"name": "myorg/myapp",
"description": "My PHP application",
"type": "project",
"license": "MIT",
"require": {
"php": "^8.2",
"symfony/console": "^7.0",
"monolog/monolog": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit",
"analyze": "phpstan analyze"
},
"config": {
"sort-packages": true
}
}
31-2. 主要コマンド
composer init # 初期化(インタラクティブ)
composer require monolog/monolog # 依存追加
composer require --dev phpunit/phpunit
composer install # composer.lockから復元
composer update # 最新版へ更新
composer update monolog/monolog # 個別更新
composer remove monolog/monolog
composer dump-autoload # autoload再生成
composer dump-autoload -o # 最適化
composer show # 依存一覧
composer show --tree
composer outdated # 古い依存
composer audit # 脆弱性チェック
composer run-script test # スクリプト実行
composer run test # 短縮
31-3. autoload(PSR-4)
namespace App\User\Service → src/User/Service.php
namespace App\Tests\UserTest → tests/UserTest.php
ファイルパスとnamespaceを一致させる規約。composer dump-autoload で生成されたautoloadを require するだけで、自動的にクラスがロードされる。
31-4. バージョン指定
"^7.0" メジャー固定(7.x.x)、SemVer推奨
"~7.0" マイナー固定(7.0.x)
"7.0.*" マイナー固定
">=7.0" 以上
"7.0" 完全一致
"dev-main" Gitブランチ
^ がデフォルト推奨。
31-5. プライベートリポジトリ
"repositories": [
{
"type": "vcs",
"url": "git@github.com:myorg/private-package.git"
}
],
"require": {
"myorg/private-package": "^1.0"
}
31-6. このセクションのまとめ
- composer.jsonで依存・autoload
- PSR-4でnamespace ⇔ ファイルパス
- composer install / update / require
- バージョン指定は ^ 推奨
- プライベートリポジトリも対応
32. テスト戦略
32-1. PHPUnit完全版
namespace App\Tests;
use PHPUnit\Framework\TestCase;
use App\Calculator;
class CalculatorTest extends TestCase {
private Calculator $calc;
protected function setUp(): void {
$this->calc = new Calculator();
}
public function testAdd(): void {
$this->assertSame(3, $this->calc->add(1, 2));
}
/**
* @dataProvider addProvider
*/
public function testAddProvider(int $a, int $b, int $expected): void {
$this->assertSame($expected, $this->calc->add($a, $b));
}
public static function addProvider(): array {
return [
"positive" => [1, 1, 2],
"negative" => [-1, -1, -2],
"zero" => [0, 0, 0],
];
}
public function testDivisionByZero(): void {
$this->expectException(\DivisionByZeroError::class);
$this->calc->divide(1, 0);
}
}
vendor/bin/phpunit
vendor/bin/phpunit --filter testAdd
vendor/bin/phpunit --testdox
vendor/bin/phpunit --coverage-html coverage/
32-2. Pest(DSLベース)
test('addition', function () {
expect(1 + 2)->toBe(3);
});
it('handles negatives', function () {
expect(-1 + -1)->toBe(-2);
});
dataset('numbers', [
[1, 1, 2],
[-1, -1, -2],
]);
it('adds two numbers', function ($a, $b, $expected) {
expect($a + $b)->toBe($expected);
})->with('numbers');
JSのJestやRubyのRSpecに近い書き味。新規プロジェクトで人気。
32-3. Mockery
$mock = Mockery::mock(UserRepository::class);
$mock->shouldReceive('find')
->with(1)
->andReturn(new User(name: "Alice"));
$service = new UserService($mock);
$service->getName(1); // "Alice"
PHPのモックライブラリ。
32-4. Laravelのテスト
class UserApiTest extends TestCase {
use RefreshDatabase;
public function test_can_create_user(): void {
$response = $this->postJson('/api/users', [
'name' => 'Alice',
'email' => 'alice@example.com',
]);
$response->assertCreated()
->assertJsonStructure(['id', 'name', 'email']);
$this->assertDatabaseHas('users', [
'email' => 'alice@example.com',
]);
}
}
RefreshDatabase でテスト毎にDBリセット。postJson でAPIテスト。Laravelに統合された強力なツール群。
32-5. このセクションのまとめ
- PHPUnitが標準
- PestがDSLの代替
- Mockeryでモック
- Laravelは統合テスト機能を内蔵
- カバレッジ: --coverage-html
33. Laravel詳細
PHP開発の 事実上の標準フレームワーク。コードの大半を書く時間がここに集中する人も多い。
33-1. ルーティング
// routes/web.php
Route::get('/', fn() => view('welcome'));
Route::get('/users/{id}', [UserController::class, 'show']);
Route::resource('posts', PostController::class); // RESTful 7ルート
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', DashboardController::class);
});
Route::prefix('api/v1')->group(function () {
Route::get('/users', [UserController::class, 'index']);
});
33-2. Eloquent ORM
class User extends Model {
protected $fillable = ['name', 'email'];
public function posts(): HasMany {
return $this->hasMany(Post::class);
}
}
class Post extends Model {
public function user(): BelongsTo {
return $this->belongsTo(User::class);
}
}
// 使用
$user = User::find(1);
$user->posts; // 自動的にSELECT * FROM posts
$user->posts()->where('published', true)->get();
User::where('age', '>=', 18)
->orderBy('name')
->with('posts') // eager load
->get();
// Create
User::create(['name' => 'Alice', 'email' => 'a@b.com']);
// Update
$user->update(['email' => 'new@b.com']);
// Delete
$user->delete();
User::destroy([1, 2, 3]);
ActiveRecord風ORM。Ruby on RailsのActive Recordに強く影響を受けている。
33-3. マイグレーション
// database/migrations/2024_01_01_000000_create_users_table.php
class CreateUsersTable extends Migration {
public function up(): void {
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->timestamps();
});
}
public function down(): void {
Schema::dropIfExists('users');
}
}
php artisan make:migration create_users_table
php artisan migrate
php artisan migrate:rollback
php artisan migrate:fresh --seed
33-4. Validation
class UserController extends Controller {
public function store(Request $request) {
$validated = $request->validate([
'name' => 'required|max:255',
'email' => 'required|email|unique:users',
'age' => 'integer|min:0|max:150',
]);
$user = User::create($validated);
return response()->json($user, 201);
}
}
または FormRequest クラスで分離:
class StoreUserRequest extends FormRequest {
public function rules(): array {
return [
'name' => ['required', 'max:255'],
'email' => ['required', 'email', Rule::unique('users')],
];
}
}
// Controller
public function store(StoreUserRequest $request) {
$user = User::create($request->validated());
}
33-5. Bladeテンプレート
@extends('layouts.app')
@section('content')
<h1>{{ $title }}</h1>
@if ($users->count() > 0)
<ul>
@foreach ($users as $user)
<li>{{ $user->name }}</li>
@endforeach
</ul>
@else
<p>No users.</p>
@endif
{{-- コンポーネント --}}
<x-button :type="'primary'">Save</x-button>
@endsection
{{ $var }} は自動HTMLエスケープ。XSS対策が組み込まれている。
33-6. Queue
// Jobクラス
class SendWelcomeEmail implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable;
public function __construct(public User $user) {}
public function handle(Mailer $mailer): void {
$mailer->send($this->user, new WelcomeEmail());
}
}
// 投入
SendWelcomeEmail::dispatch($user);
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(10));
SendWelcomeEmail::dispatch($user)->onQueue('emails');
php artisan queue:work
非同期ジョブをRedis / database / SQSで。
33-7. Event / Listener
event(new UserRegistered($user));
class UserRegistered {
public function __construct(public User $user) {}
}
class SendWelcomeEmail {
public function handle(UserRegistered $event): void { ... }
}
// EventServiceProvider
protected $listen = [
UserRegistered::class => [SendWelcomeEmail::class],
];
ドメインイベントベースの設計。
33-8. このセクションのまとめ
- RouteでURL → Controllerのマッピング
- Eloquent ORMでテーブル → モデル
- Migrationでスキーマバージョン
- Validationで入力検証
- Bladeでビュー(XSS自動エスケープ)
- Queueで非同期ジョブ
- Event/Listenerでドメインイベント
34. Symfony詳細
SymfonyはLaravelと双璧をなすフレームワーク。コンポーネントベースで、必要なものだけ使える。
34-1. コンポーネント単独利用
Symfonyの各機能は 独立したコンポーネントとして配布。Laravelも内部で多数のSymfonyコンポーネントを使用。
composer require symfony/console
composer require symfony/http-foundation
composer require symfony/routing
主要コンポーネント:
HttpFoundation: HTTP request/responseの抽象化
HttpKernel: リクエスト処理パイプライン
Routing: URLマッチング
DependencyInjection: DIコンテナ
Console: CLIフレームワーク
Form: フォーム処理
Validator: 入力検証
Twig: テンプレート(LaravelのBladeに相当)
Doctrine: ORM(Eloquentの代替)
34-2. Doctrine(ORM)
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private int $id;
#[ORM\Column(length: 255)]
private string $name;
#[ORM\Column(unique: true)]
private string $email;
}
// 使用
$user = $em->find(User::class, 1);
$users = $em->getRepository(User::class)
->findBy(['active' => true], ['name' => 'ASC']);
$em->persist($user);
$em->flush();
DataMapperパターン(EloquentのActiveRecordと対比)。エンティティとデータベースが疎結合。
34-3. SymfonyとLaravelの比較
Symfony:
+ 柔軟、組み合わせ自由
+ DDD親和性高い
+ エンタープライズ向け
- 初期学習が重い
- 設定が冗長
Laravel:
+ 開発が速い、規約優先
+ ドキュメントが秀逸
+ コミュニティが大きい
- 「Laravelの流儀」から外れると辛い
- 大規模では設計の自律が必要
実は LaravelはSymfonyの上に構築されているので、両方を学ぶと一気に深い理解が得られます。
34-4. このセクションのまとめ
- Symfonyはコンポーネントベース
- DoctrineがDataMapper ORM
- Laravelと双璧、両者相補的
- 大規模・DDDならSymfony、速度重視ならLaravel
35. PHP拡張FAQ
Q1. なぜ $ プレフィックスを使う?
Perl由来の慣習。変数と定数・関数を視覚的に区別しやすい。
Q2. == と ===
== は型変換あり、=== は型まで一致。常に === を使う。"0" == false はtrue、PHP 8までは "abc" == 0 もtrue(PHP 8で修正)。
Q3. nullableとOptional
PHPにはOptional型はない。?Type でのみ表現:
function find(int $id): ?User { ... }
Q4. self vs static vs parent
self::method() // 定義クラスのメソッド(早期束縛)
static::method() // 呼び出し時のクラス(遅延束縛)
parent::method() // 親クラスのメソッド
Q5. require / include / require_once
require は失敗でfatal、include はwarning。*_once で重複読み込み防止。autoloadを設定すればrequireは不要。
Q6. echo vs print vs printf
echo "hello"; // 文、複数引数可、最速
print "hello"; // 式(1を返す)
printf("%d", 42); // sprintfを出力
Q7. array vs Collection
PHPのarrayは強力だが、Laravelの Collection は fluentな操作ができて読みやすい。
collect([1, 2, 3])
->filter(fn($x) => $x > 1)
->map(fn($x) => $x * 2)
->sum(); // 10
Q8. PHPDocは必要?
型宣言が増えた今は、複雑な型(generic)や説明が必要なときだけ。
/** @return array<int, User> */
public function getUsers(): array { ... }
PHPStan / PsalmがPHPDocのgeneric表記を理解するので、有用。
Q9. final / readonly / abstract
final: 継承禁止
readonly: 再代入禁止
abstract: 実装必須
組み合わせ可能。final readonly class で「これ以上変えない」値オブジェクト。
Q10. include vs autoload
autoload (composer dump-autoload) のほうが推奨。
Q11. __construct以外のマジックメソッド
__get / __set / __isset / __unset // プロパティアクセス
__call / __callStatic // メソッド呼び出し
__toString // 文字列化
__invoke // $obj() で呼ぶ
__clone // clone $obj
__sleep / __wakeup // serialize制御
__serialize / __unserialize // モダン版(PHP 7.4+)
__debugInfo // var_dumpカスタム
__set_state // var_exportカスタム
Q12. Traitの落とし穴
use で混ぜ込むと 同名メソッドの衝突が起こる。insteadof / as で解決。
Q13. nullsafe ?-> の制約
代入には使えない:
$user?->name = "Alice"; // Error
読み取り専用。
Q14. 配列のキーで使えない型
使える: int, string
使えない: object, array, resource
objectをキーにしたいなら SplObjectStorage。
Q15. sessionの扱い
session_start();
$_SESSION['user_id'] = 1;
PHP-FPMではCookie経由。CSRF・XSS対策を別途考慮。
Q16. PHPの文字エンコーディング
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');
UTF-8が標準。mb_* 関数を使うのが慣習。
Q17. DateTimeとDateTimeImmutable
DateTime はミュータブル、DateTimeImmutable はイミュータブル。現代的なコードは DateTimeImmutable を使う。
$d = new DateTimeImmutable('2024-01-01');
$d2 = $d->modify('+1 day'); // 新しいインスタンス、$dは変わらない
Q18. Carbon vs DateTimeImmutable
CarbonはDateTime/DateTimeImmutableのラッパー。Laravelで標準。より読みやすいAPI。
Carbon::now()->addDays(7)->endOfDay();
Q19. PHPは遅い?
PHP 7+ で大幅高速化。OPcache + JITで実用十分。Web用途ではIOバウンドが多いので言語の差は小さい。
Q20. PHP-FPM vs Swoole
PHP-FPM: リクエスト毎に状態リセット、シンプル、本番標準
Swoole/RoadRunner: 常駐プロセス、状態保持可能、async可能、新しい
新規プロジェクトでパフォーマンスが必要ならSwoole / RoadRunnerも選択肢。
Q21. PHPStan / Psalm
静的解析ツール。型ヒントを補強してTypeScript的な体験を実現。
phpstan analyze --level=9 src/
psalm
CIで必須。
Q22. xdebug
PHPデバッガ + プロファイラ。IDE連携で本格的なデバッグが可能。
Q23. PHP 8で型宣言が必須?
「できるだけ型を付ける」のが現代の規範。mixed でも明示するほうが良い。
Q24. WordPressのコードが古いのはなぜ?
PHP 5.6までの長い後方互換性を保っている。現代のPHPプロジェクトの参考にはならない。
Q25. LaravelとSymfony、どっち学ぶ?
Laravelから始めるのが学習コスト低い。中級者はSymfonyで深く学ぶとOOP / DDDの理解が進む。
Q26. PHP-FPMのチューニング
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 2
pm.max_spare_servers = 10
pm.max_requests = 1000 # メモリリーク対策
CPU/メモリに合わせて調整。
Q27. シリアライゼーション
serialize() / unserialize() は 危険(任意コード実行の可能性)。JSONを使う。
Q28. __toString の罠
class A {
public function __toString(): string {
return "A"; // 必ずstringを返す
}
}
例外を投げると致命的エラーに。
Q29. PHPのWebSocket
Ratchet、Swoole、ReactPHPで実装可能。本番ではSwooleかNode.js / Goが主流。
Q30. CGI / SAPI / PHP-FPMの違い
CGI: リクエストごとにPHPプロセス起動(遅い)
mod_php: Apacheの中で動く(伝統的、リソース効率悪い)
PHP-FPM: ワーカープール(FastCGI、現代の標準)
CLI: コマンドラインで実行
Swoole: 常駐プロセス
36. PHPの現代的セットアップ
36-1. プロジェクト初期化
composer create-project laravel/laravel myapp
# または
composer create-project symfony/skeleton myapp
# または最小構成
mkdir myapp && cd myapp
composer init
36-2. 推奨ツール
PHP_CodeSniffer (phpcs): コードスタイル
php-cs-fixer: スタイル自動修正
PHPStan: 静的解析(厳格)
Psalm: 静的解析(vimeo製)
Rector: 自動リファクタリング・アップグレード
PHPUnit / Pest: テスト
Xdebug: デバッグ・プロファイル
Composer: パッケージ管理
36-3. 開発環境
Docker (Laravel Sail): 公式推奨、最も簡単
DDEV: Localの代替
Laragon: Windows用
Valet: macOS用
36-4. CI例
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: shivammathur/setup-php@v2
with: { php-version: '8.3' }
- run: composer install
- run: vendor/bin/phpcs
- run: vendor/bin/phpstan analyze
- run: vendor/bin/phpunit
37. PHP学習リソース
書籍
Web
- php.net(公式マニュアル、すべての関数の詳細)
- phptherightway.com
- laravel.com/docs
- symfony.com/doc
コミュニティ
実践: Webアプリケーション開発
39. 高度なOOPパターン
39-1. Repositoryパターン
interface UserRepositoryInterface {
public function find(int $id): ?User;
public function findByEmail(string $email): ?User;
public function save(User $user): void;
public function delete(User $user): void;
}
class EloquentUserRepository implements UserRepositoryInterface {
public function find(int $id): ?User {
return User::find($id);
}
public function findByEmail(string $email): ?User {
return User::where('email', $email)->first();
}
public function save(User $user): void {
$user->save();
}
public function delete(User $user): void {
$user->delete();
}
}
「データアクセスを抽象化」する。テスト時にモック差し替え可能。
39-2. Service Layer
class UserService {
public function __construct(
private UserRepositoryInterface $repo,
private MailerInterface $mailer,
private LoggerInterface $logger,
) {}
public function register(string $name, string $email, string $password): User {
if ($this->repo->findByEmail($email)) {
throw new EmailAlreadyExistsException($email);
}
$user = new User(
name: $name,
email: $email,
password: password_hash($password, PASSWORD_DEFAULT),
);
$this->repo->save($user);
$this->mailer->send(new WelcomeEmail($user));
$this->logger->info("User registered", ['user_id' => $user->id]);
return $user;
}
}
39-3. Value Object
final readonly class Email {
public string $value;
public function __construct(string $value) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Invalid email: $value");
}
$this->value = strtolower($value);
}
public function __toString(): string {
return $this->value;
}
public function equals(Email $other): bool {
return $this->value === $other->value;
}
}
$email = new Email("Alice@Example.com");
$email->value; // "alice@example.com"
final readonly で イミュータブル + 検証付きの値オブジェクト。
39-4. Strategyパターン
interface PaymentStrategy {
public function pay(int $amount): bool;
}
class CreditCardPayment implements PaymentStrategy {
public function pay(int $amount): bool { ... }
}
class PaypalPayment implements PaymentStrategy {
public function pay(int $amount): bool { ... }
}
class Order {
public function __construct(private PaymentStrategy $payment) {}
public function checkout(int $amount): bool {
return $this->payment->pay($amount);
}
}
39-5. Decoratorパターン
interface Logger {
public function log(string $msg): void;
}
class ConsoleLogger implements Logger {
public function log(string $msg): void {
echo $msg . "\n";
}
}
class TimestampedLogger implements Logger {
public function __construct(private Logger $inner) {}
public function log(string $msg): void {
$this->inner->log("[" . date('c') . "] " . $msg);
}
}
$logger = new TimestampedLogger(new ConsoleLogger());
$logger->log("hello"); // "[2024-01-01T12:34:56+00:00] hello"
39-6. このセクションのまとめ
- Repositoryでデータアクセス抽象化
- Service Layerでビジネスロジック
- Value Objectでドメイン概念
- Strategy / Decoratorが定番
40. ジェネリクスとPHPDoc
PHPには 言語レベルのジェネリクスはないが、PHPDoc + PHPStan / Psalmで実用的に表現可能。
/**
* @template T
*/
class Container {
/** @var T */
private mixed $value;
/**
* @param T $value
*/
public function __construct(mixed $value) {
$this->value = $value;
}
/**
* @return T
*/
public function get(): mixed {
return $this->value;
}
}
/** @var Container<User> $c */
$c = new Container(new User(...));
$user = $c->get(); // PHPStan/PsalmがUserと推論
/**
* @template TKey
* @template TValue
* @param array<TKey, TValue> $arr
* @return array<TKey, TValue>
*/
function reverse(array $arr): array {
return array_reverse($arr);
}
PHP 9でジェネリクスが言語化される可能性が議論中。それまではPHPDocが現実解。
41. Webセキュリティ
41-1. SQLインジェクション
// Bad: 直接埋め込み
$id = $_GET['id'];
$pdo->query("SELECT * FROM users WHERE id = $id");
// Good: prepared statement
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $_GET['id']]);
必ずprepared statement を使う。Eloquent / Doctrineが自動でやってくれる。
41-2. XSS(Cross-Site Scripting)
// Bad
echo $_GET['name'];
// Good
echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
Blade {{ $var }} は自動エスケープ。生PHPでは明示的に。
41-3. CSRF
Laravel / Symfonyは 自動的にCSRFトークンを発行・検証。生PHPでは:
// セッションにトークン
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// フォーム
<input type="hidden" name="_token" value="<?= $_SESSION['csrf_token'] ?>">
// 検証
if (!hash_equals($_SESSION['csrf_token'], $_POST['_token'])) {
throw new SecurityException("CSRF detected");
}
41-4. パスワード
// 保存時
$hash = password_hash($password, PASSWORD_DEFAULT); // bcrypt / argon2
// 検証時
if (password_verify($password, $hash)) {
// OK
}
// 再ハッシュが必要か
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$newHash = password_hash($password, PASSWORD_DEFAULT);
}
md5 や sha1 は 絶対に使わない。password_hash が公式の推奨。
41-5. セッション固定攻撃
session_start();
session_regenerate_id(true); // ログイン後にセッションIDを変更
41-6. ファイルアップロード
$file = $_FILES['upload'];
// 1. エラー確認
if ($file['error'] !== UPLOAD_ERR_OK) { ... }
// 2. サイズ確認
if ($file['size'] > 10 * 1024 * 1024) { ... }
// 3. MIME type確認(クライアント送信値ではなく実際の内容で)
$mime = mime_content_type($file['tmp_name']);
if (!in_array($mime, ['image/jpeg', 'image/png'])) { ... }
// 4. 安全なパスへ保存(ユーザ入力をパスに使わない)
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$safePath = "/uploads/" . bin2hex(random_bytes(16)) . "." . $ext;
move_uploaded_file($file['tmp_name'], $safePath);
41-7. このセクションのまとめ
- SQL Injection: prepared statement
- XSS: htmlspecialchars / Blade自動エスケープ
- CSRF: トークン検証
- Password: password_hash / verify
- セッション: session_regenerate_id
- ファイル: 内容ベース判定、安全なパス
42. パフォーマンスチューニング
42-1. プロファイリング
Xdebug profiler: 詳細プロファイル
Blackfire: 商用、本番でも使える
Tideways: 商用、軽量
42-2. OPcache設定
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0 # 本番(CIでデプロイ)
opcache.revalidate_freq=0
opcache.save_comments=1 # PHPDocを残す(attribute等で必要)
opcache.enable_file_override=1
opcache.preload=/app/preload.php # PHP 7.4+
42-3. 一般的な高速化
- DBクエリの最小化(N+1問題)
- インデックスの活用
- Redis / Memcachedでキャッシュ
- HTTPキャッシュ(ETag、Last-Modified)
- gzip / brotli圧縮
- CDN
- 画像最適化
42-4. preload(PHP 7.4+)
// preload.php
opcache_compile_file(__DIR__ . '/vendor/autoload.php');
foreach (glob(__DIR__ . '/src/**/*.php') as $file) {
opcache_compile_file($file);
}
opcache.preload=/path/to/preload.php
PHP起動時に一度ロードしておき、全リクエストで再利用。Laravelの起動時間が大幅短縮。
42-5. JIT
opcache.jit_buffer_size=100M
opcache.jit=tracing
CPUバウンドな処理(画像処理、暗号、数値計算)で効く。Webの通常リクエストでは効果薄。
43. ロギングと観測
43-1. Monolog
PHPの事実上の標準ロギングライブラリ。
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
$logger = new Logger('app');
$handler = new StreamHandler('/var/log/app.log', Logger::INFO);
$handler->setFormatter(new JsonFormatter());
$logger->pushHandler($handler);
$logger->info("User signed in", ['user_id' => 1]);
$logger->error("DB error", ['exception' => $e]);
PSR-3互換。Laravel / Symfonyがデフォルトで使う。
43-2. PSR-3の意味
Logger interfaceの業界標準。
interface LoggerInterface {
public function emergency($message, array $context = []);
public function alert($message, array $context = []);
public function critical($message, array $context = []);
public function error($message, array $context = []);
public function warning($message, array $context = []);
public function notice($message, array $context = []);
public function info($message, array $context = []);
public function debug($message, array $context = []);
public function log($level, $message, array $context = []);
}
ライブラリ間で互換性確保。
43-3. OpenTelemetry
composer require open-telemetry/sdk
composer require open-telemetry/exporter-otlp
分散トレーシング・メトリクス。Datadog / New Relic / Honeycombと連携。
44. デプロイと運用
44-1. Dockerfile
FROM php:8.3-fpm-alpine
# 必要な拡張
RUN apk add --no-cache postgresql-dev \
&& docker-php-ext-install pdo_pgsql opcache
# Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-interaction
COPY . .
# OPcache設定
COPY docker/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
CMD ["php-fpm"]
44-2. nginx + PHP-FPM
server {
listen 80;
root /app/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass app:9000; # PHP-FPMコンテナ
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
44-3. Laravel Sail(開発環境)
composer require laravel/sail --dev
php artisan sail:install
./vendor/bin/sail up
Docker ComposeでMySQL / Redis / Mailhogが一緒に立ち上がる。
44-4. Octane(高速化)
composer require laravel/octane
php artisan octane:install --server=swoole
php artisan octane:start
Swoole / RoadRunnerで Laravelを常駐化。リクエスト5〜10倍速くなる。
44-5. Forge / Vapor / Ploi
Laravelエコシステムのデプロイサービス。
- Forge: VPSのセットアップ自動化
- Vapor: AWS Lambda(サーバレス)
- Ploi: Forgeの代替
45. PHPのための雑多な知識
45-1. CSV処理
// 読み込み
if (($f = fopen('data.csv', 'r')) !== false) {
while (($row = fgetcsv($f)) !== false) {
process($row);
}
fclose($f);
}
// 書き出し
$f = fopen('out.csv', 'w');
fputcsv($f, ['Name', 'Age']);
fputcsv($f, ['Alice', 30]);
fclose($f);
45-2. JSON
$json = json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
// PHP 8.3+
if (json_validate($json)) { ... }
JSON_THROW_ON_ERROR で例外化(推奨)。
45-3. HTTPクライアント(Guzzle)
use GuzzleHttp\Client;
$client = new Client();
$response = $client->get('https://api.example.com/users', [
'query' => ['page' => 1],
'headers' => ['Authorization' => 'Bearer ...'],
]);
$body = json_decode($response->getBody(), true);
PSR-7 / PSR-18互換。
45-4. PSRの重要性
PSR-1: コーディング基本
PSR-3: Logger
PSR-4: Autoload
PSR-7: HTTP message
PSR-11: Container
PSR-12: コーディングスタイル
PSR-15: HTTP middleware
PSR-17: HTTP factory
PSR-18: HTTP client
PHP-FIGが制定。ライブラリ間の互換性の基盤。
45-5. Carbonの便利機能
use Carbon\Carbon;
Carbon::now();
Carbon::parse('2024-01-01')->addDays(7);
Carbon::yesterday();
Carbon::today()->endOfDay();
$diff = Carbon::now()->diff(Carbon::parse('2024-01-01'));
$human = Carbon::parse('2024-01-01')->diffForHumans(); // "2 months ago"
Laravelで標準。日時操作のデファクト。
45-6. 配列をCollectionで操作(Laravel)
use Illuminate\Support\Collection;
$users = collect($array);
$result = $users
->filter(fn($u) => $u->age >= 18)
->map(fn($u) => $u->name)
->sortBy(fn($n) => $n)
->values()
->all();
// グループ化
$users->groupBy('department');
// 集計
$users->sum('salary');
$users->avg('age');
$users->countBy('status');
// 部分的取得
$users->take(10);
$users->chunk(5);
$users->paginate(20);
応用: 設計と運用
47. ドメイン駆動設計とPHP
PHP(特にSymfony)は DDDと高い親和性を持ちます。
47-1. 値オブジェクト
final readonly class Money {
public function __construct(
public int $amount,
public string $currency,
) {
if ($amount < 0) {
throw new InvalidArgumentException();
}
if (!in_array($currency, ['JPY', 'USD', 'EUR'])) {
throw new InvalidArgumentException();
}
}
public function add(Money $other): Money {
if ($this->currency !== $other->currency) {
throw new DomainException("currency mismatch");
}
return new Money($this->amount + $other->amount, $this->currency);
}
public function equals(Money $other): bool {
return $this->amount === $other->amount
&& $this->currency === $other->currency;
}
}
final readonly class で イミュータブル + 厳密な値オブジェクト。
47-2. エンティティ
class Order {
public function __construct(
public readonly OrderId $id,
public readonly UserId $userId,
private array $items = [],
private OrderStatus $status = OrderStatus::Pending,
) {}
public function addItem(Product $product, int $quantity): void {
if ($this->status !== OrderStatus::Pending) {
throw new DomainException("cannot modify confirmed order");
}
$this->items[] = new OrderItem($product, $quantity);
}
public function confirm(): void {
if (empty($this->items)) {
throw new DomainException("empty order");
}
$this->status = OrderStatus::Confirmed;
}
public function totalAmount(): Money {
return array_reduce(
$this->items,
fn(Money $acc, OrderItem $item) => $acc->add($item->subtotal()),
new Money(0, 'JPY')
);
}
}
47-3. ドメインイベント
interface DomainEvent {
public function occurredAt(): DateTimeImmutable;
}
final readonly class OrderConfirmed implements DomainEvent {
public function __construct(
public OrderId $orderId,
public Money $total,
public DateTimeImmutable $occurredAt,
) {}
public function occurredAt(): DateTimeImmutable {
return $this->occurredAt;
}
}
48. CQRSパターン
48-1. コマンドとクエリの分離
interface CommandHandler {
public function handle(Command $cmd): void;
}
interface QueryHandler {
public function handle(Query $query): mixed;
}
// コマンド
final readonly class CreateUserCommand {
public function __construct(
public string $name,
public string $email,
) {}
}
class CreateUserHandler implements CommandHandler {
public function __construct(
private UserRepositoryInterface $repo,
private EventDispatcherInterface $events,
) {}
public function handle(Command $cmd): void {
assert($cmd instanceof CreateUserCommand);
$user = User::register($cmd->name, $cmd->email);
$this->repo->save($user);
$this->events->dispatch(new UserRegistered($user->id));
}
}
// クエリ(読み取り側、軽量でOK)
final readonly class GetUserQuery {
public function __construct(public int $userId) {}
}
class GetUserHandler implements QueryHandler {
public function __construct(private PDO $db) {}
public function handle(Query $q): ?UserDto {
assert($q instanceof GetUserQuery);
$stmt = $this->db->prepare("SELECT id, name, email FROM users WHERE id = ?");
$stmt->execute([$q->userId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? new UserDto(...$row) : null;
}
}
「書き込みは厳密にドメインモデル経由、読み取りは速さ優先で生SQL OK」がCQRSの本質。
49. プロパティアクセス制御の高度な使い分け
49-1. アクセサメソッドvs直接アクセス
// 古いスタイル
class User {
private string $name;
public function getName(): string { return $this->name; }
public function setName(string $name): void { $this->name = $name; }
}
// PHP 8.1+ readonlyでシンプルに
final readonly class User {
public function __construct(public string $name) {}
}
// PHP 8.4+(提案中)プロパティフックでJSのget/set風
class User {
public string $name {
get => strtoupper($this->name);
set => $this->name = trim($value);
}
}
49-2. インターフェース分離原則
interface UserReader {
public function find(int $id): ?User;
}
interface UserWriter {
public function save(User $user): void;
}
interface UserRepositoryInterface extends UserReader, UserWriter {}
「読み取りだけ」のクラスに書き込みも見えるのを防ぐ。
50. パッケージ作成と公開
50-1. ライブラリ構成
my-package/
├── composer.json
├── README.md
├── LICENSE
├── src/
│ ├── ClassA.php
│ └── ClassB.php
├── tests/
│ └── ClassATest.php
├── .github/workflows/ci.yml
├── phpunit.xml
├── phpstan.neon
└── .gitignore
50-2. composer.json(ライブラリ用)
{
"name": "myorg/my-package",
"type": "library",
"description": "...",
"license": "MIT",
"require": {
"php": "^8.2"
},
"autoload": {
"psr-4": {
"MyOrg\\MyPackage\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"MyOrg\\MyPackage\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit",
"phpstan": "phpstan analyze"
}
}
50-3. Packagist公開
# GitHubにリポジトリを作成し、composer.jsonを含める
git tag v1.0.0
git push --tags
# packagist.orgでリポジトリを登録
# 自動的にwebhookで同期される
51. パフォーマンスケーススタディ
51-1. 巨大データのストリーミング
// Bad: メモリにすべて読む
$lines = file('huge.txt');
foreach ($lines as $line) { ... }
// Good: ジェネレータで1行ずつ
function readLines(string $path): Generator {
$f = fopen($path, 'r');
try {
while (($line = fgets($f)) !== false) {
yield $line;
}
} finally {
fclose($f);
}
}
foreach (readLines('huge.txt') as $line) {
process($line);
}
51-2. DBのN+1問題
// Bad
$users = User::all();
foreach ($users as $user) {
echo $user->orders->count(); // ループ内で別クエリ
}
// Good: eager load
$users = User::with('orders')->get();
foreach ($users as $user) {
echo $user->orders->count(); // 既にロード済み
}
// またはwithCount
$users = User::withCount('orders')->get();
foreach ($users as $user) {
echo $user->orders_count;
}
51-3. キャッシュ戦略
use Illuminate\Support\Facades\Cache;
// シンプルなキャッシュ
$users = Cache::remember('all_users', 3600, fn() => User::all());
// 永続化
Cache::forever('config', $config);
// タグ(Redis等)
Cache::tags(['users', 'admin'])->put('admin_users', $admins, 3600);
Cache::tags('users')->flush(); // タグごとにクリア
52. 環境変数と設定
// .env
APP_NAME=MyApp
APP_ENV=production
APP_KEY=base64:...
DB_CONNECTION=pgsql
DB_HOST=localhost
DB_PORT=5432
// 読み込み(Laravel)
$dbHost = env('DB_HOST', 'localhost');
// 設定ファイル(config/database.php)
return [
'connections' => [
'pgsql' => [
'host' => env('DB_HOST'),
'port' => env('DB_PORT'),
],
],
];
// アクセス
$host = config('database.connections.pgsql.host');
vlucas/phpdotenv で .envを読む。.envはGitにコミットしない(.env.example を共有)。
53. 拡張FAQ(更に追加)
Q31. WeakRef / WeakMap
$obj = new stdClass();
$weak = WeakReference::create($obj);
$weak->get(); // $obj
unset($obj);
$weak->get(); // null(GCされた)
PHP 7.4+ で利用可能。観察者パターンで親子の循環参照を避ける用途。
Q32. SplFixedArray
$arr = new SplFixedArray(1000000);
$arr[0] = 1;
固定長配列。通常のarrayより省メモリ。大量数値処理で。
Q33. SplPriorityQueue / SplStack / SplQueue
データ構造ライブラリ。配列で代用できることが多いが、意図を明確にするため使うことも。
Q34. PHPUnitのデータプロバイダで複雑なケース
public static function complexProvider(): Generator {
yield 'case 1' => [...];
yield 'case 2' => [...];
// CSVから
$f = fopen('cases.csv', 'r');
while ($row = fgetcsv($f)) {
yield "csv $row[0]" => $row;
}
fclose($f);
}
Generatorで動的にケース生成。
Q35. Property hook(PHP 8.4提案)
// 提案中
class Temperature {
public float $celsius {
get => $this->celsius;
set(float $value) {
if ($value < -273.15) throw new ValueError();
$this->celsius = $value;
}
}
}
JSのget/set / Kotlinのproperty風。PHP 8.4で来る予定。
Q36. Array unpacking
function f(int $a, int $b, int $c): int { ... }
$args = [1, 2, 3];
f(...$args); // f(1, 2, 3)
// 名前付き
$args = ['c' => 3, 'a' => 1, 'b' => 2];
f(...$args); // PHP 8.1+
Q37. var_dump vs print_r vs var_export
var_dump($x); // 型 + 値、デバッグ用
print_r($x); // 人間可読
print_r($x, true); // 戻り値で取得
var_export($x); // PHPコードとして出力(eval可能)
Q38. namespaceヒエラルキー
namespace App\Domain\User;
use App\Domain\Common\Email;
use \DateTimeImmutable;
use function App\Domain\Common\format;
use const App\Domain\Common\MAX;
use でクラス、関数、定数すべてをimport。
Q39. cloneと __clone
$copy = clone $original; // 浅いコピー
class Foo {
public Bar $bar;
public function __clone() {
$this->bar = clone $this->bar; // ディープコピー
}
}
Q40. Closure::bind
$closure = function () { return $this->private; };
$bound = Closure::bind($closure, $obj, get_class($obj));
$bound(); // privateにアクセスできる
クロージャを別オブジェクトにバインド。テストやライブラリで稀に。
54. PHPの長い未来
PHPは1994年から30年続いている言語ですが、衰退しているわけではありません:
- WordPress: 今もWebの40%+
- Laravel: 業界トップクラスの成長率
- PHP 8: 言語が劇的にモダン化
- JIT / Native AOT: 性能改善
- Swoole / RoadRunner: 常駐化
- WASM: ブラウザでも動く実験的試み
「速くアプリを作る」「学習者が多い」「ホスティングが安い」「仕事が多い」 ─ これらがPHPの継続的な強みです。
PHP 9への期待:
- Generic types(言語レベル)
- Property hook
- Pipe operator |>
- 更なる型システム強化
- パフォーマンス改善
55. 学習リソース
書籍
- 『Modern PHP』Josh Lockhart
- 『Laravel: Up & Running』Matt Stauffer
- 『Symfony 6 Fast Track』Fabien Potencier
- 『PHP 8 Programming Tips, Tricks and Best Practices』Doug Bierer
Web
- php.net(公式)
- phptherightway.com
- laravel.com/docs
- symfony.com/doc
- laracasts.com(動画チュートリアル)
- freek.dev(SpatieのFreek Van der Herten)
コミュニティ
56. 完全なプロジェクト構成例
myapp/
├── app/
│ ├── Domain/
│ │ ├── User/
│ │ │ ├── User.php
│ │ │ ├── UserRepositoryInterface.php
│ │ │ └── Email.php
│ │ └── Common/
│ ├── Infrastructure/
│ │ └── Persistence/
│ │ └── EloquentUserRepository.php
│ ├── Application/
│ │ ├── Commands/
│ │ │ └── CreateUserHandler.php
│ │ └── Queries/
│ │ └── GetUserHandler.php
│ ├── Http/
│ │ ├── Controllers/
│ │ ├── Requests/
│ │ ├── Resources/
│ │ └── Middleware/
│ └── Models/
│ └── UserModel.php
├── config/
├── database/
│ ├── migrations/
│ ├── seeders/
│ └── factories/
├── public/
│ └── index.php
├── resources/
│ └── views/
├── routes/
│ ├── web.php
│ └── api.php
├── storage/
├── tests/
│ ├── Unit/
│ └── Feature/
├── .env.example
├── .gitignore
├── composer.json
├── composer.lock
├── phpunit.xml
├── phpstan.neon
└── README.md
レイヤー化された構成例。Laravelの標準構成にDDD風の分割を加えたもの。
補遺: 実例集
58. 完全な実例: REST API(Laravel)
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('users', UserController::class);
Route::apiResource('users.posts', UserPostController::class);
});
// app/Http/Controllers/UserController.php
namespace App\Http\Controllers;
use App\Http\Requests\StoreUserRequest;
use App\Http\Requests\UpdateUserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
class UserController extends Controller {
public function index() {
return UserResource::collection(User::paginate(20));
}
public function show(User $user) { // route model binding
return new UserResource($user);
}
public function store(StoreUserRequest $request) {
$user = User::create($request->validated());
return (new UserResource($user))
->response()
->setStatusCode(201);
}
public function update(UpdateUserRequest $request, User $user) {
$user->update($request->validated());
return new UserResource($user);
}
public function destroy(User $user) {
$user->delete();
return response()->noContent();
}
}
// app/Http/Requests/StoreUserRequest.php
class StoreUserRequest extends FormRequest {
public function authorize(): bool {
return $this->user()->can('create', User::class);
}
public function rules(): array {
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', Rule::unique('users')],
'password' => ['required', 'string', 'min:8'],
];
}
}
// app/Http/Resources/UserResource.php
class UserResource extends JsonResource {
public function toArray(Request $request): array {
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'posts_count' => $this->whenCounted('posts'),
];
}
}
これだけで本格的なAPI。バリデーション・認可・ペイロード変換がそれぞれ別クラスに整理されており、実プロジェクトで保守性が高い。
59. 完全な実例: Symfony
// src/Controller/UserController.php
namespace App\Controller;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
#[Route('/api/users')]
class UserController extends AbstractController {
public function __construct(
private UserRepository $userRepository,
private EntityManagerInterface $em,
private ValidatorInterface $validator,
) {}
#[Route('', methods: ['GET'])]
public function index(): JsonResponse {
$users = $this->userRepository->findAll();
return $this->json($users);
}
#[Route('/{id}', methods: ['GET'])]
public function show(int $id): JsonResponse {
$user = $this->userRepository->find($id);
if (!$user) {
return $this->json(['error' => 'not found'], 404);
}
return $this->json($user);
}
#[Route('', methods: ['POST'])]
public function create(Request $request): JsonResponse {
$data = json_decode($request->getContent(), true);
$user = new User();
$user->setName($data['name']);
$user->setEmail($data['email']);
$errors = $this->validator->validate($user);
if (count($errors) > 0) {
return $this->json(['errors' => (string)$errors], 422);
}
$this->em->persist($user);
$this->em->flush();
return $this->json($user, 201);
}
}
// src/Entity/User.php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
#[Assert\Length(max: 255)]
private string $name;
#[ORM\Column(length: 255, unique: true)]
#[Assert\NotBlank]
#[Assert\Email]
private string $email;
public function getId(): ?int { return $this->id; }
public function getName(): string { return $this->name; }
public function setName(string $name): void { $this->name = $name; }
public function getEmail(): string { return $this->email; }
public function setEmail(string $email): void { $this->email = $email; }
}
Symfonyの DI + Doctrine + Validator が連携した典型例。Spring Frameworkに近い構成。
60. 実例: 認証システム(Laravel Sanctum)
// 設定
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
// Login API
class AuthController extends Controller {
public function login(Request $request) {
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['Credentials are incorrect.'],
]);
}
$token = $user->createToken('api-token')->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
]);
}
public function logout(Request $request) {
$request->user()->currentAccessToken()->delete();
return response()->noContent();
}
}
// 保護ルート
Route::middleware('auth:sanctum')->group(function () {
Route::get('/me', fn(Request $r) => $r->user());
Route::post('/logout', [AuthController::class, 'logout']);
});
// クライアント側
fetch('/api/login', {
method: 'POST',
body: JSON.stringify({email, password}),
headers: {'Content-Type': 'application/json'},
});
fetch('/api/me', {
headers: {'Authorization': `Bearer ${token}`},
});
トークンベース認証が 20-30行で実装可能。
61. 実例: バックグラウンドジョブ
// app/Jobs/ProcessVideo.php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessVideo implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $timeout = 300;
public function __construct(public Video $video) {}
public function handle(VideoProcessor $processor): void {
$processor->process($this->video);
}
public function failed(Throwable $e): void {
Log::error("Video processing failed", [
'video_id' => $this->video->id,
'error' => $e->getMessage(),
]);
}
}
// 投入
ProcessVideo::dispatch($video);
ProcessVideo::dispatch($video)->onQueue('video');
ProcessVideo::dispatch($video)->delay(now()->addMinutes(5));
// ワーカー起動
php artisan queue:work --queue=video --tries=3
Redis / DB / SQS / SNS等にバックエンドを切り替え可能。
62. 実例: WebSocket(Laravel Reverb)
composer require laravel/reverb
php artisan reverb:start
// Eventをbroadcast
class MessageSent implements ShouldBroadcast {
public function __construct(public Message $message) {}
public function broadcastOn(): Channel {
return new PrivateChannel('chat.' . $this->message->room_id);
}
}
event(new MessageSent($message));
// クライアント
import Echo from 'laravel-echo';
window.Echo.private(`chat.${roomId}`)
.listen('MessageSent', (e) => {
console.log(e.message);
});
リアルタイム通信がPHPで完結。Reverb(Laravel 11+)で Pusher互換のサーバが組み込みに。
63. PHPの隠れた便利機能
63-1. ジェネレータの双方向通信
function dialog() {
$msg = yield "ready";
yield "got: $msg";
}
$g = dialog();
echo $g->current(); // "ready"
$g->send("hello");
echo $g->current(); // "got: hello"
63-2. Closure::fromCallable
$callable = Closure::fromCallable([$obj, 'method']);
$callable = Closure::fromCallable('strlen');
// PHP 8.1+ first-class callable
$callable = $obj->method(...);
$callable = strlen(...);
63-3. spl_autoload_register
spl_autoload_register(function ($class) {
$file = __DIR__ . '/' . str_replace('\\', '/', $class) . '.php';
if (file_exists($file)) require $file;
});
Composerのautoloadの手動版。
63-4. ReflectionClassの活用
$ref = new ReflectionClass(User::class);
$ref->getMethods(ReflectionMethod::IS_PUBLIC);
$ref->getProperties();
$ref->getAttributes(MyAttribute::class);
$ref->newInstanceArgs([$arg1, $arg2]);
メタプログラミングの基盤。ORM・シリアライザで活用。
63-5. var_exportのeval
$config = ['db' => 'localhost', 'port' => 3306];
file_put_contents('config.cache.php', '<?php return ' . var_export($config, true) . ';');
$loaded = include 'config.cache.php'; // OPcacheされるので超高速
64. PHPのドキュメンテーション
/**
* ユーザを作成する
*
* @param string $nameユーザ名
* @param string $emailメールアドレス
* @param array<string, mixed> $options追加オプション
* @return User作成されたユーザ
* @throws InvalidArgumentException無効な入力
* @throws DuplicateEmailException既存のメール
*
* @see UserRepository::save()
*
* @example
* $user = UserService::create('Alice', 'a@b.com', ['role' => 'admin']);
*/
public function create(string $name, string $email, array $options = []): User {
// ...
}
PHPDocの @template、@var、@phpstan-* などでPHPStan / Psalmの解析を強化。
66. PHPプロジェクト立ち上げチェックリスト
新規プロジェクト
☐ PHP 8.2 / 8.3を使う
☐ composer init / Laravel/Symfonyプロジェクト
☐ declare(strict_types=1) を全ファイルに
☐ PHPStan / Psalmをlevel高めで導入
☐ PHPUnit / Pestをセットアップ
☐ .env.exampleを整備、.envはgitignore
☐ Docker環境(Laravel Sail / DDEV)
☐ CI(GitHub Actions / GitLab CI)
☐ READMEに依存とセットアップ手順
コード規律
☐ public/protected/privateを最小限の粒度で
☐ 値オブジェクトにはfinal readonly
☐ 列挙型はenum
☐ DateTimeImmutable(DateTimeではなく)
☐ CarbonはLaravelで
☐ === を使う、== は禁止
☐ arraysよりCollection(Laravel)
☐ EloquentのN+1問題に注意
セキュリティ
☐ password_hash / verify
☐ CSRFトークン検証
☐ XSS対策(htmlspecialchars / Blade)
☐ SQL Injection対策(prepared statement)
☐ ファイルアップロード検証
☐ session_regenerate_idログイン時
☐ HTTPS強制
☐ Composer auditで依存脆弱性チェック
パフォーマンス
☐ OPcache有効化
☐ JIT(CPUバウンド処理あれば)
☐ DBインデックス
☐ Redis / Memcachedキャッシュ
☐ HTTPキャッシュヘッダ
☐ 画像最適化
☐ CDN
☐ N+1問題検出
67. PHP資料リスト
公式
- php.net(マニュアル)
- wiki.php.net(言語提案)
学習サイト
ツール
- PHPStan、Psalm(静的解析)
- Rector(自動アップグレード)
- PHPUnit、Pest(テスト)
- Laravel、Symfony(フレームワーク)
- Composer、Packagist(パッケージ)
書籍(再掲)
68. PHPの世界観
PHPは 「Webのための言語」として最適化された存在です。リクエスト単位で完結する実行モデル、HTMLとの混在、巨大なエコシステム ─ これらは他の汎用言語にはないPHP独自の強みです。
PHPが向く用途:
- Webアプリ全般
- CMS / ブログ
- ECサイト
- 管理画面
- REST API
- レガシー保守
PHPが向かない用途:
- 組み込み / システム
- 機械学習・データサイエンス
- 高頻度取引
- リアルタイムゲーム
- GUIデスクトップ
「得意なことに集中する」のがPHPの哲学。Web以外の領域では他言語と協調する形が自然です。
69. PHPコミュニティ
- Laracon(Laravel公式カンファレンス)
- SymfonyCon
- PHP UK Conference
- PHP Day Italia
- PHPカンファレンス(日本)
- PHP TEK
オンラインコミュニティ:
Composer とパッケージ管理
Composer の歴史と現状
Composer(getcomposer.org)は 2012 年に開発された PHP の依存関係管理ツール。npm(Node.js)や pip(Python)と同様の立場を占めています。
Composer が登場する前、PHP には標準化されたパッケージ管理がなく、クラスファイルを手動で include していました(getcomposer.org)。
composer.json の基本構造
{
"name": "acme/myapp",
"description": "My awesome application",
"require": {
"php": ">=8.1",
"monolog/monolog": "^3.0",
"symfony/console": "^6.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"squizlabs/php_codesniffer": "^3.7"
},
"autoload": {
"psr-4": {
"Acme\\MyApp\\": "src/"
}
}
}
バージョン指定
"monolog/monolog": "^3.0" // 3.0.0 以上 4.0.0 未満
"symfony/console": "~6.0" // 6.0.0 以上 6.1.0 未満
"phpunit/phpunit": ">=10.0" // 10.0.0 以上
composer.lock ファイル
composer update 実行時に composer.json から最新互換版を解決 → composer.lock 生成
composer install composer.lock があればそれを厳密に使用
同じバージョンを全環境で使用するため、composer.lock は git に commit。
Packagist (PHP パッケージレジストリ)
Packagist(packagist.org)は、Composer のパッケージリポジトリです。
composer require monolog/monolog
↓
Packagist から monolog/monolog の最新互換版を検索
↓
github から ソースをダウンロード
↓
vendor/ にインストール
PSR(PHP Standards Recommendation)
PSR-4: Autoloading Standard
PSR(php-fig.org)は、PHP コミュニティが定めた標準仕様です。PSR-4 は自動クラス読み込み規約:
namespace Acme\MyApp;
class User { }
ファイル: src/User.php
ファイルパスと namespace が 1:1 対応。Composer が自動的に PSR-4 autoloader を生成:
// vendor/autoload.php
require_once __DIR__ . '/composer/autoload_psr4.php';
use Acme\MyApp\User; // 自動的に src/User.php を読み込み
$user = new User();
PSR-12: Extended Coding Style
// PSR-12 準拠のコード例
<?php
namespace Acme\MyApp;
class User
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
- インデントは 4 スペース
- オープニングブレース
{は同一行 - メソッド間は 1 行空行
PSR-7: HTTP Message Interface
// PSR-7 準拠: Request / Response は immutable
interface RequestInterface {
public function getMethod(): string;
public function getUri(): UriInterface;
public function getHeaders(): array;
}
Slim, Laravel など多くのフレームワークが PSR-7 に準拠。
PSR-15: HTTP Server Request Handlers
// Middleware の標準インターフェース
interface MiddlewareInterface {
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface;
}
// 使用例
class AuthMiddleware implements MiddlewareInterface {
public function process($request, $handler) {
if (!$this->isAuthenticated($request)) {
return new Response(401); // Unauthorized
}
return $handler->handle($request);
}
}
PHP FIG による標準化推進
PHP-FIG(PHP Framework Interoperability Group, php-fig.org)は、フレームワーク間の相互運用性を推進する非営利団体。
PSR の提案から承認までのプロセス:
Draft → Review → Accepted → Deprecated/Superseded
現在 20+ の PSR が存在:
- PSR-1: Basic Coding Standard
- PSR-11: Container Interface
- PSR-14: Event Dispatcher
- PSR-17: HTTP Factories
- PSR-18: HTTP Client
PHP 8.0+ の型安全性とパフォーマンス
Union Types (PHP 8.0)
// 引数が int または string を受け入れる
public function processId(int|string $id): int|string {
if (is_string($id)) {
return (int) $id;
}
return $id;
}
PHP 7 では DocBlock でのみ型ヒントが可能:
/**
* @param int|string $id
* @return int
*/
public function processId($id) { }
型安全性が向上し、IDE のサポートが強化。
Named Arguments (PHP 8.0)
// 従来: 位置による引数
sendEmail($recipient, $subject, $body, $attachments, true);
// PHP 8.0: 名前付き引数
sendEmail(
recipient: $to,
subject: "Welcome",
body: $message,
isHtml: true
); // 順序不問、省略可能
Attributes (PHP 8.0) - Annotation 代替
#[Route('/users/{id}', methods: ['GET'])]
public function getUser(int $id): User { }
// リフレクションで取得
$reflection = new ReflectionMethod($class, 'getUser');
foreach ($reflection->getAttributes() as $attr) {
$route = $attr->newInstance();
echo $route->path; // "/users/{id}"
}
従来の DocBlock コメントより型安全。
Constructor Property Promotion (PHP 8.0)
// PHP 7.x
class User {
private string $name;
private string $email;
public function __construct(string $name, string $email) {
$this->name = $name;
$this->email = $email;
}
}
// PHP 8.0
class User {
public function __construct(
private string $name,
private string $email
) { } // 自動的にプロパティが作成・割り当てされる
}
Match Expression (PHP 8.0)
// switch vs match
$status = match ($code) {
200 => 'OK',
404 => 'Not Found',
500 => 'Server Error',
default => 'Unknown'
}; // match は式(値を返す)
Fibers (PHP 8.1) - 軽量スレッド
$fiber = new Fiber(function(): void {
echo "Fiber 1 start\n";
Fiber::suspend();
echo "Fiber 1 end\n";
});
$fiber->start();
// 出力: "Fiber 1 start"
$fiber->resume();
// 出力: "Fiber 1 end"
コンテキストスイッチなしの効率的な並行処理。Async ライブラリが容易に。
Readonly Properties (PHP 8.1)
class User {
public readonly int $id;
public readonly string $email;
public function __construct(int $id, string $email) {
$this->id = $id;
$this->email = $email;
}
// 以降、$id や $email は変更不可
}
$user->email = "new@example.com"; // Error!
イミュータブルオブジェクトの強制。
First-class Callable Syntax (PHP 8.1)
// PHP 7.x: 関数参照は文字列
$callable = 'strlen';
// PHP 8.1: 言語構文で参照
$callable = strlen(...);
array_map($callable, ['hello', 'world']); // [5, 5]
型安全性と IDE サポート向上。
PHP フレームワークのアーキテクチャ
PSR-4 オートローディングの実装例
// index.php
require_once __DIR__ . '/vendor/autoload.php';
// ユーザーコード
use App\Controllers\UserController;
use App\Models\User;
$user = new User();
$controller = new UserController();
Composer が自動生成した autoload_psr4.php が、namespace から ファイルパスへマッピング。
依存性注入コンテナ
PSR-11 準拠のコンテナを使用:
// DI Container の設定
$container = new Container();
$container->set(PDO::class, function() {
return new PDO('sqlite::memory:');
});
$container->set(UserRepository::class, function($c) {
return new UserRepository($c->get(PDO::class));
});
// 使用
$repository = $container->get(UserRepository::class);
$users = $repository->getAll();
イベント駆動(PSR-14)
use Psr\EventDispatcher\EventDispatcherInterface;
class UserService {
public function __construct(
private EventDispatcherInterface $dispatcher
) { }
public function createUser(array $data): User {
$user = new User($data);
$this->dispatcher->dispatch(new UserCreated($user));
return $user;
}
}
// リスナー登録
$dispatcher->addSubscriber(new UserCreatedSubscriber());
まとめ
PHPは、Webアプリケーションの実装に特化して発展してきた言語です。現代のPHPでは、型宣言、Composer、PSR、フレームワーク、テストを前提にすることで、従来の手軽さを保ちながら、保守しやすいアプリケーションを構築できます。