1. ホーム
  2. PHP

【PHP】名前付き引数(Named Arguments)の使い方|引数を名前で指定して読みやすく書く方法

Share

setcookie('token', $value, 0, '/', '', true, true); のような呼び出しを見て、後ろに並んだ true が何を表しているのか、マニュアルを開かずに答えられる人はほとんどいないはずです。PHP 8.0 で追加された名前付き引数(Named Arguments)を使うと、これを setcookie('token', $value, secure: true, httponly: true) と書けます。この記事では、名前付き引数の書き方・位置引数と混ぜるときのルール・デフォルト値を飛ばせる利点・可変長引数との組み合わせ、そして「引数名が公開 API になる」という見落としがちな注意点までを解説します。

名前付き引数で何が変わるのか

PHP 8.0 より前、関数に値を渡す方法は書いた順番で対応づける位置引数(positional argument)だけでした。第1引数はここ、第2引数はここ、という決まりなので、途中の引数を飛ばすことはできません。3番目の引数だけ変えたければ、必要のない1番目と2番目もデフォルト値と同じ内容で書く必要がありました。

名前付き引数は、値を「何番目か」ではなく「引数名」で指定する仕組みです。引数名を書くので順番に縛られず、指定しなかった引数はデフォルト値のまま残ります。次のコードで違いを見てみます。

index.php
<?php
function createUser(
    string $name,
    string $role = 'member',
    bool $isActive = true,
    bool $sendMail = false
): void {
    // 省略(ユーザーを作る処理)
}

// PHP 8.0 より前:sendMail だけ変えたいのに、間の引数も全部書く必要がある
createUser('たろう', 'member', true, true);

// PHP 8.0 以降:変えたい引数だけを名前で指定できる
createUser(name: 'たろう', sendMail: true);

2つの呼び出しは、まったく同じ結果になります。違うのは読みやすさです。前者の 'member', true, true は、関数の定義を見に行かないと意味が分かりません。後者は sendMail: true と書いてあるので、「メールを送る設定にしている」ことがその行だけで読み取れます。引数の意味を、呼び出し側のコードに残せるのが名前付き引数のいちばんの価値です。

基本の書き方

書き方は 引数名: 値 です。引数名のあとにコロンを置き、続けて値を書きます。関数側の宣言では $name のように $ が付いていますが、呼び出し側では $ を付けません。ここを間違えると変数として解釈されてしまうので注意してください。

index.php
<?php
function createUser(
    string $name,
    string $role = 'member',
    bool $isActive = true,
    bool $sendMail = false
): void {
    echo "{$name} / {$role}", PHP_EOL;
}

// すべて名前付きで指定する
createUser(name: 'はなこ', role: 'admin');   // はなこ / admin

// 名前付き引数どうしは順番が自由。上と同じ意味になる
createUser(role: 'admin', name: 'はなこ');   // はなこ / admin

// 値には変数も式も書ける
$inputName = 'じろう';
createUser(name: $inputName, role: strtolower('ADMIN'));

名前付き引数どうしであれば、書く順番は関数の宣言順と一致していなくても構いません。role を先に書いても name を先に書いても、PHP は引数名を見て正しい位置に割り当てます。なお、引数名に PHP の予約語(class、function、for など)が使われている場合も、そのまま class: 'btn' のように書けます。

位置引数と混ぜるときのルール

すべてを名前で書く必要はありません。第1引数のように意味が明らかなものは位置引数のまま渡し、分かりにくいものだけ名前を付ける、という混在した書き方ができます。ただしひとつだけ守るべき順序のルールがあります。名前付き引数は、必ず位置引数より後ろに置くことです。

index.php
<?php
function createUser(
    string $name,
    string $role = 'member',
    bool $isActive = true,
    bool $sendMail = false
): void {
    // 省略
}

// OK:位置引数 → 名前付き引数 の順
createUser('たろう', sendMail: true);
createUser('たろう', 'admin', sendMail: true);

// NG:名前付き引数のあとに位置引数を書いている
createUser(name: 'たろう', 'admin');
// Fatal error: Cannot use positional argument after named argument

このルールは、位置引数が「何番目か」で意味が決まることを考えれば納得できます。名前付き引数をあいだに挟むと、そのあとの位置引数が何番目にあたるのか決められなくなるためです。Cannot use positional argument after named argument はコンパイル時に検出されるエラーなので、その行が実行されるかどうかに関係なく、ファイルを読み込んだ時点で止まります。

デフォルト値のある引数を飛ばして指定する

名前付き引数がいちばん効果を発揮するのが、デフォルト値を持つ引数がたくさん並んでいる標準関数を呼ぶときです。代表例が htmlspecialchars() です。シグネチャは次のようになっています。

signature.php
<?php
htmlspecialchars(
    string $string,
    int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401,
    ?string $encoding = null,
    bool $double_encode = true
): string

第4引数の $double_encode は、文字列の中にすでに &amp; のような HTML エンティティが含まれているとき、それをさらに変換するかどうかの設定です。すでにエスケープ済みの文字列を二重にエスケープしたくないときは false にします。しかしこれを位置引数だけで指定しようとすると、興味のない第2・第3引数まで書かなければなりません。

index.php
<?php
$string = '&amp; <b>太字</b>';

// 位置引数だけの場合:デフォルト値をわざわざ書き写すことになる
echo htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8', false);

// 名前付き引数なら、変えたい引数だけ指定すれば済む
echo htmlspecialchars($string, double_encode: false);

前者の書き方には隠れた危険があります。$flags のデフォルト値は PHP 8.1 で ENT_COMPAT から ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 に変わりました。デフォルト値を手で書き写すと、その値に固定されてしまい、PHP 側の改善を受け取れません。名前付き引数で必要な引数だけを指定しておけば、残りは常にその時点のデフォルト値が使われます。

ほかにも、後ろのほうに設定用の引数を持つ標準関数はたくさんあります。よく使うものをいくつか挙げます。

名前付き引数での呼び出し意味
htmlspecialchars($s, double_encode: false)既存のエンティティを二重変換しない
array_slice($array, 1, preserve_keys: true)切り出した要素の数値キーを保持する
json_decode($json, associative: true)オブジェクトではなく連想配列で受け取る
str_pad('7', length: 3, pad_string: '0', pad_type: STR_PAD_LEFT)'007' のように左側を埋める
number_format($price, decimals: 2)小数点以下2桁で桁区切りする
array_fill(start_index: 0, count: 100, value: 50)キー0から100個、値50の配列を作る

引数名は自分で決められるものではなく、マニュアルに書かれている名前と完全に一致していなければなりません。str_contains() なら $haystack と $needle、array_slice() なら $array・$offset・$length・$preserve_keys です。うろ覚えで書かず、php.net の関数ページで確認するのが確実です。

真偽値のフラグ引数が読めるようになる

コードレビューで指摘されがちなのが、いわゆるブーリアントラップです。true や false をそのまま引数に並べると、呼び出し側だけを見ても何のオン・オフなのか判断できません。名前付き引数は、この問題をほぼそのまま解決します。

index.php
<?php
// 読み手には true / true が何を指すのか分からない
setcookie('token', $value, 0, '/', '', true, true);

// 名前付き引数なら、どの設定を有効にしたのかが一目で分かる
setcookie('token', $value, secure: true, httponly: true);

// JSON も同様。true だけでは「何が true なのか」が伝わらない
$data = json_decode($json, true);
$data = json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR);

これまでは、フラグが増えてきたら連想配列で $options を受け取るように設計を変える、という対処がよく取られていました。しかし連想配列にすると、引数の型宣言もデフォルト値の宣言も効かなくなり、キーのタイポが実行時まで見つかりません。名前付き引数なら、通常の引数として型宣言とデフォルト値を保ったまま、呼び出し側の読みやすさだけを手に入れられます。オプション配列を導入する前に、まず名前付き引数で足りないかを検討してみてください。

可変長引数(…)と組み合わせる

...$args で受け取る可変長引数と名前付き引数は、きれいに噛み合います。関数に宣言されていない引数名を名前付きで渡すと、その名前をキーにして可変長引数の配列に入ります。HTML 属性のように「キーと値のペアが何組来るか分からない」データを渡す場面で便利です。

tag.php
<?php
function tag(string $name, string $content, string ...$attributes): string
{
    $attr = '';
    foreach ($attributes as $key => $value) {
        // $key に名前付き引数の「名前」が入る
        $attr .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
    }
    return "<{$name}{$attr}>{$content}</{$name}>";
}

echo tag('a', 'webool について', href: '/about', class: 'nav-link');
// <a href="/about" class="nav-link">webool について</a>

href も class も tag() の宣言には存在しませんが、エラーにはならず $attributes に ['href' => '/about', 'class' => 'nav-link'] として収まります。string ...$attributes と型を書いておけば、値が文字列かどうかのチェックも効きます。

文字列キーの配列をスプレッドで渡す

渡したい引数がすでに連想配列になっているときは、スプレッド演算子 ... で展開できます。文字列キーは、そのまま名前付き引数として解釈されます。

tag.php
<?php
$attributes = [
    'href'   => '/contact',
    'class'  => 'nav-link',
    'target' => '_blank',
];

echo tag('a', 'お問い合わせ', ...$attributes);
// <a href="/contact" class="nav-link" target="_blank">お問い合わせ</a>

// 展開したあとに名前付き引数を足すことも可能(PHP 8.1.0 以降)
echo tag(...['name' => 'span', 'content' => 'NEW'], class: 'badge');
// <span class="badge">NEW</span>

展開した引数のあとに名前付き引数を書けるのは PHP 8.1.0 以降です。このとき、展開済みの引数を名前付き引数で上書きすることはできません。tag(...['name' => 'span'], name: 'div') のように同じ引数を二重に渡すと Named parameter $name overwrites previous argument というエラーになります。

オブジェクトを生成するときこそ効いてくる

名前付き引数は関数だけでなく、メソッド呼び出しや new によるインスタンス生成でも使えます。とくに PHP 8.0 で同時に追加されたコンストラクタプロモーション(コンストラクタの引数宣言にアクセス修飾子を書くと、同名のプロパティが自動的に定義される記法)と組み合わせると、値オブジェクトの生成がかなり書きやすくなります。

Button.php
<?php
final class Button
{
    public function __construct(
        public readonly string $label,
        public readonly string $type = 'button',
        public readonly bool $disabled = false,
        public readonly ?string $icon = null,
    ) {}
}

// 位置引数だと、icon を指定するために type と disabled も書く必要がある
$a = new Button('送信', 'submit', false, 'check');

// 名前付き引数なら、必要なものだけを選んで渡せる
$b = new Button(label: '送信', type: 'submit', icon: 'check');

// 無効化されたボタンが欲しいだけ、というときも1行で済む
$c = new Button(label: '送信できません', disabled: true);

プロパティ名と引数名がそのまま対応するので、new Button(label: '送信', disabled: true) という呼び出しは、ほとんど設定ファイルを書いているような読み心地になります。プロパティが増えても呼び出し側の見た目が壊れないのも利点です。

エラーになるケース

名前付き引数で起きるエラーは3種類しかありません。メッセージを覚えておけば原因はすぐ特定できます。

エラーメッセージ原因
Cannot use positional argument after named argument名前付き引数のあとに位置引数を書いた
Named parameter $x overwrites previous argument同じ引数を位置と名前の両方、または名前で2回渡した
Unknown named parameter $xその関数に存在しない引数名を指定した

同じ引数を二重に渡してしまう

位置引数と名前付き引数を混ぜていると起きやすいのがこれです。位置引数として渡したものと同じ引数を、あとから名前付きでも指定すると重複になります。

index.php
<?php
function foo($a, $b = 2, $c = 3) {}

foo(1, a: 5);        // Error: Named parameter $a overwrites previous argument
foo(a: 1, a: 2);     // Error: Named parameter $a overwrites previous argument

後から書いたほうで上書きされる、という挙動にはなりません。位置引数の 1 はすでに $a に割り当て済みなので、そこへ名前付きで $a を渡すのは矛盾した指定として Error が投げられます。こちらは実行時に発生する Error 例外なので、try / catch で捕まえることもできます。

引数名が思っていたものと違う

Unknown named parameter は、多くの場合ただのタイポですが、標準関数の引数名が想像と違っていたというケースもよくあります。setcookie() の第3引数は $expires ではなく $expires_or_options ですし、json_decode() の第2引数は $assoc ではなく $associative です。名前付き引数を使うときは、必ずマニュアルのシグネチャを確認してください。

また、array_map() や printf() のように引数を可変長で受け取る標準関数では、そもそも名前を付けて渡す意味がありません。こうした関数は位置引数で呼びます。

引数名は公開 API の一部になる

名前付き引数を導入して初めて意識することになるのが、この点です。PHP 8.0 より前、引数名は関数の内側だけで使われる「実装の詳細」でした。$str を $string にリネームしても、呼び出し側には何の影響もなかったのです。ところが名前付き引数があると、呼び出し側が引数名に依存するようになります。

つまり、公開している関数やメソッドの引数名を変更することは、後方互換性を壊す変更です。ライブラリや共有のユーティリティを書いているなら、引数名は関数名と同じくらい慎重に決め、一度公開したら安易に変えないようにしてください。逆に、プライベートなメソッドやクロージャの引数名は自由に変えて構いません。

継承やインターフェース実装での引数名

もうひとつ、継承したときの落とし穴があります。親クラスやインターフェースと違う引数名でメソッドを実装しても、PHP はエラーを出しません。しかし名前付き引数で呼ばれたとたんに壊れます。

Greeter.php
<?php
class Greeter
{
    public function greet(string $name): string
    {
        return "こんにちは、{$name}さん";
    }
}

class LoudGreeter extends Greeter
{
    // 引数名を $userName に変えてしまった(定義自体はエラーにならない)
    public function greet(string $userName): string
    {
        return "こんにちは、{$userName}さん!!";
    }
}

function sayHello(Greeter $greeter): string
{
    // Greeter 型として呼んでいるので name: と書くのは自然
    return $greeter->greet(name: 'たろう');
}

echo sayHello(new Greeter());       // こんにちは、たろうさん
echo sayHello(new LoudGreeter());   // Error: Unknown named parameter $name

sayHello() は Greeter 型として受け取っているので、Greeter の宣言どおり name: と書いています。ところが渡されたのが LoudGreeter だと、その実装には $name という引数が存在しないためエラーになります。オーバーライドするメソッドやインターフェースの実装では、引数名も親と揃える。これを習慣にしておけば避けられます。PHPStan や Psalm などの静的解析ツールは、この不一致を警告してくれます。

まとめ

名前付き引数は PHP 8.0 で追加された、関数名(引数名: 値) の形で値を渡す書き方です。呼び出し側では $ を付けず、マニュアルどおりの引数名を書きます。名前付き引数どうしの順番は自由ですが、位置引数と混ぜるときは名前付き引数を必ず後ろに置くのがルールです。デフォルト値のある引数を飛ばして必要なものだけ指定できるので、htmlspecialchars($s, double_encode: false) のように、デフォルト値を書き写す必要がなくなります。

とくに効果が大きいのは、true や false が並んで意味が読めなくなっていた呼び出しと、デフォルト値の多いコンストラクタです。オプション用の連想配列を設計する代わりに、型宣言とデフォルト値を保ったまま読みやすさを得られます。可変長引数と組み合わせれば、宣言にない引数名は文字列キーとして ...$args に収まり、連想配列をスプレッドで渡すこともできます。

一方で、引数名が呼び出し側から参照される以上、公開する関数やメソッドの引数名はリネームできないという制約が生まれます。継承やインターフェース実装で引数名を変えてしまうと、名前付き引数で呼ばれたときだけ壊れるという見つけにくい不具合になります。引数名も API の一部だと考えて命名しておけば、名前付き引数は読みやすさを大きく底上げしてくれます。

参考ページ