本ツールで使用しているオープンソースライブラリ

本ツールのコードには 1 個のオープンソースライブラリが含まれています。

PHP チートシート — 簡潔なリファレンス

PHP 8.3 の構文、型、配列と最もよく使う標準ライブラリをカバーするチートシートで、日常シーンの約 80% を網羅する。

Php

PHP PHP 8.3(LTS 相当)

Zend Engine(PHP ランタイム) · 命令型 · OO · 関数型 · 動的(オプションで静的型付け)

おすすめの学習パス

まず php hello.php と php -S 内蔵サーバーを使いこなす → 変数、型、フロー制御を修得 → 関数、クロージャ、配列を深く学ぶ → 参照のセマンティクスとオブジェクトの参照ハンドルを理解 → クラス、インターフェース、例外でコードを組み立てる → 必要に応じてファイル I/O、cURL、日付、正規表現を学ぶ → Fiber/pcntl で並行処理に対応する。FAQ セクションは後で落とし穴を回避するのに役立つ。

1.Hello World と実行環境

PHP プログラムの実行、組み込み Web サーバー、コマンドライン引数と終了コード。

最小プログラム

PHP ファイルは <?php で始まり、CLI では php ファイル名 で実行する。echo で文字列を出力する。

1
2
3
4
5
6
7
8
9
echo "Hello, world!\n";
// シングルクォートはエスケープと補間を解釈しない
echo 'Hello, world!\n';
echo "\n";
// ダブルクォート内で変数を補間
$name = 'PHP';
echo "Hello, $name!\n";

CLI 実行

php コマンドでスクリプトを直接実行する。php -l で構文チェック、php -r でコードを直接実行する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// スクリプトファイルを実行:
// $ php hello.php
// 出力 Hello, world!
// 一行コードを直接実行:
// $ php -r 'echo "hi\n";'
// 構文チェック(実行しない):
// $ php -l hello.php
// 対話式シェルを起動:
// $ php -a
// バージョンを確認:
// $ php -v

組み込み Web サーバー

php -S で組み込みサーバーを起動し、ディレクトリをドキュメントルートとして、ローカル開発やデバッグに合う。

1
2
3
4
5
6
7
8
9
10
11
12
13
// プロジェクトディレクトリで起動:
// $ php -S localhost:8000
// ドキュメントルートを指定:
// $ php -S localhost:8000 -t public
// ルートスクリプトを指定:
// $ php -S localhost:8000 router.php
// 起動後にブラウザでアクセス:
// http://localhost:8000/index.php
// ポートが使用中だと起動に失敗する

ファイル構造

<?php と ?> タグで PHP と HTML を分ける。PHP のみのファイルでは閉じタグを省略することを推奨する。

1
2
3
4
5
6
7
echo "纯 PHP 文件顶部直接写代码\n";
// 終了タグ ?> は省略を推奨
// ファイル末尾の改行が出力とみなされるのを防ぐ
// テンプレート混在時は ?> で HTML に戻す:
// <p><?= $title ?></p>
// 短縮タグ <?= は echo と等価

コマンドライン引数

$argv は引数の配列、$argc は引数の個数、argv[0] はスクリプト名である。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 実行:$ php args.php a b c
var_dump($argc); // 4
var_dump($argv);
// ["args.php", "a", "b", "c"]
// 業務パラメータを取得:
$name = $argv[1] ?? 'guest';
echo "hello, $name\n";
// 全パラメータを走査:
foreach (array_slice($argv, 1) as $arg) {
echo "arg: $arg\n";
}

終了コード

exit() または die() でスクリプトを終了しステータスコードを返す。0 が成功、0 以外がエラーである。

1
2
3
4
5
6
7
8
9
10
11
12
// 正常終了(0 を返す):
exit(0);
// エラー情報付きで終了:
die("发生错误\n"); // exit と同じ
// ステータスコード付きで終了:
exit(1); // 非 0 は失敗を表す
// スクリプトの自然終了は exit(0) と等価
// CLI の成否判定:
// $ php run.php && echo "ok"

ファイル読み込み

require は必ず存在するファイルを取り込み、include は失敗時に警告のみ、_once は二重読み込みを防ぐ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 必須ファイルを読み込む:
require 'config.php';
// 任意ファイルを読み込む(失敗は警告のみ):
include 'optional.php';
// 重複読み込みを防ぐ:
require_once 'lib/functions.php';
include_once 'helper.php';
// __DIR__ で絶対パスを組み立てる:
require __DIR__ . '/../src/bootstrap.php';
// include は真偽値を返し成否を判断できる

環境チェック

php -i で設定を出力、php -m で拡張を一覧、phpinfo() で完全な情報を出力する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// コマンドラインで設定を確認:
// $ php -i
// 特定の設定項目を確認:
// $ php -i | grep memory_limit
// 読み込み済み拡張を一覧:
// $ php -m
// 拡張のインストール確認:
// $ php -m | grep mbstring
// ページに完全な情報を出力:
phpinfo();
// 本番環境では必ず無効化し,設定の漏えいを防ぐ

2.変数と定数

変数の宣言、定数、型宣言、strict モードとスコープ。

変数宣言

変数は $ で始まり、型の宣言は不要で、最初に代入した時点で作成され、名前は大文字小文字を区別する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// $ 接頭辞 + 名前,型宣言なし:
$count = 0;
$name = 'Rex';
$price = 19.99;
$active = true;
// 変数名は大文字小文字を区別:
$Foo = 1;
$foo = 2; // 別々の変数
// 未定義変数の読み取りは警告を発生:
// echo $undefined;
// 名前は数字で始められない

値コピー代入

通常の代入は値コピーであり、スカラーは独立する。配列もコピーされるが、内部でコピーオンライトにより最適化される。

1
2
3
4
5
6
7
8
9
10
11
12
13
// スカラーの代入は互いに影響しない:
$a = 5;
$b = $a;
$b = 10;
echo $a; // 依然 5
// 配列の代入もコピー:
$arr1 = [1, 2, 3];
$arr2 = $arr1;
$arr2[0] = 99;
echo $arr1[0]; // 1(影響なし)
// 参照代入は &,ポインタの節を参照

定数

const は名前空間の定数を定義し、define() はグローバル定数を定義する。定義後に変更はできない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 名前空間レベルの定数:
const MAX_SIZE = 1024;
// グローバル定数:
define('APP_NAME', 'demo');
// 定数は式で使える:
$half = MAX_SIZE / 2;
// 定義済みか判定:
defined('APP_NAME'); // true
// 動的に定数名を読み取る:
constant('MAX_SIZE'); // 1024
// 定数名は全て大文字の慣習

マジック定数

__DIR__、__FILE__、__LINE__ などのコンパイル時定数は対応する位置で評価され、ログ出力によく使われる。

1
2
3
4
5
6
7
8
9
10
11
echo __DIR__; // ファイルがあるディレクトリ
echo __FILE__; // ファイルの完全パス
echo __LINE__; // 現在の行番号
echo __FUNCTION__; // 現在の関数名
echo __CLASS__; // 現在のクラス名
echo __NAMESPACE__; // 現在の名前空間
// ログの記録位置:
error_log(__FILE__ . ':' . __LINE__);
// __DIR__ でのパス結合が最も安全

スコープと static

関数内の変数はデフォルトでローカルであり、global でグローバル変数を参照し、static で複数呼び出し間で値を保持する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$global_count = 0;
function countUp(): void
{
static $n = 0; // 呼び出し間で保持
$n++;
echo $n;
}
// 関数内でグローバルにアクセス:
function readGlobal(): int
{
global $global_count;
return $global_count;
}
countUp(); // 1
countUp(); // 2

引数と戻り値の型

引数と戻り値には型を宣言でき、弱いモードでは自動変換され、strict_types と組み合わせると厳格になる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 引数と戻り値の型:
function add(int $a, int $b): int
{
return $a + $b;
}
// 型を nullable に ?int:
function find(?int $id): ?array
{
return $id === null ? null : [$id];
}
// ユニオン型 int|float:
function area(int|float $w): float
{
return (float) $w * 2;
}

strict モード

declare(strict_types=1) で型を厳密に検証し、一致しなければ直接 TypeError をスローする。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 必ずファイルの先頭に書く:
declare(strict_types=1);
function add(int $a, int $b): int
{
return $a + $b;
}
add(1, 2); // 3
// add('1', 2); // 厳密モードは TypeError を投げる
// 弱モードでは '1' が自動的に 1 になる
// ファイルごとに宣言,ファイルをまたぐ継承はない

命名規約

クラスと名前空間はパスカルケース、関数と変数はキャメルケース、定数は全大文字で、PSR-12 に従う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// クラス名は大キャメルケース:
class UserProfile {}
// 関数と変数は小キャメルケース:
function getUserName() {}
$userName = 'Rex';
// 定数は全て大文字:
const MAX_ITEMS = 100;
// プライベートメンバーはアンダースコア可:
class Demo
{
private $privateData;
}
// PSR-12:クラスは大キャメル,メソッドは小キャメル

3.データ型

スカラー、配列、null、列挙と型システムを理解し、暗黙の変換と型判定を学ぶ。

スカラー型

int、float、string、bool の 4 種類のスカラー型がある。文字列はバイト列であり、中国語などはマルチバイトとして扱う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$age = 30; // int
$pi = 3.14; // float
$ok = true; // bool
$name = 'Rex'; // string
// 整数のオーバーフローは自動的に浮動小数点へ:
$big = PHP_INT_MAX + 1; // float
// 文字列はバイト列:
$s = 'Hello, 世界';
// 明示的な型変換:
(int) '42'; // 42
(string) 123; // '123'

配列

PHP の配列は順序付きマップであり、整数キーと文字列キーを持ち、リストと辞書を兼ねる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// リスト:
$colors = ['red', 'green', 'blue'];
// 連想配列:
$user = ['name' => 'Rex', 'age' => 5];
// 混在キー:
$mixed = [1, 'a', true];
// 要素を追加:
$colors[] = 'yellow';
// 長さを取得:
count($colors); // 4
// キーの順序は挿入順

null と空の値

null は値がないことを表す。未代入の変数、明示的な null、関数が返す null はいずれも null である。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$value = null; // 明示的な null
// null かどうか判定:
$value === null; // true
is_null($value); // true
// nullable の引数型:
function greet(?string $name): void
{
echo $name ?? 'guest';
}
// ?? でデフォルト値を提供:
$name = $input['name'] ?? 'guest';

callable と iterable

callable は呼び出し可能を表し、iterable は反復可能な配列または Traversable オブジェクトを表す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// iterable 引数は配列かオブジェクトを受け取れる:
function sumAll(iterable $items): int
{
$total = 0;
foreach ($items as $n) {
$total += $n;
}
return $total;
}
sumAll([1, 2, 3]); // 6
// callable は関数名かクロージャを受け取る:
function apply(callable $fn, int $x): int
{
return $fn($x);
}
apply(fn($x) => $x * 2, 5); // 10

mixed と void

mixed は任意の型を表し、void は戻り値なし、never は決して戻らないことを表す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// mixed:任意の型:
function debug(mixed $v): void
{
var_dump($v);
}
// void:戻り値なし:
function logMsg(string $msg): void
{
echo $msg . "\n";
}
// never:決して返らない:
function abort(): never
{
throw new \RuntimeException('终止');
}
// never 関数は例外を投げるか exit が必要

列挙 enum

enum(8.1+)で一連の名前付き定数を定義する。純粋な列挙もしくは値を持つ列挙で、メソッドを追加できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 純粋な列挙型:
enum Color
{
case Red;
case Green;
case Blue;
}
// 値を伴う列挙型:
enum Status: string
{
case Ok = 'ok';
case NotFound = '404';
}
// 値の取得と比較:
$s = Status::Ok;
$s->value; // 'ok'
$s === Status::Ok; // true
// match と組み合わせて安全にマッチ

ユニオンと交差型

int|float はそのいずれかを表し、交差型は複数のインターフェース制約を同時に満たすことを要求する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ユニオン型(8.0+):
function format(int|float $n): string
{
return number_format((float) $n, 2);
}
// nullable の短縮 ?T は T|null と等価:
function maybe(?string $s): string
{
return $s ?? '空';
}
// 交差型(8.1+):
function dump(\Countable&\Traversable $c): void
{
echo count($c);
}
// DNF 型 8.2+:(A&B)|null

暗黙の型変換

弱いモードでは演算時に自動的に型が変換され、文字列と数値の比較規則は特殊なので FAQ を参照すること。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 文字列と数値の演算は自動変換:
$n = '3' + 2; // 5
$n = '3' * '2'; // 6
// 文字列を数値に強制変換:
(int) '42px'; // 42
(float) '3.5.9'; // 3.5
// 真偽値への強制変換:
(bool) '0'; // false
(bool) '0.0'; // true('0' 以外)
// 厳密モードではこれらはエラーになる

型判定関数

is_int、is_string などの is_* シリーズで型を判定し、真偽値を返す。

1
2
3
4
5
6
7
8
9
10
11
12
is_int(3); // true
is_float(3.0); // true
is_string('x'); // true
is_bool(true); // true
is_array([1]); // true
is_null(null); // true
is_numeric('42'); // true(数値に変換可)
is_callable('strlen'); // true
// 緩い判定は is_numeric:
is_int('42'); // false
is_numeric('42'); // true

4.参照とメモリセマンティクス

PHP には生のポインターはない。変数参照、コピーオンライト、オブジェクトハンドル、null 処理について。

参照とは何か

PHP の参照は変数の別名であり、2 つの名前が同じ内容を指し、一方を変更すれば他方も変更される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 参照代入で,$b が $a の別名になる:
$a = 1;
$b = &$a;
$b = 2;
echo $a; // 2(一緒に変わる)
// 配列要素も参照にできる:
$arr = [1, 2];
$last = &$arr[1];
$last = 99;
echo $arr[1]; // 99
// 参照はアドレスポインタではない
// アドレス取得やポインタ演算はできない

コピーオンライト

配列とオブジェクトの代入ではまずデータが共有され、実際に書き込まれた時点でコピーされ、メモリを節約する。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 代入はまず共有し,すぐにはコピーしない:
$a = [1, 2, 3];
$b = $a;
echo $b[0]; // 1,読み取りのみコピーなし
// 書き込み時にコピーが発生:
$b[0] = 99;
echo $a[0]; // 1(影響を受けない)
// 大きな配列の代入は安い:
// 修正したときだけコピーのコスト
// foreach 中の修正は別配列を推奨

参照の解除

unset は現在の名前の参照のみを解除し、他の別名が指す内容には影響しない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$a = 'original';
$b = &$a;
// unset は $b の参照だけを外す:
unset($b);
echo $a; // original(残っている)
// 配列要素間の参照:
$data = [1, 2];
$alias = &$data[0];
$data[0] = 100;
echo $alias; // 100
// 参照カウントを観察:
// debug_zval_dump($a)

参照引数

引数の前に & を付けると参照渡しとなり、関数内での変更が呼び出し元の変数に反映される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 参照引数で元の変数を変更:
function increment(int &$n): void
{
$n++;
}
$x = 5;
increment($x);
echo $x; // 6
// 戻り値とともに状態を返す:
function parse(string $s, &$ok): int
{
$ok = is_numeric($s);
return (int) $s;
}
parse('42', $valid); // $valid が true になる

オブジェクトハンドル

オブジェクト変数はハンドルを保持し、代入や引数渡しでは同じインスタンスを指す。複製するには clone を使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Counter
{
public int $n = 0;
}
$c1 = new Counter();
$c2 = $c1; // 同じインスタンス
$c2->n = 5;
echo $c1->n; // 5(共有)
// 独立したコピーは clone:
$c3 = clone $c1;
$c3->n = 0;
echo $c1->n; // まだ 5
// === で同じインスタンスか判定:
$c1 === $c2; // true

null 処理

isset は存在し null でないことを判定し、is_null は null であることを判定し、empty は空であることを判定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$arr = ['a' => null, 'b' => 1];
// isset:存在し null でない:
isset($arr['a']); // false(値は null)
isset($arr['b']); // true
isset($arr['x']); // false(存在しない)
// is_null:null かどうか:
is_null($arr['a']); // true
// 存在しないキーへの直接アクセスは警告:
// $v = $arr['x'];
// ?? で安全に読み取る:
$v = $arr['x'] ?? '默认';

null 合体演算子 ??

?? は左辺が存在しないか null の場合に右辺を返し、連鎖可能で、throw と組み合わせることも可能である。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 存在しない可能性のあるキーを読み取る:
$name = $_GET['name'] ?? 'guest';
// 連鎖する null 合体:
$config = $a ?? $b ?? 'default';
// throw との組み合わせ(8.0+):
$value = $input['key'] ?? throw new \InvalidArgumentException('缺 key');
// 未定義キーの警告を出さない:
// 直接の添字アクセスだけが警告を出す
// 代入演算子 ??=(8.0+)

可変変数

$$var は変数の値を別の変数名として使う。可読性が低いため、配列の使用を推奨する。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 可変変数:
$name = 'foo';
$foo = 42;
echo $$name; // 42(つまり $foo)
// 配列方式の方が明確:
$values = ['foo' => 42];
echo $values[$name]; // 42
// ネストした可変変数は紛らわしい:
// $$$name はほぼ読めない
// 連想配列での代替を推奨

unset とメモリ

unset で名前を解放し参照カウントを減少させ、0 になるとメモリは回収可能となり、循環参照は GC が処理する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 大きな変数を解放:
$big = str_repeat('x', 1000000);
unset($big); // すぐ回収可能
// 配列要素を解放:
$arr = [1, 2, 3];
unset($arr[1]);
// キーは並び替えられず,値は null になる
// 循環参照:
class Node
{
public ?Node $next = null;
}
$a = new Node();
$b = new Node();
$a->next = $b;
$b->next = $a; // 環を形成
unset($a, $b);
// 循環ガベージコレクタが掃除

5.制御フロー

条件分岐、ループ、switch、match、そして break/continue。

if / else

if で条件を判定して分岐を実行し、elseif で順次判定し、else でフォールバックする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$n = 0;
if ($n > 0) {
echo "正数";
} elseif ($n === 0) {
echo "零";
} else {
echo "负数";
}
// 単行の短縮形:
if ($n > 0) echo "正数";
// return で早く抜ける:
// ネストの深さを減らす

三項演算子

条件 ? 真の値 : 偽の値 は式であり代入できる。ネストは可読性を損なう。

1
2
3
4
5
6
7
8
9
10
$score = 85;
$grade = $score >= 90 ? 'A' : 'B';
echo $grade; // B
// ?: と ?? の違い:
$x = $a ?: '默认'; // $a が偽のときデフォルト
$y = $a ?? '默认'; // $a が null のときデフォルト
// ネストした三項演算子は可読性が低い:
// if か match に分解を推奨

switch 文

switch はデフォルトで緩い比較を行い、マッチ後に break しないとフォールスルーし、default がフォールバックとなる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$status = 200;
switch ($status) {
case 200:
case 201:
echo "成功";
break;
case 404:
echo "未找到";
break;
default:
echo "其它";
}
// case '0' は 0 に緩くマッチするので注意
// 複雑な判定は match を推奨

match 式

match(8.0+)は厳密比較で値を返し、分岐が自動的に戻り、break は不要である。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$status = 404;
$label = match ($status) {
200, 201 => '成功',
404, 410 => '未找到',
default => '其它',
};
echo $label; // 未找到
// default がなくマッチしないと例外を投げる
// UnhandledMatchError 例外
// match true で条件判定:
$result = match (true) {
$score >= 90 => 'A',
$score >= 80 => 'B',
default => 'C',
};

for ループ

for(初期化; 条件; ステップ) は古典的なカウンタループであり、ステップは自由に制御できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for ($i = 0; $i < 3; $i++) {
echo "$i ";
} // 0 1 2
// 減算ステップ:
for ($i = 10; $i >= 0; $i -= 2) {
echo "$i ";
} // 10 8 6 4 2 0
// 多重ループ:
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 2; $j++) {
echo "$i$j ";
}
}

foreach での反復

foreach は配列とオブジェクトを反復処理し、キーと値を同時に取得でき、参照での変更には残存に注意が必要である。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$items = ['a' => 1, 'b' => 2];
// 値だけ取り出す:
foreach ($items as $v) {
echo "$v ";
} // 1 2
// キーと値を取り出す:
foreach ($items as $k => $v) {
echo "$k=$v ";
} // a=1 b=2
// 参照で要素を変更:
foreach ($items as &$v) {
$v *= 2;
}
unset($v); // 重要!参照を外す

while と do-while

while は先に条件を判定するため一度も実行されないことがあり、do-while は必ず一度は実行される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// while は先に判定:
$n = 0;
while ($n < 3) {
echo $n++;
} // 012
// do-while は最低 1 回:
$i = 10;
do {
echo $i;
} while ($i < 3); // 10
// ファイルを 1 行ずつ読む定番の書き方:
$h = fopen('log.txt', 'r');
while (($line = fgets($h)) !== false) {
echo $line;
}
fclose($h);

break と continue

break はループや switch を抜け、continue は次の繰り返しへ進む。数値を付ければ複数階層を抜けられる。

1
2
3
4
5
6
7
8
9
10
11
12
13
for ($i = 0; $i < 10; $i++) {
if ($i % 2 === 0) continue; // 偶数をスキップ
if ($i > 5) break; // 途中で終了
echo "$i ";
} // 1 3 5
// 2 重ループを抜ける:
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
if ($j === 1) break 2;
echo "$i$j ";
}
} // 00 10

6.関数とクロージャ

関数定義、デフォルト引数、可変長引数、名前付き引数、クロージャとジェネレータ。

関数定義

function で関数を定義し、引数に型を付け、return で値を返す。関数名は大文字小文字を区別しない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function add(int $a, int $b): int
{
return $a + $b;
}
// 呼び出し:
add(1, 2); // 3
// 関数名は大文字小文字を区別しない:
ADD(1, 2); // 3
// 先に呼んで後から定義してもよい:
sayHi();
function sayHi(): void
{
echo "hi\n";
}
// 同名関数の重複定義は致命的エラー

デフォルト引数

引数にはデフォルト値を指定でき、呼び出し時に省略できる。デフォルト値は定数式でなければならない。

1
2
3
4
5
6
7
8
9
10
11
12
13
function greet(string $name, string $title = 'Mr.'): string
{
return "$title $name";
}
greet('Nick'); // Mr. Nick
greet('Anna', 'Ms.'); // Ms. Anna
// デフォルト値は定数式である必要がある:
// 関数呼び出しや変数は書けない
// 任意引数は必須引数の後ろに置く:
// function f($a, $b = 1, $c = 2)

可変長引数

...$args は残りの引数をすべて配列としてまとめる。引数リストの最後に置く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sum(int ...$nums): int
{
return array_sum($nums);
}
sum(1, 2, 3); // 6
sum(10, 20); // 30
sum(); // 0
// 配列を渡すには展開が必要:
$arr = [1, 2, 3];
sum(...$arr); // 6
// 型宣言は各要素を制約:
// int ...$nums で全て int を保証

名前付き引数

呼び出し時に 引数名: 値 の形で渡せる(8.0+)。省略可能な引数を飛ばせ、順序も自由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function makeProfile(
string $name,
string $email = '',
int $age = 0,
): array {
return [$name, $email, $age];
}
// email を飛ばして age に直接渡す:
makeProfile(
name: 'Rex',
age: 5,
);
// ['Rex', '', 5]
// 配列の展開と組み合わせる:
$args = ['name' => 'Rex', 'age' => 5];
makeProfile(...$args);

戻り値の型

戻り値の型はコロンの後ろに宣言する。void、?T、合併型、配列に対応する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function nothing(): void {} // 戻り値なし
function maybe(): ?int // null になり得る
{
return null;
}
function both(): int|string // ユニオン型の戻り値
{
return 'ok';
}
function shape(): array // 配列を返す
{
return ['name' => 'Rex'];
}
function never(): never // 決して返らない
{
throw new \Exception('终止');
}
// 戻り値の型が不一致なら TypeError

クロージャ

無名関数は function() で定義し、use で外側の変数を捕捉する。変数に代入したりコールバックとして渡せる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 無名関数を変数に代入:
$square = function (int $x): int {
return $x * $x;
};
$square(4); // 16
// use で値渡しキャプチャ:
$factor = 2;
$mul = function ($x) use ($factor): int {
return $x * $factor;
};
$mul(5); // 10
// use で参照キャプチャすると外部を変更できる:
$count = 0;
$inc = function () use (&$count): void {
$count++;
};
$inc();
echo $count; // 1

アロー関数

fn() => 式 は外側の変数を自動的に値で捕捉する。単一の式で簡潔に書けるため、コールバックによく使われる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$factor = 2;
$double = fn(int $x) => $x * $factor;
$double(10); // 20
// 配列コールバックがより簡潔:
$nums = [1, 2, 3, 4];
$evens = array_filter(
$nums,
fn($n) => $n % 2 === 0,
); // [2, 4]
// アロー関数は式が 1 つだけ:
// 複数ステートメントは通常のクロージャが必要
// 値キャプチャ,外部変更には use & のクロージャ

呼び出し可能オブジェクト

8.1+ では strlen(...) で first-class callable を作り、関数を第一級の値として扱える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ファーストクラス呼び出し可能(8.1+):
$len = strlen(...);
$len('hello'); // 5
// オブジェクトのメソッド:
$c = new Counter();
$inc = $c->increment(...);
$inc();
// callable 型で引数を受け取る:
function run(callable $fn): void
{
echo $fn();
}
run(fn() => 'done');
// call_user_func は旧式の書き方
// 新規コードは (...) 構文を優先

ジェネレータ

yield で値を一つずつ生成する関数をジェネレータと呼ぶ。遅延評価なので大量データの処理でメモリを節約できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function rangeGen(int $n): Generator
{
for ($i = 0; $i < $n; $i++) {
yield $i;
}
}
foreach (rangeGen(5) as $v) {
echo "$v "; // 0 1 2 3 4
}
// ジェネレータは遅延,使いながら生成:
$gen = rangeGen(3);
$gen->current(); // 0
$gen->next();
$gen->current(); // 1
// キーと値の生成:
yield 'key' => 'value';

7.文字列

文字列リテラル、変数展開、連結、マルチバイト処理とよく使う関数。

文字列リテラル

シングルクォートはそのまま出力し、ダブルクォートはエスケープと変数を解釈する。この二つのスタイルがよく使われる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// シングルクォートはそのまま出力:
echo 'Hello\n'; // エスケープしない
// ダブルクォートはエスケープを解釈:
echo "Hello\n"; // 改行
// ダブルクォート内の変数補間:
$name = 'Rex';
echo "hi $name"; // hi Rex
// シングルクォート内のシングルクォートはエスケープが必要:
echo 'it\'s';
// 複数行テキストは連結か sprintf を使う
// heredoc はインデントの影響を受けやすい

変数展開

ダブルクォート内では $var がそのまま展開される。複雑な式は波括弧を使って {$arr['k']} と書く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$name = 'Rex';
$age = 5;
// 単純な変数:
echo "姓名 $name"; // 姓名 Rex
// 配列要素は波括弧を使う:
$user = ['name' => 'Rex'];
echo "{$user['name']}"; // Rex
// オブジェクトのプロパティ:
class Dog
{
public string $name = '旺财';
}
$dog = new Dog();
echo "{$dog->name}"; // 旺财
// 複雑な式は波括弧で包む
// シンプルな場面は . で連結

文字列の連結

. は二つの文字列を連結し、.= は追記する。何度も連結する場合は implode や sprintf のほうが読みやすい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$first = 'Rex';
$last = 'Wang';
// 連結:
$full = $first . ' ' . $last;
echo $full; // Rex Wang
// 追加:
$msg = '';
$msg .= 'hello ';
$msg .= 'world';
echo $msg; // hello world
// 配列を文字列に:
implode('-', [1, 2, 3]); // 1-2-3
// 数値の連結は自動的に文字列へ:
echo '第' . 3 . '名'; // 第3名

マルチバイト関数

日本語などを扱うときは mb_ で始まる関数を使って文字数で数える。strlen はバイト数で数える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$s = 'Hello, 世界';
// バイト数 vs 文字数:
strlen($s); // 12(バイト)
mb_strlen($s); // 8(文字)
// 文字単位で切り出す:
mb_substr($s, 0, 5); // 'Hello'
// バイトで切ると中国語が壊れる
// 大文字小文字変換:
mb_strtoupper('hello');
// 文字配列に分解:
mb_str_split($s);
// 中国語の処理は必ず mb_ 系を使う

sprintf による書式化

sprintf/printf は %s や %d のプレースホルダで書式化でき、連結より読みやすい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$name = 'Rex';
$age = 5;
// フォーマット:
$line = sprintf('%s 今年 %d 岁', $name, $age);
echo $line; // Rex 今年 5 岁
// 直接出力:
printf('%.2f', 3.14159); // 3.14
// よく使うプレースホルダ:
// %s 文字列 %d 整数 %f 浮動小数
// %b 2 進数 %x 16 進数
// %-5s 左詰め %05d ゼロ埋め
// 引数の順序は入れ替え可能:
sprintf('%2$s-%1$s', 'a', 'b'); // b-a

検索と置換

strpos で位置を探し、str_contains で含まれるかを判定し、str_replace でまとめて置換する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$s = 'Hello, world!';
// 含むか判定(8.0+):
str_contains($s, 'world'); // true
// 位置を探す:
strpos($s, 'world'); // 7
strpos($s, 'xyz'); // false
// 置換:
str_replace('world', 'PHP', $s);
// 'Hello, PHP!'
// 大文字小文字を無視して置換:
str_ireplace('HELLO', 'Hi', $s);
// 位置指定で置換:
// substr_replace($s, 'X', 0, 5)

分割と結合

explode は区切り文字で配列に分割し、implode は配列を文字列に戻す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 分割:
$parts = explode(',', 'a,b,c');
// ['a', 'b', 'c']
// 個数を制限:
explode(',', 'a,b,c', 2);
// ['a', 'b,c']
// 空文字列の要素も保持:
explode(',', 'a,,c');
// ['a', '', 'c']
// 結合:
implode(' | ', ['a', 'b']);
// 'a | b'
// 空白で分割:
preg_split('/\s+/', 'a b c');
// ['a', 'b', 'c']

大文字小文字と空白除去

strtolower/strtoupper で大文字小文字を変換し、trim で前後の空白を除去する。ltrim/rtrim は片側だけを除去する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$s = ' Hello World ';
// 先頭末尾の空白を除去:
trim($s); // 'Hello World'
// 片側だけ除去:
ltrim(' hi'); // 'hi'
rtrim('hi '); // 'hi'
// 大文字小文字:
strtolower('ABC'); // 'abc'
strtoupper('abc'); // 'ABC'
ucfirst('hello'); // 'Hello'
ucwords('hello world');// 'Hello World'
// 指定文字を除去:
trim('...hi...', '.'); // 'hi'

8.配列とコレクション

配列の作成、追加・削除・更新・参照、走査、ソートと Spl データ構造。

配列の作成

[] または array() で配列を作る。配列リテラルは連番の整数キーを自動的に割り当てる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// リテラルで作成:
$list = [1, 2, 3];
// キーを指定:
$map = ['a' => 1, 'b' => 2];
// キーを省略すると自動で増加:
$list = ['x', 'y']; // キー 0,1
// 空配列:
$empty = [];
// キーを指定して追加:
$arr[5] = 'five';
$arr[] = 'six'; // キー 6
// 数字の文字列キーは自動的に整数へ

アクセスと変更

添字で要素にアクセスし、[] で追加、unset で削除する。存在しないキーを読むと警告が出る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$user = ['name' => 'Rex', 'age' => 5];
// アクセス:
echo $user['name']; // Rex
// 変更:
$user['age'] = 6;
// 追加:
$user['city'] = '北京';
// 削除:
unset($user['age']);
// 安全に読み取り警告を回避:
$age = $user['age'] ?? '未知';
// ネストアクセス:
$data['a']['b']['c'];

追加・削除と件数

array_push/array_pop は末尾を操作し、array_shift/unshift は先頭を操作する。count で件数を数える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$stack = [1, 2, 3];
// 末尾に追加:
$stack[] = 4; // 推奨
array_push($stack, 5, 6);
// 末尾をポップ:
$last = array_pop($stack); // 6
// 先頭をシフト:
$first = array_shift($stack); // 1
// 先頭に挿入:
array_unshift($stack, 0);
// カウント:
count($stack);
// 存在の判定:
in_array(2, $stack); // true

配列の走査

foreach で値またはキーと値の組を走査する。リスト分解を使えば二次元配列も走査できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$users = [
['name' => 'Rex', 'age' => 5],
['name' => 'Anna', 'age' => 30],
];
// 値だけ取り出す:
foreach ($users as $u) {
echo $u['name'];
}
// キーと値を取り出す:
foreach ($users as $i => $u) {
echo "$i: {$u['name']}";
}
// リストの分解:
foreach ($users as ['name' => $n]) {
echo $n;
}
// while+each は旧式,使わない

マップとフィルタ

array_map は要素ごとに変換し、array_filter はコールバックで絞り込み、array_reduce は畳み込む。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$nums = [1, 2, 3, 4];
// マップ:
$doubled = array_map(
fn($n) => $n * 2,
$nums,
); // [2, 4, 6, 8]
// フィルタ:
$evens = array_filter(
$nums,
fn($n) => $n % 2 === 0,
); // [2, 4]
// 畳み込み:
$sum = array_reduce(
$nums,
fn($acc, $n) => $acc + $n,
0,
); // 10
// 複数配列を同時にマップ:
array_map(fn($a, $b) => $a + $b, [1, 2], [3, 4]);

ソート

sort は値を並べ替え、ksort はキー順、asort は値順でキーの対応を保ち、usort は比較を自作する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$nums = [3, 1, 4, 1, 5];
// 値でソート(キーを作り直す):
sort($nums); // [1, 1, 3, 4, 5]
rsort($nums); // 降順
// キーを保持してソート:
$ages = ['a' => 30, 'b' => 20];
asort($ages); // 値の昇順,キー保持
ksort($ages); // キーでソート
// カスタム比較:
usort(
$users,
fn($x, $y) => $x['age'] <=> $y['age'],
);
// 多次元の列でソート:
array_multisort($ages, SORT_DESC);

結合とスライス

array_merge は配列を結合し、array_slice は部分配列を取り出し、array_combine はキーと値を組み合わせる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 結合(文字列キーは後者が上書き):
$a = ['a' => 1, 'b' => 2];
$b = ['b' => 3, 'c' => 4];
$m = array_merge($a, $b);
// ['a' => 1, 'b' => 3, 'c' => 4]
// 数値キーの結合は並び替わる:
array_merge([1], [2]); // [1, 2]
// 部分配列を取り出す:
$nums = [1, 2, 3, 4, 5];
array_slice($nums, 1, 2); // [2, 3]
// キーと値を組み合わせる:
array_combine(['a', 'b'], [1, 2]);
// ['a' => 1, 'b' => 2]
// 重複を除去:
array_unique([1, 1, 2]); // [1, 2]

分割代入

リスト形式の [] やキー指定の [] による分解で、配列の要素を複数の変数にまとめて代入できる。foreach の中でも使える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// リストの分解:
[$a, $b] = [1, 2];
echo $a; // 1
// キーを指定して分解:
[0 => $x, 2 => $z] = [1, 2, 3];
echo $x; // 1
// キーと値の分解:
['name' => $n] = ['name' => 'Rex'];
echo $n; // Rex
// 残りを収集:
[$first, ...$rest] = [1, 2, 3];
// $rest = [2, 3]
// 関数の戻り値配列を分解:
[$min, $max] = minMax($nums);

Spl データ構造

SplStack、SplQueue、SplFixedArray は用途を絞ったデータ構造を提供する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// スタック(LIFO):
$stack = new SplStack();
$stack->push('a');
$stack->push('b');
$stack->pop(); // 'b'
// キュー(FIFO):
$queue = new SplQueue();
$queue->enqueue('a');
$queue->enqueue('b');
$queue->dequeue(); // 'a'
// 固定サイズ配列:
$fixed = new SplFixedArray(3);
$fixed[0] = 'hi';
$fixed->setSize(5);
// オブジェクト集合:
$store = new SplObjectStorage();
$store->attach($obj, 'meta');
$store->contains($obj);

9.メモリと性能

参照カウント、循環参照の GC、WeakReference とメモリ計測。

参照カウント

zval が参照数を記録し、ゼロになった時点で即座に解放される。代入や引数渡しでカウントが増える。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 各変数は参照カウントを持つ:
$a = 'hello'; // カウント 1
$b = $a; // カウント 2
unset($a); // カウント 1
unset($b); // カウント 0,回収
// コピーオンライト:書き込み時だけコピー
// カウントを観察(xdebug 必要):
// xdebug_debug_zval('a')

循環参照の GC

オブジェクトが互いに参照し合うと参照カウントがゼロにならないため、循環参照用のガベージコレクタが定期的に回収する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Node
{
public ?Node $next = null;
}
$a = new Node();
$b = new Node();
$a->next = $b;
$b->next = $a; // 環を形成
unset($a, $b);
// 参照カウントは 1 のまま,ゼロにならない
// GC バッファが満杯で自動スキャン:
gc_collect_cycles(); // 手動回収
// 長期オブジェクトは積極的にリンク解除:
// $a->next = null;

WeakReference 弱参照

WeakReference(8.0+)はオブジェクトを保持しつつ回収を妨げないため、キャッシュなどの用途に向く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Cache
{
public array $data = [];
}
$obj = new Cache();
// 弱参照は強参照カウントを増やさない:
$weak = WeakReference::create($obj);
$weak->get(); // Cache インスタンス
unset($obj);
// オブジェクトが回収された後:
$weak->get(); // null
// 弱マップ 8.4+:WeakMap
// オブジェクトに付加データを紐付けるのに適する

メモリ計測

memory_get_usage で現在の使用量を、memory_get_peak_usage でピーク値を確認する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 現在のメモリ使用量(バイト):
$used = memory_get_usage();
// ピーク使用量:
$peak = memory_get_peak_usage();
// MB 表示に変換:
printf('%.2f MB', $peak / 1048576);
// メモリ上限を変更:
// ini_set('memory_limit', '512M');
// CLI 起動時に指定:
// $ php -d memory_limit=1G script.php
// 大きな配列は処理後にすぐ unset

早めの解放

大きな変数は使い終わったら unset で参照カウントをゼロにするとピークが下がり、バッチ処理が安定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 大きな配列は処理後に解放:
$lines = file('big.log');
unset($lines);
// ループ内で分割処理:
$result = [];
for ($i = 0; $i < 1000; $i++) {
$result[] = str_repeat('x', 100000);
if ($i % 100 === 99) {
// 分割して書き込み後クリア:
$result = [];
}
}
// オブジェクト参照を null にして回収を促す:
// $obj = null;

OPcache キャッシュ

OPcache はコンパイル済みのバイトコードをキャッシュし、リクエストごとの再コンパイルを防ぐ。本番では必ず有効にする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 有効化を確認:
// $ php -m | grep opcache
// php.ini の推奨設定:
// opcache.enable=1
// opcache.memory_consumption=128
// opcache.max_accelerated_files=10000
// CLI ではデフォルトで有効でない:
// $ php -d opcache.enable=1 script.php
// バイトコードをキャッシュし,コンパイルを省略:
// 高トラフィックページのスループットが大幅に向上
// 開発環境では一時的に無効化可

大規模データの処理

巨大な配列はメモリを圧迫するため、ジェネレータやストリーム読み込み、SplFixedArray で使用量を抑える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ジェネレータで全体配列を代替:
function readLines(string $file): Generator
{
$h = fopen($file, 'r');
while (($line = fgets($h)) !== false) {
yield rtrim($line);
}
fclose($h);
}
// 読みながら処理,メモリ一定:
foreach (readLines('big.log') as $line) {
process($line);
}
// 大量の整数は SplFixedArray:
$fixed = new SplFixedArray(1000000);
// 大きなファイルを一度に読み込むのを避ける

性能の要点

ループ内での文字列連結を避け、事前に確保し、接続を使い回し、無駄なクエリを減らす。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 連結はループ内の累加を避ける:
$parts = [];
foreach ($items as $it) {
$parts[] = $it->name;
}
$result = implode(',', $parts);
// 接続オブジェクトを再利用:
$pdo = new PDO($dsn); // シングルトンで共有
// ループ外でカウントを取る:
$count = count($list);
// データベースへの重複クエリを避ける:
// 1 回だけ取得し,メモリでフィルタ
// opcache とバッファリングを組み合わせる
// ホットパスはシンプルに保つ

10.オブジェクト指向

クラス、コンストラクタ、可視性、継承、インターフェース、trait と読み取り専用プロパティ。

クラス定義

class でクラスを定義し、プロパティに型を付け、メソッドをクラス内に宣言する。new でインスタンスを生成する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User
{
public string $name = '';
public int $age = 0;
public function describe(): string
{
return "{$this->name}, {$this->age}";
}
}
$u = new User();
$u->name = 'Rex';
$u->age = 5;
echo $u->describe(); // Rex, 5
// プロパティは宣言時に初期化が必要
// またはコンストラクタで代入

コンストラクタとプロパティ昇格

__construct はオブジェクトを初期化するコンストラクタ。8.0+ ではコンストラクタ引数をそのままプロパティに昇格できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 従来の書き方:
class OldUser
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
// プロパティ昇格(8.0+):
class User
{
public function __construct(
public string $name,
public int $age,
) {}
}
$u = new User('Rex', 5);
echo $u->name; // Rex
// 昇格した引数は自動的にプロパティになる

可視性

public は公開、protected はサブクラスから参照可能、private は自クラスのみ。既定は public。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BankAccount
{
public string $owner = '';
protected float $balance = 0;
private string $secret = 'xx';
public function deposit(float $n): void
{
$this->balance += $n;
}
}
class Child extends BankAccount
{
public function peek(): float
{
return $this->balance; // protected はアクセス可
}
}
$acc = new BankAccount();
$acc->owner = 'Rex';
// $acc->balance = 1; // アクセス不可

継承

extends で親クラスを継承し、メソッドをオーバーライドし、parent:: で親クラスのメソッドを呼ぶ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Animal
{
public function __construct(
protected string $name,
) {}
public function speak(): string
{
return "$this->name: ...";
}
}
class Dog extends Animal
{
public function speak(): string
{
return parent::speak() . ' 汪汪';
}
}
$dog = new Dog('旺财');
echo $dog->speak(); // 旺财: ... 汪汪
// サブクラスは親クラスのメソッドとプロパティを上書き
// final メソッドは上書き不可

インターフェース

`interface` はメソッドのシグネチャを定義し、`implements` で実装する。クラスは複数のインターフェースを実装できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface Speakable
{
public function speak(): string;
}
interface Runnable
{
public function run(): void;
}
// 複数のインターフェースを実装:
class Robot implements Speakable, Runnable
{
public function speak(): string
{
return 'beep';
}
public function run(): void
{
echo "running\n";
}
}
// インターフェースはインターフェースを継承できる:
// interface X extends Speakable {}
// インターフェースに依存して差し替えやすく

抽象クラス

`abstract class` はインスタンス化できず、抽象メソッドはサブクラスで実装する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
abstract class Shape
{
abstract public function area(): float;
public function describe(): string
{
return '面积: ' . $this->area();
}
}
class Circle extends Shape
{
public function __construct(
private float $r,
) {}
public function area(): float
{
return 3.14159 * $this->r * $this->r;
}
}
$c = new Circle(2);
echo $c->describe(); // 面积: 12.56636

trait による再利用

`trait` は複数のクラスにメソッドを横方向に再利用し、`use` で取り込む。複数の trait を組み合わせることもできる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
trait Loggable
{
public function log(string $msg): void
{
echo "[LOG] $msg\n";
}
}
trait Timestampable
{
public function createdAt(): string
{
return date('Y-m-d H:i:s');
}
}
class Order
{
use Loggable, Timestampable;
public function create(): void
{
$this->log('创建订单');
}
}
$order = new Order();
$order->create(); // [LOG] 创建订单
// クラスのメソッドは trait より優先

静的メンバー

`static` プロパティとメソッドはインスタンスではなくクラスに属し、`self::` または `クラス名::` でアクセスする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Counter
{
private static int $total = 0;
public static function hit(): void
{
self::$total++;
}
public static function get(): int
{
return self::$total;
}
}
Counter::hit();
Counter::hit();
echo Counter::get(); // 2
// 静的メソッド内には $this がない:
// 非静的メンバーにはアクセス不可
// 静的プロパティは全インスタンスで共有
// 遅延静的束縛は static:: を使う

読み取り専用プロパティ

`readonly` プロパティ(8.1+)は宣言時とコンストラクタで 1 回だけ代入でき、その後は読み取り専用となる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Point
{
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
}
$p = new Point(1.0, 2.0);
echo $p->x; // 1
// $p->x = 3; // 読み取り専用エラー
// 読み取り専用クラス(8.2+):
readonly class Config
{
public function __construct(
public string $dsn,
) {}
}
// 値オブジェクトと不変設定に適する
// プロパティ昇格 + readonly がよく使われる

11.エラー処理

例外の階層、try/catch/finally、独自の例外とエラー処理。

try / catch

`try` ブロックで投げられた例外は `catch` で捕捉され、捕捉後はプログラムの実行が継続する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
$n = 1 / 0; // 8.0+ は DivisionByZeroError を投げる
} catch (\DivisionByZeroError $e) {
echo '除数不能为零';
}
// 例外を捕捉:
try {
throw new \RuntimeException('出错了');
} catch (\RuntimeException $e) {
echo $e->getMessage(); // 出错了
}
// 捕捉されない例外は致命的エラーになる
// スクリプトが終了し,エラーコード 255

Throwable の階層

`Throwable` は基底クラスで、`Error` はエンジン側のエラー、`Exception` はプログラム側の例外を表す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Throwable
// ├── Error(エンジン級)
// │ ├── TypeError
// │ ├── ValueError
// │ └── DivisionByZeroError
// └── Exception(プログラム級)
// ├── LogicException
// └── RuntimeException
// 任意のエラー/例外を捕捉:
try {
risky();
} catch (\Throwable $e) {
echo $e->getMessage();
}
// Error も Exception も Throwable を実装
// それぞれ catch して精密に処理できる

独自の例外

`Exception` または `RuntimeException` を継承して業務例外を定義し、追加フィールドを保持できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ValidationException extends \RuntimeException
{
public function __construct(
public readonly string $field,
string $message = '',
) {
parent::__construct(
$message ?: "字段 {$field} 无效",
);
}
}
// 投げる:
throw new ValidationException('email');
// 捕捉時にフィールドを取得:
try {
validate($input);
} catch (ValidationException $e) {
echo $e->field; // email
echo $e->getMessage();
}

多重キャッチ

複数の例外型を `catch` するには `|` で区切る。上から順にマッチングされるため、サブクラスを親クラスより先に書く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
parse($input);
} catch (\InvalidArgumentException | \LengthException $e) {
// 2 種類の例外を同様に処理
echo $e->getMessage();
} catch (\Exception $e) {
// その他の例外は個別に処理
logError($e);
}
// マッチの順序は上から下:
// 具体的な例外を先に,親クラスを後に
// 8.0+ は変数名を省略可能:
// catch (\Exception) { ... }

finally

`finally` ブロックは例外の有無に関わらず実行され、リソースの解放や接続のクローズに使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function process(): string
{
$conn = openConnection();
try {
return doWork($conn);
} finally {
$conn->close(); // 必ず実行
}
}
// return でも先に finally が実行される
// ファイルハンドルを解放:
$h = fopen('a.txt', 'r');
try {
// 処理
} finally {
fclose($h);
}
// finally だけ使い,例外を捕捉しなくてもよい

グローバル例外ハンドラ

`set_exception_handler` は未捕捉例外の受け皿となり、ログとレスポンスを一元化する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set_exception_handler(function (\Throwable $e): void {
// ログを記録:
error_log($e->getMessage());
// 500 を返す:
http_response_code(500);
echo '服务器开小差了';
});
// 以降の未捕捉例外はすべてここを通る
// デフォルトのハンドラに戻す:
restore_exception_handler();
// set_error_handler は非例外エラーを処理
// 8.0+ では大半のエラーも例外

Error と Exception

エンジンのエラー(型/引数)は `Error`、業務上の例外は `Exception` であり、別々に捕捉する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// TypeError:型の不一致:
function add(int $a, int $b): int
{
return $a + $b;
}
try {
add('a', 'b'); // TypeError を投げる
} catch (\TypeError $e) {
echo '参数类型错误';
}
// ValueError:値が不正:
try {
array_chunk([1, 2], 0); // ValueError を投げる
} catch (\ValueError $e) {
echo '长度必须为正';
}
// エンジンのエラーは多くが Error を継承
// Exception と分けて捕捉

例外チェーン

`catch` 内で新しい例外を投げるとき、元の例外を第 3 引数に渡すと `getPrevious` で遡れる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try {
$data = fetchFromApi();
} catch (\HttpException $e) {
throw new \RuntimeException(
'获取数据失败',
0,
$e, // 直前の例外
);
}
// 原因を遡る:
try {
loadAll();
} catch (\RuntimeException $e) {
$prev = $e->getPrevious();
if ($prev) {
echo $prev->getMessage();
}
}
// ログに完全なコールスタックを記録:
// error_log($e->getTraceAsString());

12.ファイル I/O

ファイルの読み書き、JSON、CSV、標準ストリーム。

ファイルの読み込み

`file_get_contents` はファイル全体を一つの文字列として読み込む。小さなファイルに向いており、失敗時は `false` を返す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ファイル全体を読む:
$text = file_get_contents('data.txt');
if ($text === false) {
echo '读取失败';
}
// 配列として読む(各行 1 要素):
$lines = file('log.txt');
// 改行と空行を無視:
$lines = file(
'log.txt',
FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES,
);
// リモート読み取り(allow_url_fopen 必要):
$json = file_get_contents('https://example.com/api');

ファイルの書き込み

`file_put_contents` は文字列を書き込み、追記には `FILE_APPEND` を指定する。書き込みバイト数を返す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 上書き書き込み:
$n = file_put_contents('out.txt', 'Hello!');
// 追記モード:
file_put_contents('log.txt', "更多\n", FILE_APPEND);
// 配列を書き込む:
$lines = ['a', 'b'];
file_put_contents('list.txt', implode("\n", $lines));
// 書き込み失敗を確認:
if (file_put_contents('x.txt', 'data') === false) {
echo '写入失败';
}
// 権限の問題は false を返す

行単位の読み込み

`fopen` と `fgets` を組み合わせて行単位で読み込む。メモリ使用量が一定のため、大規模ファイルに適する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$h = fopen('big.log', 'r');
if ($h === false) {
exit('无法打开文件');
}
while (($line = fgets($h)) !== false) {
// 行末の改行を除去:
$line = rtrim($line, "\r\n");
process($line);
}
fclose($h);
// ジェネレータで包むと扱いやすい:
// function lines(string $f): Generator
// 大きなファイルはストリーム処理し,一括読み込みしない

オープンモード

`fopen` の `r` / `w` / `a` などのモードが読み書きの挙動を制御し、`w` は既存の内容を消去する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// r:読み取り専用,ファイル必須
$h = fopen('a.txt', 'r');
// r+:読み書き,ファイル必須
$h = fopen('a.txt', 'r+');
// w:書き込み,空にするか作成
$h = fopen('a.txt', 'w');
// a:追記,末尾に書く
$h = fopen('a.txt', 'a');
// b:バイナリモード(Windows で必要)
$h = fopen('img.bin', 'wb');
// 書き終えたら fclose でハンドルを解放
// ハンドルリークは長時間プロセスで蓄積する

JSON のエンコード/デコード

`json_encode` で JSON に変換し、`json_decode` で解析する。第 2 引数に `true` を指定すると配列として取得できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$data = ['name' => 'Rex', 'age' => 5];
// エンコード:
$json = json_encode($data);
// {"name":"Rex","age":5}
// 整形と中国語をエスケープしない:
json_encode(
$data,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE,
);
// 配列として解析:
$arr = json_decode($json, true);
// オブジェクトとして解析:
$obj = json_decode($json);
// エラーを確認:
if (json_last_error() !== JSON_ERROR_NONE) {
echo json_last_error_msg();
}

CSV の読み書き

`fgetcsv` は 1 行の CSV を解析し、`fputcsv` は 1 行を書き出す。引用符やエスケープは自動で処理される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 読み取り:
$h = fopen('data.csv', 'r');
$headers = fgetcsv($h);
while (($row = fgetcsv($h)) !== false) {
$item = array_combine($headers, $row);
echo $item['name'];
}
fclose($h);
// 書き込み:
$h = fopen('out.csv', 'w');
fputcsv($h, ['name', 'age']);
fputcsv($h, ['Rex', 5]);
fclose($h);
// カンマ/クォートは自動エスケープ
// 区切り文字をカスタム:fgetcsv($h, 0, ';')

ディレクトリと glob

`glob` はパターンでファイルをマッチし、`scandir` はディレクトリを列挙し、`mkdir` はディレクトリを作成する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ワイルドカードでファイルをマッチ:
foreach (glob('data/*.csv') as $file) {
echo $file;
}
// ディレクトリを列挙:
$items = scandir('.');
// 型を判定:
is_dir('src'); // true
is_file('a.txt'); // true
// ディレクトリを作成(親も含む):
mkdir('logs/2026', 0755, true);
// ファイルを削除:
unlink('tmp.txt');
// 存在の判定:
file_exists('a.txt');

標準ストリーム

`STDIN`、`STDOUT`、`STDERR` の定数は標準入力/標準出力/標準エラー出力に対応し、CLI でよく使われる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1 行入力:
$line = fgets(STDIN);
$name = trim($line);
// 標準出力に出力:
fwrite(STDOUT, "hello $name\n");
// エラーストリームに出力:
fwrite(STDERR, "警告:出错了\n");
// 行ごとに入力を処理:
while (($line = fgets(STDIN)) !== false) {
echo strtoupper($line);
}
// パイプと組み合わせる:
// echo "hi" | php app.php

13.よくある落とし穴

日常開発で踏みやすい落とし穴と正しい書き方。

== と ===

`==` は型を緩く比較し、`===` は型と値の両方を厳密に比較する。判定には基本的に `===` を使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:緩い比較の意外な結果
0 == 'foo'; // true
0 == ''; // true
'1' == 1; // true
// GOOD:厳密比較
0 === 'foo'; // false
'1' === 1; // false
// ゼロ判定は厳密に区別:
$count = 0;
if ($count == false) { /* 誤判定 */ }
if ($count === 0) { /* 正しい */ }
// 慣例:特別な場合以外は === を使う

foreach の参照の落とし穴

`foreach` で `&` を使って要素を変更したあと、最後の要素は元の配列を指したままになり、`$item` を再利用すると思いがけない上書きが起きる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$items = [1, 2, 3];
// BAD:ループ後に $item が参照を残す
foreach ($items as &$item) {
$item *= 2;
}
// 後で $item を再利用すると配列を誤って変更する
// GOOD:使い終わったら unset で解除
foreach ($items as &$item) {
$item *= 2;
}
unset($item);
// または添字で変更:
foreach ($items as $i => $v) {
$items[$i] = $v * 2;
}

isset とキーの存在

`isset` は値が `null` のキーを「存在しない」とみなす。キーが実在するかどうかは `array_key_exists` で判定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$arr = ['a' => null];
// BAD:キーは存在するが値は null
isset($arr['a']); // false
// GOOD:キーが実際に存在するか判定
array_key_exists('a', $arr); // true
// isset は読み取り前の判定に使う:
$name = isset($arr['b']) ? $arr['b'] : '默认';
// ?? は isset と同等でデフォルトを返す:
$name = $arr['b'] ?? '默认';
// null と欠落を区別する場合は array_key_exists

弱い型比較の落とし穴

弱い比較では文字列と数値のルールが特殊で、`'0'` と `false` が等しくなる。明示的に型変換が必要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// BAD:緩い比較の意外な結果
'0' == false; // true
'0' == null; // true
'abc' == 0; // true
'1e3' == 1000; // true(指数表記)
// GOOD:明示的に変換してから比較
(int) 'abc' === 0; // 意図が明確
filter_var('1e3', FILTER_VALIDATE_INT);
// ハッシュ比較は hash_equals:
// hash_equals($a, $b) はタイミング攻撃を防ぐ
// データベース結果の戻り値型に注意

未定義キーの警告

存在しない配列キーにアクセスすると警告が出る(8.0+)。`??` や `isset` で先に判定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$user = ['name' => 'Rex'];
// BAD:存在しないキーに直接アクセス
$city = $user['city'];
// PHP Warning: Undefined array key
// GOOD:空合体でデフォルトを提供
$city = $user['city'] ?? '未知';
// または先に判定してからアクセス:
$city = isset($user['city'])
? $user['city']
: '';
// 引数のデフォルト値:
function f(array $cfg = []) {
$x = $cfg['x'] ?? 0;
}

出力後のレスポンスヘッダ送信

すでに出力したあとに `header()` を呼ぶと失敗する。先にバッファを有効化するか、ヘッダを先に送信する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:出力後にレスポンスヘッダーを送る
echo "hello\n";
// header('Location: /login'); // エラー
// GOOD:先にレスポンスヘッダーを送ってから出力
header('Location: /login');
exit;
// 制御できない場合は先にバッファを開く:
ob_start();
echo "later\n";
ob_end_flush();
// 出力済みか確認:
// headers_sent()

empty と isset

`empty` は `0`、`''`、`'0'` などに対して `true` を返し、`isset` は `null` かどうかだけを判定する。意味に合う方を選ぶ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$age = 0;
$name = '';
// BAD:empty は 0 を空とみなす
if (empty($age)) { /* 未設定と誤判定 */ }
// GOOD:数値 0 は有効な値
if (!isset($age) || $age === '') { /* 未設定 */ }
// empty の意味:
empty(0); // true
empty('0'); // true
empty(''); // true
empty(null); // true
// フォームの空値判定:
// trim($val) !== '' の方が明確

SQL インジェクション対策

SQL 文字列の連結はインジェクションのリスクがある。PDO のプリペアドステートメントでパラメータをバインドする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$id = $_GET['id'];
// BAD:文字列連結 SQL(インジェクションリスク)
// $sql = "SELECT * FROM users WHERE id = $id";
// GOOD:プリペアド + バインドパラメータ
$pdo = new PDO($dsn, $user, $pass);
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE id = ?',
);
$stmt->execute([$id]);
$rows = $stmt->fetchAll();
// 名前付きパラメータ:
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE id = :id',
);
$stmt->execute([':id' => $id]);
// 入力検証:
// filter_var($id, FILTER_VALIDATE_INT)

14.並行処理とコルーチン

PHP の並行モデル:マルチプロセス、コルーチン、Fiber、そしてサードパーティ拡張。

プロセスモデル

PHP はデフォルトでシングルスレッドで動作し、リクエストごとに独立したプロセスで実行されるため、変数はリクエスト終了時に解放される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// PHP はデフォルトでシングルスレッド:
// 各リクエストは独立したプロセス/スレッドで実行
// 共有メモリは限定的,コピーオンライト:
// 子プロセスはメモリ全体をコピーしない
// 共有状態の問題なし:
// 変数はリクエスト終了とともに解放
// 並行が必要なときの選択肢:
// 1. マルチプロセス pcntl_fork
// 2. コルーチン Fiber / Swoole
// 3. parallel 拡張の真スレッド
// CLI サーバーモード:
// $ php -S localhost:8000

pcntl_fork によるマルチプロセス

`pcntl_fork` は子プロセスを生成し、親子それぞれのプロセスで実行される。CLI のみで利用可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// CLI のみ,pcntl 拡張の有効化が必要:
// $ php -m | grep pcntl
$pid = pcntl_fork();
if ($pid === -1) {
exit('fork 失败');
}
if ($pid === 0) {
// 子プロセス:戻り値 0
echo "子进程 PID " . getmypid() . "\n";
exit(0);
}
// 親プロセス:子プロセスを待機
pcntl_wait($status);
echo "父进程 PID " . getmypid() . "\n";
// 子プロセスはコードとメモリスナップショットを共有
// 変更は互いに影響しない(コピーオンライト)

シグナル処理

`pcntl_signal` でシグナルハンドラを登録し、`dispatch` で保留中のシグナルを配送する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
declare(ticks = 1);
// シグナルハンドラを登録:
pcntl_signal(SIGINT, function (int $signo): void {
echo "收到信号 $signo,退出\n";
exit(0);
});
// 保留中のシグナルを処理:
while (true) {
pcntl_signal_dispatch();
usleep(100000);
}
// よく使うシグナル:
// SIGTERM 終了 SIGINT Ctrl+C
// SIGHUP ハングアップ SIGCHLD 子プロセス終了
// 本番環境ではシグナルでグレースフルシャットダウン

parallel 拡張

`parallel` は PECL 拡張であり、真のマルチスレッド worker を提供する。標準では組み込まれていない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// parallel は PECL 拡張,組み込みではない:
// $ pecl install parallel
use parallel\Runtime;
use parallel\Future;
// ワーカースレッドを起動:
$rt = new Runtime();
$future = $rt->run(function (int $n): int {
return $n * $n;
}, [7]);
// ブロックして結果を取得:
$result = $future->value(); // 49
// スレッド共有の変数は明示的に引数で渡す
// 外部変数を捕捉できない
// 本番のマルチコア集中タスクに使える
// Web 環境は通常マルチプロセスの方が安定

Swoole コルーチン

Swoole は高性能なネットワークフレームワークで、コルーチンによるスケジューリングと常駐メモリを特徴とし、高並行処理に適する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Swoole は高性能ネットワークフレームワーク拡張:
// $ pecl install swoole
// コルーチンサーバーを作成:
use Swoole\Coroutine\Http\Server;
$server = new Server('0.0.0.0', 9501);
$server->handle('/', function ($req, $res) {
// 接続ごとに 1 コルーチン:
$res->end("hello\n");
});
$server->start();
// 常駐メモリ + コルーチンスケジューリング:
// 高並行でもメモリはスレッドモデルより大幅に少ない
// マルチプロセス worker もサポート:
// Swoole\Server の worker_num 設定

Fiber コルーチン

`Fiber`(8.1+)はネイティブのコルーチンで、`suspend` で中断し、`resume` で再開する手動スケジューリングである。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Fiber(8.1+)コルーチン,手動切替:
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('first');
echo "恢复,收到 $value\n";
});
// 開始,最初の suspend まで実行:
$value = $fiber->start();
echo "挂起返回 $value\n"; // first
// 再開して値を渡す:
$fiber->resume('second');
// 恢复,收到 second
// 単一スレッド内での協調,並列ではない
// 非同期コードを同期的に書くために使う

非同期ライブラリ

PHP には組み込みの `async` / `await` がないため、ReactPHP や Amp といったイベントループライブラリで非同期を実現する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 組み込みの async/await キーワードはない:
// イベントループライブラリで非同期 I/O を実現
// ReactPHP のイベントループ例:
use React\EventLoop\Factory;
$loop = Factory::create();
$loop->addTimer(1.0, function () {
echo "ok\n";
});
$loop->run();
// 非同期ライブラリの定番抽象:
// Promise / Deferred / await
// 単純な並行ならマルチプロセスで十分
// 高並行 I/O は Swoole/ReactPHP

プロセス間通信

複数プロセスでデータを共有するには、メッセージキュー、ファイル、共有メモリを使い、同じファイルを直接書き換えるのは避ける。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// マルチプロセス間でデータ共有:
// 1. ファイル/データベース
// 2. メッセージキュー(Redis など)
// 3. 共有メモリ shmop
// 4. パイプ
// メッセージキューの簡単な例:
// Redis リストをタスクキューに:
// $redis->lpush('jobs', $task);
// 子プロセスがパイプに書く:
// posix_kill($pid, SIGUSR1);
// タスク結果の収集:
// 各 worker が独立ファイルに書く
// 本番システムでよく使う:
// RabbitMQ / Kafka / Redis Stream
// 複数プロセスで同一ファイルを直接変更するのは避ける

15.ネットワーク

HTTP クライアント、curl、URL 解析、セッションとソケット。

HTTP リクエスト

`file_get_contents` で簡単なリクエストを送り、`stream_context_create` でタイムアウトやヘッダを設定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// シンプルな GET(allow_url_fopen):
$body = file_get_contents(
'https://example.com/api',
);
// タイムアウト付き:
$ctx = stream_context_create([
'http' => ['timeout' => 5],
]);
$body = file_get_contents(
'https://example.com',
false,
$ctx,
);
// 本番では curl か guzzle を推奨
// ステータスコードと詳細な制御に対応

curl 拡張

`curl_init` と `curl_exec` でリクエストを完全に制御し、ステータスコードやエラー情報を取得する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// curl 拡張(CLI 組み込み):
$ch = curl_init(
'https://api.example.com/data',
);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer xxx',
],
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
// エラー確認:
if (curl_errno($ch)) {
echo curl_error($ch);
}
// POST データ:
// CURLOPT_POST + CURLOPT_POSTFIELDS

POST と Guzzle

`stream_context` で POST の JSON を送信できるが、実運用では Guzzle クライアントを優先する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// POST JSON:
$data = json_encode(['name' => 'Rex']);
$ctx = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => $data,
],
]);
$res = file_get_contents(
'https://example.com/api',
false,
$ctx,
);
// Guzzle の方がエンジニアリング的:
// $client->request('POST', $url, [
// 'json' => $payload,
// ]);
// リクエストヘッダーの区切りは CRLF

リクエストとレスポンスヘッダー

header() でレスポンスヘッダーを送信し、http_response_code でステータスコードを設定する。出力前に呼び出す必要がある。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// レスポンスヘッダーを読む:
$ctx = stream_context_create([
'http' => ['ignore_errors' => true],
]);
$body = file_get_contents(
'https://example.com',
false,
$ctx,
);
$headers = $http_response_header ?? [];
// レスポンスヘッダーを送信:
header('Content-Type: application/json');
header('Cache-Control: max-age=3600');
// ステータスコードを設定:
http_response_code(404);
// リクエストヘッダーを読む:
// getallheaders() は連想配列を返す
// 出力前に必ず header を送る

URL 解析

parse_url で URL の各コンポーネントを分解し、parse_str でクエリ文字列を解析、http_build_query で組み立てる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$url = 'https://user:[email protected]:8080/a?q=1#top';
// 各コンポーネントを解析:
$parts = parse_url($url);
// [
// 'scheme' => 'https',
// 'host' => 'example.com',
// 'port' => 8080,
// 'path' => '/a',
// 'query' => 'q=1',
// 'fragment' => 'top',
// ]
// クエリ文字列:
parse_str('a=1&b=2', $out);
// $out = ['a' => '1', 'b' => '2']
// URL エンコードされたパラメータを組み立て:
http_build_query(['a' => 1, 'b' => 2]);
// a=1&b=2

Cookie とセッション

session_start でセッションを開始し、setcookie で Cookie を書き、$_COOKIE で読み取る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// セッションを開始:
session_start();
// セッション cookie が自動生成される
// セッションデータを読み書き:
$_SESSION['user_id'] = 123;
echo $_SESSION['user_id'];
// cookie を設定:
setcookie('theme', 'dark', [
'expires' => time() + 3600,
'path' => '/',
'httponly' => true,
'samesite' => 'Lax',
]);
// 読み取り:
echo $_COOKIE['theme'] ?? '默认';
// セッションを破棄:
session_destroy();
// セッションデータはサーバー側,cookie は id のみ

ソケット

stream_socket_client でリモートに接続し、fwrite で送信、fgets で読み取る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// クライアントソケット:
$sock = stream_socket_client(
'tcp://example.com:80',
$errno,
$errstr,
10,
);
if (!$sock) {
echo "连接失败: $errstr";
}
fwrite($sock, "GET / HTTP/1.0\r\n\r\n");
while (!feof($sock)) {
echo fgets($sock);
}
fclose($sock);
// サーバー側の待ち受け:
// stream_socket_server('tcp://0.0.0.0:9000')
// 本番のプロトコルサーバーは Swoole を使う
// 手書き socket は境界問題が起きやすい

SSE と WebSocket

SSE は単方向のテキスト push、WebSocket は双方向通信で、用途に応じて選択する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SSE:サーバーがテキストストリームをプッシュ
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
while (true) {
echo "data: " . json_encode(['t' => time()]) . "\n\n";
flush();
sleep(1);
}
// クライアントは EventSource で受信
// 一方向,HTTP ベースでシンプルで信頼性が高い
// WebSocket 双方向通信:
// Swoole、Ratchet などの拡張が必要
// 選定:簡単なプッシュは SSE
// 双方向の対話にのみ WebSocket

16.日付と時刻

タイムスタンプ、フォーマット、解析、タイムゾーン、時刻計算。

現在時刻の取得

time() で秒単位のタイムスタンプを取得し、microtime でミリ秒単位を取得、date でフォーマットする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 現在のタイムスタンプ(秒):
$ts = time();
// ミリ秒級:
$ms = microtime(true);
// 現在時刻をフォーマット:
echo date('Y-m-d H:i:s'); // 2026-08-02 12:30:00
// デフォルトタイムゾーンを読む:
date_default_timezone_get();
// タイムゾーン設定:
// date_default_timezone_set('Asia/Shanghai');
// 旧式の time() は date() の第 2 引数と同等

日付のフォーマット

date() はフォーマット文字でタイムスタンプをレンダリングし、年・月・日・時・分・秒とローカライズ表記に対応する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ts = strtotime('2026-08-02 12:30:00');
// フォーマット:
echo date('Y-m-d', $ts); // 2026-08-02
echo date('H:i:s', $ts); // 12:30:00
echo date('D, d M Y', $ts); // Sun, 02 Aug 2026
echo date('Y年m月d日', $ts); // 2026年08月02日
// よく使う書式記号:
// Y 4 桁の年 m 2 桁の月 d 2 桁の日
// H 24 時間 i 分 s 秒
// D 曜日略記 M 月略記
// タイムスタンプに戻す(逆):
// strtotime('2026-08-02')

日付の解析

strtotime は日付テキストと相対時刻を解析し、失敗時は false を返す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 日付文字列をタイムスタンプに解析:
$ts = strtotime('2026-08-02 12:30:00');
echo $ts;
// 相対時間:
strtotime('+1 day'); // 明日
strtotime('next Monday'); // 次の月曜
strtotime('-2 weeks'); // 2 週間前
// 解析失敗は false を返す:
$bad = strtotime('不是日期');
// false
// 複雑な解析は DateTime:
// new DateTime('2026-08-02 12:30:00')
// タイムゾーンが解析結果に影響することに注意

タイムスタンプ

Unix タイムスタンプは UTC の秒数であり、DateTime がタイムスタンプとオブジェクトの相互変換を提供する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// タイムスタンプ(epoch 秒):
$ts = time();
// 指定の時刻を作成:
$dt = new DateTime('2026-08-02 12:30:00');
$ts = $dt->getTimestamp();
// タイムスタンプのフォーマット:
$dt = new DateTime();
$dt->setTimestamp($ts);
echo $dt->format('Y-m-d');
// ミリ秒タイムスタンプ(サーバー):
$ms = (int) (microtime(true) * 1000);
// タイムスタンプは UTC の秒数
// 表示時はローカルタイムゾーンで換算

タイムゾーン

date_default_timezone_set でデフォルトのタイムゾーンを設定し、DateTime は独自のタイムゾーンを保持できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// デフォルトタイムゾーンを設定:
date_default_timezone_set('Asia/Shanghai');
// 全タイムゾーンを表示:
// DateTimeZone::listIdentifiers()
// DateTime でタイムゾーン指定:
$dt = new DateTime('now', new DateTimeZone('UTC'));
echo $dt->format('Y-m-d H:i:s');
// タイムゾーン換算:
$dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
echo $dt->format('Y-m-d H:i:s P');
// 本番は UTC で統一保存
// 表示層はユーザーのタイムゾーンで変換
// date('T') でタイムゾーンを推測しない

時間間隔

DateInterval は期間を表し、DateTime の add/sub で間隔を加算・減算する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 2 つの時刻の差:
$start = new DateTime('2026-08-01');
$end = new DateTime('2026-08-02');
$diff = $start->diff($end);
echo $diff->days; // 1
// 時間を加える:
$dt = new DateTime('2026-08-02');
$dt->add(new DateInterval('P3D')); // 3 日加算
echo $dt->format('Y-m-d'); // 2026-08-05
// 時間を引く:
$dt->sub(new DateInterval('PT2H')); // 2 時間減算
// DateInterval の形式:
// P 期間 D 日 T 時間部分
// P1Y 1 年 P2M 2 か月 PT1H 1 時間
// 間隔の日数換算:
// $diff->days 総日数

時間差の計算

DateTime diff は DateInterval を返し、年齢や残日数などを読み取れる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 年齢を計算:
$birth = new DateTime('1990-05-20');
$now = new DateTime();
$diff = $birth->diff($now);
echo $diff->y . ' 岁'; // 整数の年
// 差分の各フィールド:
$diff->y; // 年
$diff->m; // 月
$diff->d; // 日
$diff->h; // 時間
$diff->i; // 分
// 人間が読める形式:
echo $diff->format('%y 年 %m 月 %d 天');
// 合計秒数:
// $diff->s + $diff->i * 60 + ...
// 負数は invert フラグを反転

スリープとタイマー

sleep はブロッキングで一時停止し、イベントループのタイマーは非ブロッキングで遅延タスクをスケジューリングできる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 実行を一時停止(秒):
sleep(2);
// マイクロ秒の一時停止:
usleep(500000); // 0.5 秒
// タスク遅延(ReactPHP):
use React\EventLoop\Factory;
$loop = Factory::create();
$loop->addTimer(1.0, function () {
echo "1 秒后执行\n";
});
$loop->run();
// Swoole タイマー:
// Swoole\Timer::tick(1000, fn() => ...)
// CLI の長時間タスクは sleep のブロックに注意
// 高並行シーンはイベントループのタイマーを

17.プロセスと CLI

コマンドライン引数、入出力、環境変数、プロセス制御。

コマンドライン引数

$argv 配列はコマンドライン引数を保持し、$argv[0] がスクリプト名である。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// $ php app.php arg1 arg2
// $argv[0] = 'app.php'
// $argv[1] = 'arg1'
// $argv[2] = 'arg2'
// 引数の個数:
echo count($argv); // 3
// スクリプト名を除いて走査:
foreach (array_slice($argv, 1) as $arg) {
echo $arg;
}
// オプション解析:
// getopt('a:b::') 次節参照
// stdin 入力を読む:
// fgets(STDIN)

入力の読み取り

fgets(STDIN) は 1 行を読み、stream_get_contents は全部を読み、trim で改行を除去する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 対話入力:
echo '请输入姓名:';
$name = trim(fgets(STDIN));
// 複数行を一括で読む:
while (($line = fgets(STDIN)) !== false) {
echo strtoupper($line);
}
// 入力をすべて読む:
$all = stream_get_contents(STDIN);
// パイプ入力か判定:
// stream_isatty(STDIN)
// 入力が空の可能性:
// 対話前に posix_isatty で確認

外部コマンドの実行

shell_exec は出力を取得し、exec はステータスコードを取得する。引数渡しは escapeshellarg でインジェクションを防ぐ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 外部コマンドを実行し出力を返す:
$out = shell_exec('ls -la');
echo $out;
// ステータスコードを取得:
exec('git status', $lines, $code);
// $code 0 は成功を表す
// 配列は各行に出力:
// $lines は行ごとに 1 要素
// ユーザー入力をコマンドに連結するのは禁止:
// $cmd = 'rm ' . $_GET['f']; // 危険
// 引数を渡すときは escapeshellarg:
// exec('cat ' . escapeshellarg($file))

環境変数

getenv で環境変数を読み取り、putenv で設定する。機密性の高い設定は環境変数経由とする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 環境変数を読む:
echo getenv('HOME');
// 環境変数を設定:
putenv('APP_ENV=production');
echo getenv('APP_ENV');
// 全部読む:
print_r(getenv());
// $_ENV スーパーグローバル:
echo $_ENV['PATH'] ?? '';
// .env ファイル用の定番ライブラリ:
// vlucas/phpdotenv
// 機密情報は環境変数で
// コードベースにハードコードしない

プロセス情報

getmypid で現在の PID を取得し、posix_* 系でユーザーと親プロセスを調べ、hrtime で計測する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 現在のプロセス PID:
$pid = getmypid();
// 親プロセス PID:
echo posix_getppid();
// 現在のユーザー:
echo posix_getuid();
echo posix_getpwuid(posix_getuid())['name'];
// 実行時間:
// hrtime() でマイクロ秒を計測:
$start = hrtime(true);
work();
$ms = (hrtime(true) - $start) / 1e6;
echo "$ms ms";
// CLI プロセス情報:
// コマンド名 / proc/self/cmdline

プロセスシグナル

pcntl_signal でシグナル処理を制御し、pcntl_wait で子プロセスの終了を待つ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// シグナルを無視:
pcntl_signal(SIGTERM, SIG_IGN);
// デフォルト処理:
pcntl_signal(SIGTERM, SIG_DFL);
// 子プロセスを待機(非ブロッキング):
$pid = pcntl_fork();
if ($pid === 0) { exit(0); }
// ブロッキング待機:
pcntl_wait($status);
// 終了コードを確認:
echo pcntl_wexitstatus($status);
// プロセスにシグナルを送る:
// posix_kill($pid, SIGTERM)
// シグナル処理内で複雑な操作は避ける

パイプと端末

stream_isatty で対話端末かを判定し、stderr で進捗を出力し、exit で終了コードを制御する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// パイプから stdin を読む:
// echo "hi" | php app.php
// 出力ストリームをリダイレクト:
// php app.php > out.txt 2>&1
// 対話ターミナルか判定:
if (stream_isatty(STDIN)) {
echo "交互模式\n";
} else {
echo "管道输入\n";
}
// 進捗は stderr に出力:
fwrite(STDERR, "处理中...\n");
// バックグラウンドで実行:
// $ php app.php &
// 終了コード制御:
exit(0); // 成功
exit(1); // 失敗

終了コード

exit() は終了コードを渡し、0 が成功・非 0 が失敗で、シェルは $? で読み取る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 正常終了:
exit(0);
// 業務エラー:
if (!$data) {
exit(1); // 一般的なエラー
}
// カスタムコード:
exit(2); // 使い方エラー
exit(3); // 入力が無効
// スクリプト内で早期終了:
return; // または exit
// shell での確認:
// php app.php
// echo $? // 直前のコマンドの終了コード
// 0 は成功,非 0 は失敗
// 128+ はシグナルで終了したことを示す

18.正規表現

マッチ、キャプチャ、置換、分割、マルチバイトモード。

preg_match

preg_match は最初の結果にマッチし、キャプチャグループは $m に格納される。名前付きグループは名前で取得する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 最初の結果をマッチ:
$s = '订单号 A123 和 B456';
if (preg_match('/[A-Z]\d{3}/', $s, $m)) {
echo $m[0]; // A123
}
// キャプチャグループ:
preg_match('/([A-Z])(\d{3})/', $s, $m);
echo $m[1]; // A
echo $m[2]; // 123
// 名前付きグループ:
preg_match('/(?<type>[A-Z])(?<num>\d{3})/', $s, $m);
echo $m['type']; // A
// 正規表現エラーは false を返す

preg_match_all

すべての結果にマッチし、デフォルトはグループ単位、PREG_SET_ORDER はマッチ単位でグループ化する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$s = '订单号 A123 和 B456';
preg_match_all('/[A-Z]\d{3}/', $s, $m);
print_r($m[0]);
// ['A123', 'B456']
// グループ構造:
preg_match_all('/([A-Z])(\d{3})/', $s, $m);
// $m[0] 全体マッチ
// $m[1] 第 1 グループ
// $m[2] 第 2 グループ
// キーと値のキャプチャ:
preg_match_all(
'/(\w+)=(\d+)/',
'a=1&b=2',
$m,
PREG_SET_ORDER,
);
// $m[0] = ['a=1', 'a', '1']
// 第 2 引数で返り値の構造が決まる

preg_replace

正規表現で置換し、$1 でキャプチャグループを参照する。preg_replace_callback はコールバックで処理する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// マッチを置換:
$s = '电话 138-1234-5678';
$r = preg_replace('/\d{3}-\d{4}-\d{4}/', '[隐藏]', $s);
// '电话 [隐藏]'
// キャプチャグループで再構成:
preg_replace(
'/(\d{4})(\d{4})/',
'$1-$2',
'13812345678',
);
// '1381-2345-678'(例)
// 後方参照:
preg_replace('/(\w+)\s+\1/', '$1', 'hi hi there');
// 'hi there'
// 配列で一括置換:
preg_replace(['/a/', '/b/'], ['A', 'B'], 'abc');
// コールバック置換:
preg_replace_callback('/\d+/', fn($m) => $m[0] * 2, '1 2');

よく使うパターン

文字クラス、数量子、貪欲マッチ、正規表現のデリミタとエスケープ規則。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 文字クラス:
\d // 数字 [0-9]
\w // 単語文字 [A-Za-z0-9_]
\s // 空白
// 量指定子:
* // 0 回以上
+ // 1 回以上
? // 0 回または 1 回
{n,m} // n 回から m 回
// 貪欲と怠惰:
'<b>x</b>'
/<.*>/ // 貪欲:<b>x</b>
/<.*?>/ // 怠惰:<b>
// エスケープ:
\. \( \) \\ // \\ プレフィックスが必要
// デリミタ:
// 先頭文字が区切り,末尾に修飾子を補う
// '/' 内のエスケープは '\/' と書く

キャプチャとアサーション

キャプチャグループ、非キャプチャグループ、名前付きグループ、前後アサーション。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// キャプチャグループ:
/(\d{4})-(\d{2})/
// 2026-08 にマッチ
// 非キャプチャグループ:
/(?:ab)+/
// グループ化のみ,性能が良い
// 名前付きグループ:
/(?P<year>\d{4})-(?P<month>\d{2})/
// $m['year']
// 後方参照:
/(\w+)\s+\1/
// \1 は第 1 グループの内容を参照
// アサーション:
/(?=\d{5}$)/ // 先読み
/(?<=^\d{3})/ // 後読み
// グループ内の交替:
/gr(a|e)y/

アンカーと境界

^ は行頭、$ は行末、\b は単語境界で、m 修飾子で複数行マッチに対応する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 行頭行末:
/^php/ // php で始まる
/php$/ // php で終わる
// 単語境界:
/\bcat\b/ // 独立した単語 cat にマッチ
// category、concatenate にはマッチしない
// 複数行モード:
// 修飾子 m で ^ $ が各行にマッチ
preg_match_all(
'/^\d+/m',
"12\nabc\n34",
$m,
);
// ['12', '34']
// 完全一致:
// /^...$/ に \z を足して末尾改行を防ぐ
// 単語境界は中文に適用できない

正規表現による分割

preg_split は正規表現で文字列を分割し、区切り文字を保持したり数を制限したりできる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 正規表現で分割:
$parts = preg_split('/[,;]/', 'a,b;c');
// ['a', 'b', 'c']
// 区切りを保持:
preg_split('/(,)/', 'a,b', -1, PREG_SPLIT_DELIM_CAPTURE);
// ['a', ',', 'b']
// 個数を制限:
preg_split('/\s+/', 'a b c d', 2);
// ['a', 'b c d']
// 空白で分割:
preg_split('/\s+/', ' a b ');
// ['', 'a', 'b'](先頭末尾が空)
// 空要素を除くなら array_filter と組み合わせる
// 単純な区切りは explode で十分

マルチバイトと u 修飾子

マルチバイト文字を扱うには u 修飾子を付け、preg_quote でユーザー入力をエスケープする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 中文処理には u 修飾子:
preg_match('/\p{Han}+/u', '你好世界abc', $m);
echo $m[0]; // 你好世界
// マルチバイト文字クラス:
/\p{Han}/ // 漢字
/\p{L}/ // 任意の文字
/\p{N}/ // 数字
// エスケープ(危険):
// ユーザー入力を正規表現にする前にエスケープ:
$lit = preg_quote($_GET['q'], '/');
// エスケープ:
preg_match(
'/' . $lit . '/u',
'a+b',
$m,
);
// u 修飾子で文字化けを防ぐ

19.ビルドとツールチェーン

Composer の依存管理、オートロード、CLI ビルドフロー。

プロジェクトの初期化

composer init は composer.json を生成し、依存関係とオートロードルールを宣言する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// プロジェクト初期化:
// $ composer init
// composer.json を生成:
// {
// "name": "vendor/demo",
// "type": "project",
// "require": { "php": "^8.1" },
// "autoload": { "psr-4": { "App\\": "src/" } }
// }
// 対話式ウィザードで生成
// その後 composer install を実行
// PHP のバージョン制約は require に記述
// ^8.1 は 8.1.x 以上を意味する

依存関係のインストール

composer install は lock に基づいてインストールし、require で新しい依存関係を追加する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 依存をインストール:
// $ composer install
// 本番依存のみインストール:
// $ composer install --no-dev
// 単一パッケージをインストール:
// $ composer require monolog/monolog
// アンインストール:
// $ composer remove monolog/monolog
// 最新の制約版に更新:
// $ composer update
// 依存の不具合を確認:
// $ composer validate
// $ composer audit
// 本番環境では --no-dev を付ける
// 攻撃面とサイズを減らす
// composer.lock は必ずコミット
// 正確なバージョンをロックして再現可能に

依存関係の取り込み

require vendor/autoload.php を実行すれば依存クラスを use でき、オートロードが有効になる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// プロジェクトで依存を使う:
require __DIR__ . '/vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(
new StreamHandler('php://stderr'),
);
$log->info('启动完成');
// オートローダーは install で生成
// autoload 設定を変えるたびに dump:
// $ composer dump-autoload
// クラス名とファイルの対応:
// PSR-4 名前空間はディレクトリに対応

PSR-4 オートロード

名前空間とディレクトリが 1 対 1 に対応し、App\ が src/ にマップされる。dump-autoload が必要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// composer.json:
// {
// "autoload": {
// "psr-4": {
// "App\\": "src/"
// }
// }
// }
// src/User.php:
// namespace App;
// class User {}
// 自動ロード後:
use App\User;
$u = new User();
// 名前空間パスは一対一対応:
// App\\Controller\\Home
// → src/Controller/Home.php
// 設定変更後は再生成:
// $ composer dump-autoload
// PSR-4 はクラスプレフィックスのみマッピング
// 他の名前空間には影響しない

Composer スクリプト

composer.json の scripts はコマンドのエイリアスを定義し、インストールフックのコールバックに対応する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// composer.json スクリプト:
// {
// "scripts": {
// "test": "phpunit",
// "lint": "php -l src/*.php"
// }
// }
// 実行:
// $ composer test
// $ composer lint
// 組み込みスクリプトフック:
// pre-install-cmd、post-install-cmd
// ライフサイクルコールバック:
// インストール前後にクリーンアップ/権限設定を実行
// カスタムコマンドは scripts に整理
// 長いコマンドを手で打つのを避ける
// クロスプラットフォームのパスに注意
// バックスラッシュでなく / を使う

バージョン制約

^、~、>= などの制約構文で依存バージョンの範囲を制御し、lock で正確な版を固定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// バージョン制約の構文:
// ^8.1 8.1.x を許可(>=8.1 <9)
// ~8.1 8.1.x を許可(<8.2)
// >=8.1 任意の >=8.1
// * 任意のバージョン
// 1.2.3 厳密なバージョン
// ブランチ参照:
// dev-master
// v2.0.0-beta
// 正確なバージョンにロック:
// composer.lock は実際のバージョンを記録
// 指定バージョンをインストール:
// $ composer require foo/bar:^2.0
// アップグレード可能を確認:
// $ composer outdated
// 本番はロックしたバージョンでデプロイ

CLI コマンド

フレームワークコマンド(artisan / console)がマイグレーション、キャッシュ、スケジュールタスクを実行する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// コマンドのヘルプを表示:
// $ php bin/console list
// カスタムコマンドを実行:
// $ php artisan migrate
// $ php bin/console cache:clear
// アプリキーを生成:
// $ php artisan key:generate
// データベースマイグレーション:
// $ php artisan migrate --seed
// 定期実行タスク:
// $ crontab -e
// * * * * * php /path/app/cron.php
// 本番コマンドには --env=production を付ける
// CI と連携し自動デプロイ
// よく使うフレームワークのコマンド接頭辞:
// Symfony bin/console
// Laravel artisan

PHP 設定

php -i / php --ini / php -m でランタイムを検査し、ini_get で設定を参照する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 設定を確認:
// $ php -i 全設定
// $ php --ini 読み込んだ ini パス
// $ php -m ロード済み拡張
// 拡張を確認:
// $ php -m | grep curl
// 実行時クエリ:
echo ini_get('memory_limit');
echo ini_get('upload_max_filesize');
// 実行時変更:
ini_set('memory_limit', '512M');
// CLI 引数で設定指定:
// $ php -d error_reporting=E_ALL app.php
// 本番設定チェック:
// $ php -v
// $ php --ri opcache

公式リンク

公式ドキュメントとリソースへの直接リンク。

このチートシートについて

本ページは PHP 8.3 の自己完結的なチートシートであり、実プロジェクトで頻出する言語コアと最もよく使う標準ライブラリの約 80% の一般的用法をカバーする。内容は現代のイディオムに寄せており、declare(strict_types=1) 厳格モード、enum、readonly プロパティ、match 式、名前付き引数、アロー関数、first-class callable を含む。権威あるリファレンスは公式 PHP マニュアルと PHP The Right Way を参照。 19 のセクションはそれぞれ 1 つのテーマに焦点を当てており、最初のプログラムから配列、オブジェクト指向、よくある落とし穴までを扱う。各セクションは 8〜14 のサンプル付きサブセクション(各 5〜20 行)に分かれ、合計約 150 トピック。コードスニペットは意図的に短く、自己説明的である。 すべての処理はブラウザ内で行われる。アップロードもトラッキングもない。本ページは GuruToolkit の無料開発者ツールセットの一部であり、コードスニペットはいかなる保証もなく自由に使用できる。

バージョン 2.1.0