PHP
melangの各noteで他言語と比較した、PHPの設計をまとめたページ。ここに書かれている内容は、各noteの比較表から自動で集めたものです。
型
関数の引数・戻り値には型宣言を書ける。
$age = 20;$age = 20;
$age = "Alice"; // 再代入で型が変わる
function setAge(int $age): void {} // 引数・戻り値には型宣言を書ける変数自体に型注釈を書く構文はないが、関数の引数・戻り値・プロパティには型宣言をつけられる。`declare(strict_types=1)`を宣言すると、型不一致時の暗黙変換をやめて厳格に検査できる。
型注釈の位置
引数はprefix、戻り値はpostfixという、1つのシグネチャの中で位置が混在する。
function setAge(int $age): void {}$age = 20; // 変数自体には型注釈を書けない
function setAge(int $age): void { // 引数はprefix(int $age)、戻り値はpostfix(: void)
// ...
}変数自体には型注釈の構文がないが、関数の引数は`Type $name`という前置、戻り値は`: Type`という後置と、1つの関数シグネチャの中でprefix/postfixが混在する。引数はC系の伝統を継ぎ、戻り値は後発の`: Type`構文(PHP 7以降)を採用した歴史的経緯の違いがそのまま表れている。
型変換
キャスト構文で変換し、失敗を黙って別の値にする場合がある。
$n = (int) $s;$n = (int) "42"; // 42
$m = (int) "abc"; // 0(失敗を隠す)
$k = (int) "12abc"; // 12(途中まで読む)
filter_var("abc", FILTER_VALIDATE_INT); // false
filter_var("42", FILTER_VALIDATE_INT); // 42`(int)`キャストは変換に失敗しても`0`を返すなど、失敗を通常の値として扱う。失敗を検出したい場合は`filter_var`のような別の関数を使い、失敗は`false`で返る。成功した`0`と失敗を区別するには呼び出し側の注意が必要になる。
暗黙の型変換
数値と数値形式の文字列間で暗黙変換が広く行われる。
$x = 10;$x = 10;
$x + "5abc"; // 15(文字列が数値へ暗黙変換される)
$x == "10"; // true(==は型を揃えてから比較する)数値と数値形式の文字列との間でも暗黙変換が広く行われ、`==`のような比較演算子でも型が揃えられてから比較される。`declare(strict_types=1)`や`===`を使うことで暗黙変換を抑制できるが、デフォルトでは緩い。
Enum / Sum Type
backed enumで値を持てるが、matchの網羅漏れは実行時エラー。
enum Status: string { case Pending = 'pending'; case Running = 'running'; }enum Status: string {
case Pending = 'pending';
case Running = 'running';
}
echo match ($status) {
Status::Pending => 'pending',
Status::Running => 'running',
}; // 網羅しないと実行時にUnhandledMatchErrorになるbacked enumで文字列/整数値を持てるが、variantごとに異なるフィールドは持てない。`match`式は全caseを網羅しないと、コンパイル時ではなく実行時に`UnhandledMatchError`になる。
値の不変性
変数もオブジェクトも既定は変更可能。`readonly`で個別に制限できる。
public readonly string $name; / readonly class (8.2)final class User {
public function __construct(public readonly string $name) {}
public function withName(string $name): static {
return new static($name);
}
}
$a = new User("alice");
// $a->name = "bob"; // Error: readonlyは初期化後に変更できない
$b = $a->withName("bob"); // 新しい値を生成
$arr = [1, 2];
$arr[] = 3; // OK: 配列は値として扱われるが、変数の中身は変えられる`readonly`プロパティ(8.1)と`readonly class`(8.2)で、初期化後の変更を禁止できる。配列は代入時に値として扱われる(コピーオンライト)ため、別の変数から見える影響は出ない。一方でオブジェクトは、ハンドルが共有される。不変にするかどうかは、書き手の選択である。
名前付き引数
PHP 8.0で導入。位置引数と併用でき、途中の引数だけ省略することもできる。
function foo(int $width, int $height = 0)function foo(int $width, int $height = 0) {}
foo(width: 10, height: 20); // 名前付き引数
foo(10, height: 20); // 位置引数と併用可
foo(height: 20, width: 10); // 名前付きだけなら順序は自由PHP 8.0で追加された比較的新しい機能。名前付き引数を使うと、デフォルト値を持つ引数の一部だけを省略して呼び出せる。
関数の引数
定義側に&を付けると参照渡し。呼び出し側は印が要らない。
function f(&$x)function a($n) { $n = 1; }
function b(&$n) { $n = 1; }
$x = 0;
a($x); // $xは0のまま
b($x); // $xが1になる。呼び出し側は見た目が同じ定義側に`&`を付けると参照渡しになるが、呼び出し側は印が要らない。`a($x)`と`b($x)`が同じ見た目で、変数が書き換わるかは関数の定義を見ないと分からない。かつては呼び出し側に`&`を書ける仕組みもあったが、現在は廃止されている。
if 文/式
`if`はstatement。値の分岐は三項演算子`?:`やmatch式で書く。
if ($cond) { ... } else { ... } / $cond ? $a : $b$x = $cond ? 1 : 2;
if ($cond) {
process();
}`if`はstatementで、値の分岐は三項演算子か、PHP 8.0で加わったmatch式を使う。`else if`と`elseif`の2つの書き方がある。
I/O の明示
I/Oを示す構文がない。
function findUser(UserId $id): Userfunction findUser(UserId $id): User {
return $db->find($id);
}
function getUser(UserId $id): User {
return findUser($id); // 呼べる
}引数・戻り値の型宣言はできるが、effectを示す仕組みは言語にない。
言語名
"Personal Home Page"の略が起源だが、現在は再帰的頭字語に意味が変わった。
元々"Personal Home Page Tools"の略だったが、言語が本格化した後に"PHP: Hypertext Preprocessor"という自己言及的な(再帰的)頭字語に意味を変えた。名前の綴りを変えずに意味だけを再定義するという、ブランド刷新の一手法。