PHP の関数は、function 名前(型 $引数 = デフォルト値): 戻り値の型 { return 値; } の形で書きます。 引数の型・デフォルト値・戻り値の型はどれも省略できますが、PHP 8.x では型を書いておくと、渡し間違いをその場でエラーとして知らせてくれるので、読みやすく直しやすいコードになります。

この記事では、関数の定義から、型宣言、戻り値型(void・never・nullable・union)、デフォルト値、可変長引数、参照渡し、名前付き引数、無名関数・アロー関数・第一級 callable 構文までを、PHP 8.4.21 で実際に動かした結果とエラー文つきで順に解説します。冒頭に書き方の早見表と対応バージョン表を置いたので、必要な書き方をすぐにコピーできます。

PHP 関数の書き方 早見表

書き方の一覧

やりたいこと 書き方 呼び出し例
基本の関数 function greet($name) { return "Hi, $name"; } greet('佐藤')
引数に型を付ける function subtotal(int $price, int $count) subtotal(1200, 3)
戻り値に型を付ける function subtotal(int $p, int $c): int 戻り値は必ず int
デフォルト値 function greet(string $name, string $greeting = 'こんにちは') greet('佐藤')
null も受け取る function find(?int $id) find(null)
null を返すことがある function findName(int $id): ?string 戻り値は string か null
複数の型を受け取る function toLabel(int|string $code) toLabel(7)、toLabel('A7')
何も返さない function logLine(string $m): void 戻り値は NULL
決して戻らない function stop(string $r): never 必ず例外か exit で終わる
可変長引数 function sum(int ...$numbers) sum(1, 2, 3)
配列を展開して渡す (呼び出し側で)sum(...$list) $list = [10, 20, 30]
参照渡し function addTax(int &$price) addTax($price) で元の変数が変わる
名前付き引数 (呼び出し側で)greet(greeting: 'やあ', name: '田中') 順番を入れ替えられる
無名関数 $f = function (int $p) use ($rate) { ... }; $f(1000)
アロー関数 $f = fn(int $p) => $p * 2; $f(1000)
第一級 callable 構文 $f = strlen(...); $f('hello')

対応バージョン表

機能 使えるバージョン 確認方法
スカラー型宣言(int・float・string・bool) PHP 7.0〜 PHP マニュアル「型宣言」の変更履歴を今回確認
戻り値の型宣言 PHP 7.0〜 同上
nullable 型(?int) PHP 7.1〜 同上
void PHP 7.1〜 同上
union 型(int|string)・mixed PHP 8.0〜 同上
never PHP 8.1〜 同上
null・false 単独の型、true 型 PHP 8.2〜 同上
引数なしの呼び出しで ArgumentCountError PHP 7.1〜 PHP マニュアル「関数の引数」を今回確認
呼び出し時の末尾カンマ PHP 7.3〜 同上
引数リスト(定義側)の末尾カンマ PHP 8.0〜 同上
名前付き引数 PHP 8.0〜 同上
任意引数の後に必須引数を置くと非推奨 PHP 8.0〜 同上
デフォルト値に new を書ける PHP 8.1〜 同上
配列の展開の後に名前付き引数 PHP 8.1〜 同上
型 $x = null の暗黙の nullable が非推奨 PHP 8.4〜 同上
可変長引数(...) PHP 5.6〜 一般に知られた仕様
アロー関数(fn) PHP 7.4〜 一般に知られた仕様
第一級 callable 構文(f(...)) PHP 8.1〜 一般に知られた仕様

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「PHP 関数の書き方|引数・デフォルト値・型宣言・戻り値を一覧で解説」

検証した環境と読み方

項目 内容
PHP 8.4.21(CLI)
実行方法 php -d display_errors=1 -d error_reporting=-1 ファイル名.php
表示の調整 エラー文に含まれるファイルのパスは、ファイル名だけに置き換えています

この記事のコードは、そのままファイルに保存して php ファイル名.php で実行できる形にしています。実行結果は、すべて上の環境で実際に出力されたものです。バージョンごとの対応表は、PHP マニュアルの「型宣言」と「関数の引数」のページを今回開いて確認した内容で、PHP 8.4 より前のバージョンでの動作そのものは実行していません。

関数の基本の書き方

<?php
function greet(string $name, string $greeting = 'こんにちは'): string
{
    return "{$greeting}、{$name}さん";
}

echo greet('佐藤'), PHP_EOL;
echo greet('鈴木', 'おはよう'), PHP_EOL;
echo greet(greeting: 'こんばんは', name: '田中'), PHP_EOL;
こんにちは、佐藤さん
おはよう、鈴木さん
こんばんは、田中さん
部品 例 意味
関数名 greet 英字かアンダースコアで始める。大文字と小文字は区別されない
引数の型 string $name 受け取れる値の種類
デフォルト値 $greeting = 'こんにちは' 省略されたときに使う値
戻り値の型 : string 返す値の種類
return return "..."; 値を返して関数を終える

1つの関数には1つの役割だけを持たせ、名前は「動詞+対象」にすると、呼び出し側が読みやすくなります。

知っておくと役立つ基本の性質

次の性質をまとめて確認しました。

<?php
// return を書かない関数は NULL を返す
function noRet() {}
var_dump(noRet());

// 関数の中から、外の変数は見えない
$site = 'memo';
function showSite() { return isset($site) ? 'あり' : 'なし'; }
echo showSite(), PHP_EOL;

// 定義より前の行からでも呼べる
echo later(), PHP_EOL;
function later(): string { return '後ろで定義しても呼べる'; }

// 関数名は大文字と小文字を区別しない
echo LATER(), PHP_EOL;

// static 変数は呼び出しをまたいで値を保つ
function nextId(): int { static $id = 0; return ++$id; }
echo nextId(), nextId(), nextId(), PHP_EOL;

// 再帰(自分自身を呼ぶ)
function fact(int $n): int { return $n <= 1 ? 1 : $n * fact($n - 1); }
echo fact(5), PHP_EOL;
NULL
なし
後ろで定義しても呼べる
後ろで定義しても呼べる
123
120
性質 結果 ポイント
return なし NULL 値を返さないことを示すなら : void を付ける
外の変数 見えない(「なし」) 必要な値は引数で渡す
定義の位置 後ろに書いても呼べる ファイルの読み込み時に関数が登録されるため
大文字と小文字 区別しない 定義したとおりの表記で呼ぶと読みやすい
同じ名前で2回定義 Fatal error 下のエラー文を参照

同じ名前の関数を2回定義すると、次の表示で止まりました。

Fatal error: Cannot redeclare function dup() (previously declared in 12_dup.php:2) in 12_dup.php on line 3

引数と戻り値に型を付ける(型宣言)

<?php
function subtotal(int $price, int $count = 1): int
{
    return $price * $count;
}
function findName(array $members, int $id): ?string
{
    return $members[$id] ?? null;
}
function toLabel(int|string $code): string
{
    return 'CODE-' . $code;
}
function logLine(string $message): void
{
    echo '[log] ', $message, PHP_EOL;
}
function stop(string $reason): never
{
    throw new RuntimeException($reason);
}

var_dump(subtotal(1200, 3));
var_dump(subtotal(1200));
var_dump(findName([1 => '佐藤'], 1));
var_dump(findName([1 => '佐藤'], 2));
var_dump(toLabel(7));
var_dump(toLabel('A7'));
var_dump(logLine('保存しました'));
try {
    stop('在庫がありません');
} catch (RuntimeException $e) {
    echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
}
int(3600)
int(1200)
string(6) "佐藤"
NULL
string(6) "CODE-7"
string(7) "CODE-A7"
[log] 保存しました
NULL
RuntimeException: 在庫がありません

使える型の一覧

型 書き方 受け取れる・返せるもの 使える場所
整数 int 整数 引数・戻り値
小数 float 浮動小数点数(int も受け取れる) 引数・戻り値
文字列 string 文字列 引数・戻り値
真偽値 bool true / false 引数・戻り値
配列 array 配列 引数・戻り値
クラス名 DateTimeImmutable など そのクラスのインスタンス 引数・戻り値
オブジェクト全般 object 任意のオブジェクト 引数・戻り値
呼び出せるもの callable 関数名の文字列、クロージャなど 引数・戻り値
繰り返せるもの iterable 配列または Traversable 引数・戻り値
何でも mixed すべての値(null を含む) 引数・戻り値
nullable ?int int または null 引数・戻り値
union int|string どちらかの型 引数・戻り値
void void 何も返さない 戻り値のみ
never never 戻らない(例外か終了) 戻り値のみ
static static 呼び出されたクラスのインスタンス 戻り値のみ(メソッド)

戻り値型 void・never・nullable・union の違い

戻り値型 意味 関数の中で書けること 守らないとどうなるか(実行結果)
void 値を返さない return; または return なし Fatal error: A void function must not return a value
never 呼び出し元に戻らない throw や exit で必ず終わる TypeError: n(): never-returning function must not implicitly return
?string string か null を返す return null; も可 return を書かないと Return value must be of type ?string, none returned
string|false string か false を返す 見つからないときに return false; 既定のモードで int の 1 を返すと、文字列の "1" に変換された
int 必ず int を返す return 整数; TypeError: noReturn(): Return value must be of type int, none returned

void は「返す値がない」、never は「そもそも戻ってこない」という違いです。 void の関数を var_dump すると NULL が表示されますが、never の関数は例外を投げるか処理を終えるので、呼び出しの次の行には進みません。

union 型で「見つからないときは false」を表す例と、mixed の例も確認しました。

<?php
function ratio(int|float $a, int|float $b): float { return $a / $b; }
var_dump(ratio(1, 4));

function find(int $id): string|false { return $id === 1 ? '佐藤' : false; }
var_dump(find(2));

function typeName(mixed $v): string { return get_debug_type($v); }
echo typeName(1), ' ', typeName('a'), ' ', typeName(null), ' ', typeName([1]), ' ', typeName(1.5), PHP_EOL;
float(0.25)
bool(false)
int string null array float

なお、?int|string のように ? と union を組み合わせる書き方はできません。実行すると構文エラーになりました。null を含めたいときは int|string|null と書きます。

Parse error: syntax error, unexpected token "|", expecting variable in 16_nu.php on line 2

strict_typesの有無で結果が変わる例

PHP の型宣言には2つのモードがあります。既定のモード(型の自動変換あり)では、渡された値を宣言した型に変換できるなら変換して受け取ります。 ファイルの先頭に declare(strict_types=1); を書くと、変換をせず、型が合わなければ TypeError になります。

同じ関数に同じ値を渡して、両方のモードで比べました。

<?php
// declare(strict_types=1);   ← この行の有無だけを変えて2回実行
function subtotal(int $price, int $count): int
{
    return $price * $count;
}
function half(float $n): float { return $n / 2; }
function count2(): int { return '3'; }

// 以下を1つずつ try〜catch (TypeError $e) で囲んで実行
subtotal('1200', 3);
subtotal(1200.0, 3);
subtotal(true, 3);
subtotal(1200.5, 3);
subtotal('12a', 3);
subtotal(null, 3);
half(5);
count2();
渡した値 strict_types なし(既定) strict_types=1
subtotal('1200', 3) int(3600)(文字列が数値に変換される) TypeError(string given)
subtotal(1200.0, 3) int(3600) TypeError(float given)
subtotal(true, 3) int(3)(true が 1 になる) TypeError(true given)
subtotal(1200.5, 3) Deprecated を出したうえで int(3600) TypeError(float given)
subtotal('12a', 3) TypeError(string given) TypeError(string given)
subtotal(null, 3) TypeError(null given) TypeError(null given)
half(5)(float に int を渡す) float(2.5) float(2.5)
count2()(int を返す関数が '3' を返す) int(3) TypeError(string returned)

実際に表示されたメッセージは次のとおりです。

(strict_types なしで subtotal(1200.5, 3) を実行)
Deprecated: Implicit conversion from float 1200.5 to int loses precision in 03_weak.php on line 2
int(3600)

(strict_types=1 で subtotal('1200', 3) を実行)
TypeError: subtotal(): Argument #1 ($price) must be of type int, string given, called in 04_strict.php on line 7

(strict_types=1 で count2() を実行)
TypeError: count2(): Return value must be of type int, string returned

この結果から読み取れる要点は4つです。

  • 既定のモードでは '1200' や true が黙って int に変換される。 画面のフォームから届く値は文字列なので便利な反面、意図しない値が通ることもある
  • 小数部分のある float を int に渡すと、既定のモードでは非推奨の通知とともに切り捨てられる(1200.5 が 1200 として計算された)
  • strict_types=1 では、型が一致しない値はすべて TypeError になる。 例外は「float の引数に int を渡す」場合だけで、これは両方のモードで通る
  • '12a' や null は、どちらのモードでも TypeError になる(nullable ではない型に null は渡せない)

PHP マニュアルの「型宣言」のページによると、strict_types はその宣言を書いたファイルの中からの関数呼び出しに効きます。関数を定義したファイルではなく、呼び出す側のファイルの宣言で決まる点が要点です。新しく書くファイルでは先頭に declare(strict_types=1); を置き、外から届いた文字列は (int) や filter_var() で明示的に変換してから渡す、という進め方がおすすめです。

デフォルト値の書き方と、置く順番

デフォルト値は $引数 = 値 の形で書き、呼び出し側が省略したときに使われます。

<?php
function createUser(string $name, int $age = 20, bool $admin = false, string $lang = 'ja'): string
{
    return sprintf('%s / %d / %s / %s', $name, $age, $admin ? 'admin' : 'user', $lang);
}
echo createUser('佐藤'), PHP_EOL;   // 佐藤 / 20 / user / ja
デフォルト値に書けるもの 例
数値・文字列・真偽値・null $count = 1、$lang = 'ja'、$id = null
配列 $options = []
定数・定数式 $limit = PHP_INT_MAX、$size = 10 * 2
new 式(PHP 8.1〜) $at = new DateTimeImmutable('2026-10-03')

new をデフォルト値に書く例も動かしました。

<?php
function stamp(DateTimeImmutable $at = new DateTimeImmutable('2026-10-03')): string
{
    return $at->format('Y-m-d');
}
echo stamp(), PHP_EOL;   // 2026-10-03

任意引数の後に必須引数を置くと、非推奨の警告が出る

デフォルト値のある引数(任意引数)は、必須引数の後ろに並べます。 逆の順番にするとどうなるかを確認しました。

<?php
function makeLabel($prefix = 'ID', $number)
{
    return $prefix . '-' . $number;
}
echo makeLabel('NO', 5), PHP_EOL;
try {
    echo makeLabel(number: 5), PHP_EOL;
} catch (Error $e) {
    echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
}
Deprecated: makeLabel(): Optional parameter $prefix declared before required parameter $number is implicitly treated as a required parameter in 06_deprecated.php on line 2
NO-5
ArgumentCountError: makeLabel(): Argument #1 ($prefix) not passed

非推奨(Deprecated)の通知が出て、$prefix のデフォルト値は使われず、必須引数として扱われます。 名前付き引数で number だけを渡しても、$prefix が渡されていないという ArgumentCountError になりました。

直す前 直した後
function makeLabel($prefix = 'ID', $number) function makeLabel($number, $prefix = 'ID')

型 $x = null は ?型 と書く

PHP 8.4 では、nullable ではない型に = null を付ける書き方にも通知が出ました。

<?php
function setOwner(string $owner = null) { return var_export($owner, true); }
echo setOwner(), PHP_EOL;
Deprecated: setOwner(): Implicitly marking parameter $owner as nullable is deprecated, the explicit nullable type must be used instead in 06_deprecated.php on line 9
NULL
直す前 直した後
function setOwner(string $owner = null) function setOwner(?string $owner = null)

TypeErrorとArgumentCountErrorを再現する

引数の数や型が合わないときに出るエラーを、まとめて再現しました。

<?php
function subtotal(int $price, int $count): int
{
    return $price * $count;
}
try { subtotal(1200); }      catch (ArgumentCountError $e) { echo get_class($e), ': ', $e->getMessage(), PHP_EOL; }
try { subtotal('abc', 2); }  catch (TypeError $e)          { echo get_class($e), ': ', $e->getMessage(), PHP_EOL; }
var_dump(subtotal(1200, 2, 99));
try { strlen(); }            catch (ArgumentCountError $e) { echo get_class($e), ': ', $e->getMessage(), PHP_EOL; }
try { strlen('a', 'b'); }    catch (ArgumentCountError $e) { echo get_class($e), ': ', $e->getMessage(), PHP_EOL; }
var_dump($e instanceof TypeError);
ArgumentCountError: Too few arguments to function subtotal(), 1 passed in 05_errors.php on line 6 and exactly 2 expected
TypeError: subtotal(): Argument #1 ($price) must be of type int, string given, called in 05_errors.php on line 7
int(2400)
ArgumentCountError: strlen() expects exactly 1 argument, 0 given
ArgumentCountError: strlen() expects exactly 1 argument, 2 given
bool(true)
状況 自分で定義した関数 組み込み関数(strlen など)
引数が足りない ArgumentCountError(Too few arguments) ArgumentCountError
引数が多い エラーにならず、余った引数は使われない(int(2400)) ArgumentCountError(expects exactly 1 argument, 2 given)
型が合わない TypeError TypeError

最後の bool(true) が示すとおり、ArgumentCountError は TypeError の一種です。 catch (TypeError $e) と書けば両方をまとめて受け取れます。

try〜catch で受け取らない場合は、Fatal error として次のように表示されます。

Fatal error: Uncaught TypeError: subtotal(): Argument #1 ($price) must be of type int, string given, called in 05_errors.php on line 18 and defined in 05_errors.php:2
Stack trace:
#0 05_errors.php(18): subtotal()
#1 {main}
  thrown in 05_errors.php on line 2

エラー文の読み方

subtotal(): Argument #1 ($price) must be of type int, string given, called in 05_errors.php on line 18
────┬────  ─────┬───── ───┬───                 ─┬─  ─────┬─────            ──────────┬───────────
    │           │         │                     │        │                          └ 呼び出した場所
    │           │         │                     │        └ 実際に渡された型
    │           │         │                     └ 宣言されている型
    │           │         └ 引数の名前
    │           └ 何番目の引数か
    └ 関数名

「called in」の後ろのファイル名と行番号が、直す場所です。 関数の定義側ではなく、呼び出している行を見にいきます。

可変長引数で、いくつでも受け取る

引数の前に ... を付けると、渡された値をまとめて配列として受け取れます。

<?php
function sum(int ...$numbers): int
{
    return array_sum($numbers);
}
var_dump(sum());
var_dump(sum(1, 2, 3));

$list = [10, 20, 30];
var_dump(sum(...$list));          // 配列を展開して渡す

function tagLine(string $sep, string ...$tags): string
{
    return implode($sep, $tags);
}
echo tagLine(' / ', 'PHP', '関数', '引数'), PHP_EOL;

try {
    sum(1, 'x');
} catch (TypeError $e) {
    echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
}
int(0)
int(6)
int(60)
PHP / 関数 / 引数
TypeError: sum(): Argument #2 must be of type int, string given, called in 07_variadic.php on line 21
場所 書き方 意味
定義側 function sum(int ...$numbers) 残りの引数をまとめて配列 $numbers で受け取る
呼び出し側 sum(...$list) 配列 $list の要素を1つずつの引数として渡す

可変長引数に付けた型(int)は、渡された値の1つ1つに対して確認されます。また、可変長引数は引数リストの最後にだけ置けます。途中に置くと次の表示になりました。

Fatal error: Only the last parameter can be variadic in 15_var.php on line 2

文字列キーの配列を展開したり、名前付き引数で渡したりすると、可変長引数はキーつきの配列として受け取ります。

<?php
function show(...$args) { var_dump($args); }
show(a: 1, b: 2);
array(2) {
  ["a"]=>
  int(1)
  ["b"]=>
  int(2)
}

型宣言のない時代から使われている func_get_args() も、現在の PHP で動きます(legacy(1, 2, 3) で 3:1,2,3 を確認)。新しく書くコードでは、型を付けられる ... のほうが読みやすくなります。

参照渡しで、呼び出し元の変数を書き換える

通常の引数は値渡しで、関数の中で引数を書き換えても呼び出し元の変数は変わりません。引数の前に & を付けると参照渡しになり、呼び出し元の変数そのものを書き換えます。

<?php
function addTaxByValue(int $price): void { $price = (int)($price * 1.1); }
function addTaxByRef(int &$price): void { $price = (int)($price * 1.1); }

$a = 1000; addTaxByValue($a); var_dump($a);
$b = 1000; addTaxByRef($b);  var_dump($b);

function addItem(array &$cart, string $item): void { $cart[] = $item; }
$cart = ['ペン'];
addItem($cart, 'ノート');
print_r($cart);
int(1000)
int(1100)
Array
(
    [0] => ペン
    [1] => ノート
)

値渡しの $a は 1000 のまま、参照渡しの $b は 1100 に変わりました。配列も既定では値渡しなので、関数の中で要素を追加したいときは & を付けるか、追加後の配列を return で返します。

オブジェクトを渡したときの動き

オブジェクトは & を付けなくても、関数の中でプロパティを変更すると呼び出し元に反映されます。

<?php
function renameItem(object $o): void { $o->name = '変更後'; }
function replaceItem(object $o): void { $o = new stdClass; $o->name = '差し替え'; }

$obj = new stdClass; $obj->name = '変更前';
renameItem($obj);  var_dump($obj->name);
replaceItem($obj); var_dump($obj->name);
string(9) "変更後"
string(9) "変更後"

プロパティの変更は反映されますが、引数に別のオブジェクトを代入しても、呼び出し元の変数は元のオブジェクトを指したままです。オブジェクトの場合は「同じオブジェクトを指す値」がコピーされて渡される、と考えるとこの結果を説明できます。

渡すもの & なし & あり
数値・文字列・配列 コピーが渡る。元は変わらない 元の変数が変わる
オブジェクト プロパティの変更は反映される。代入し直しは反映されない 代入し直しも反映される

参照渡しには変数を渡す

参照渡しの引数に、変数ではなく値そのものを渡すと、次の表示で止まりました。

$ addTaxByRef(1000);
Fatal error: Uncaught Error: addTaxByRef(): Argument #1 ($price) could not be passed by reference in 08_ref.php:21
判断の目安 おすすめ
計算結果を1つ返したい return で返す
複数の値を返したい 配列で返して [$min, $max] = minMax($nums); で受け取る
大きな配列をその場で並べ替えたい・書き換えたい 参照渡し(sort() などの組み込み関数と同じ形)

複数の値を返す例は次のとおりです。

<?php
function minMax(array $nums): array { return [min($nums), max($nums)]; }
[$min, $max] = minMax([3, 9, 1]);
echo "$min / $max", PHP_EOL;   // 1 / 9

まずは return で返す形を選び、参照渡しは「その場で書き換える」ことが名前から伝わる関数に絞ると、呼び出し側が読みやすくなります。

名前付き引数で、必要な引数だけを渡す

PHP 8.0 から、引数名: 値 の形で引数を渡せます。途中のデフォルト値を飛ばして、後ろの引数だけを指定できるのがいちばんの利点です。

<?php
function createUser(string $name, int $age = 20, bool $admin = false, string $lang = 'ja'): string
{
    return sprintf('%s / %d / %s / %s', $name, $age, $admin ? 'admin' : 'user', $lang);
}
echo createUser('佐藤', lang: 'en'), PHP_EOL;           // 位置引数 + 名前付き
echo createUser(admin: true, name: '鈴木'), PHP_EOL;    // 順番を入れ替え
echo createUser('田中', 30, lang: 'en'), PHP_EOL;

$args = ['name' => '高橋', 'admin' => true];
echo createUser(...$args), PHP_EOL;                     // 連想配列を展開
echo createUser(...['伊藤', 41], lang: 'en'), PHP_EOL;  // 展開の後に名前付き(8.1〜)

echo str_pad(string: '7', length: 3, pad_string: '0', pad_type: STR_PAD_LEFT), PHP_EOL;
佐藤 / 20 / user / en
鈴木 / 20 / admin / ja
田中 / 30 / user / en
高橋 / 20 / admin / ja
伊藤 / 41 / user / en
007

組み込み関数(str_pad)にも使えます。true や false が並ぶ呼び出しでは、何を指定しているのかが読み取りやすくなります。

名前付き引数のルールとエラー文

書き方 結果(実行して確認)
createUser(nmae: '佐藤')(名前の打ち間違い) Error: Unknown named parameter $nmae
createUser('佐藤', name: '鈴木')(同じ引数を2回) Error: Named parameter $name overwrites previous argument
createUser(age: 30)(必須引数がない) ArgumentCountError: createUser(): Argument #1 ($name) not passed
createUser(name: '佐藤', 30)(名前付きの後に位置引数) Fatal error: Cannot use positional argument after named argument

位置で渡す引数を先に、名前付き引数を後に書く、という順番だけ覚えておけば十分です。 引数名が呼び出し側のコードに現れるようになるので、公開している関数の引数名を変えるときは、呼び出し側も一緒に直します。

無名関数・アロー関数・第一級callable構文

関数を変数に入れたり、ほかの関数に渡したりする書き方です。

<?php
$rate = 0.1;

// 無名関数(クロージャ):外の変数は use で取り込む
$withTax = function (int $price) use ($rate): int {
    return (int) round($price * (1 + $rate));
};
var_dump($withTax(1000));

// アロー関数:外の変数を自動で取り込む
$withTax2 = fn(int $price): int => (int) round($price * (1 + $rate));
var_dump($withTax2(1000));

// 定義した後で外の変数を変えても、取り込んだ値は変わらない
$rate = 0.5;
var_dump($withTax(1000));
var_dump($withTax2(1000));
int(1100)
int(1100)
int(1100)
int(1100)

無名関数の use もアロー関数も、定義した時点の値をコピーして取り込みます。 後から $rate を 0.5 に変えても、結果は 1100 のままでした。

外の変数を書き換えたいとき

<?php
// use (&$var) で参照として取り込むと、外の変数を書き換えられる
$total = 0;
$add = function (int $n) use (&$total): void { $total += $n; };
$add(5); $add(7);
var_dump($total);

// アロー関数の中で外の変数を変更しても、外には反映されない
$count = 0;
$inc = fn() => $count++;
$inc(); $inc();
var_dump($count);

// use を書かずに外の変数を使うと
$rate = 0.1;
$noUse = function () { return $rate; };
var_dump($noUse());
int(12)
int(0)
Warning: Undefined variable $rate in 10_closure.php on line 32
NULL
比べる点 無名関数 function () use (...) { } アロー関数 fn() => 式
書ける処理 複数の文 式を1つだけ(その値が戻り値になる)
外の変数の取り込み use ($a, $b) で明示する 使った変数を自動で取り込む
取り込み方 値のコピー(use (&$a) で参照にできる) 値のコピーのみ
外の変数の書き換え use (&$a) でできる できない(結果は int(0))
use を書き忘れると Warning: Undefined variable 起きない(自動で取り込むため)
向いている場面 数行の処理、外の変数を更新する処理 array_map などに渡す短い変換

コールバックとして渡す

<?php
$prices = [100, 250, 400];
print_r(array_map(fn($p) => $p * 2, $prices));
print_r(array_filter($prices, fn($p) => $p >= 250));
usort($prices, fn($a, $b) => $b <=> $a);
print_r($prices);
Array ( [0] => 200 [1] => 500 [2] => 800 )
Array ( [1] => 250 [2] => 400 )
Array ( [0] => 400 [1] => 250 [2] => 100 )

(print_r の出力は、横幅を抑えるために1行にまとめて表示しています。)array_filter の結果はキーが元のまま(1 と 2)残ります。0 から振り直したいときは array_values() を通します。

第一級callable構文:既存の関数をそのまま値にする

PHP 8.1 から、関数名の後ろに (...) を付けると、その関数を Closure オブジェクトとして取り出せます。

<?php
$len = strlen(...);
var_dump($len('hello'));
var_dump($len instanceof Closure);

function double(int $n): int { return $n * 2; }
print_r(array_map(double(...), [1, 2, 3]));

class Cart {
    public function __construct(private float $rate) {}
    public function withTax(int $p): int { return (int) round($p * (1 + $this->rate)); }
    public static function label(int $p): string { return $p . '円'; }
}
$cart = new Cart(0.1);
print_r(array_map($cart->withTax(...), [100, 200]));
print_r(array_map(Cart::label(...), [100, 200]));
int(5)
bool(true)
Array ( [0] => 2 [1] => 4 [2] => 6 )
Array ( [0] => 110 [1] => 220 )
Array ( [0] => 100円 [1] => 200円 )
対象 文字列・配列で渡す書き方 第一級 callable 構文
関数 'double' double(...)
インスタンスメソッド [$cart, 'withTax'] $cart->withTax(...)
静的メソッド 'Cart::label' または ['Cart', 'label'] Cart::label(...)

文字列で渡す書き方(array_map('double', [6]))も引き続き動きます。違いが出るのは、名前を打ち間違えたときです。

(array_map('doubel', [1]) を実行)
TypeError: array_map(): Argument #1 ($callback) must be a valid callback or null, function "doubel" not found or invalid function name

($x = doubel(...); を実行)
Error: Call to undefined function doubel()

文字列は「使われた場所」で初めてエラーになりますが、(...) は書いた行でエラーになるので、原因の場所がすぐにわかります。 エディタの補完や名前の一括変更も効きやすくなります。

関数を書くときのチェックリスト

確認すること よい状態
引数と戻り値に型が付いているか すべての引数と戻り値に型がある
null を受け取る・返すか ?型 または 型|null と明示している
引数の並び 必須引数が先、任意引数が後、可変長引数が最後
ファイルの先頭 declare(strict_types=1); がある
値を返さない関数 : void を付けている
参照渡し その場で書き換えることが関数名から伝わる
真偽値の引数が並ぶ呼び出し 名前付き引数で意味がわかるようにしている
コールバック 短い変換はアロー関数、既存の関数は (...) で渡している

よくある質問

Q. 型宣言は必ず書いたほうがよいですか? A. 書くことをおすすめします。型があると、渡し間違いが TypeError として呼び出した行つきで表示されるので、原因を探す時間が短くなります。どんな値でも受け取る引数には mixed と書いておくと、「型を書き忘れたのではなく、何でも受け取る」という意図が伝わります。

Q. strict_types=1 は、既存のファイルにも入れたほうがよいですか? A. 入れると、これまで自動変換で通っていた呼び出し('1200' を int の引数に渡すなど)が TypeError になります。今回の比較表のとおり影響する箇所が決まっているので、新しく書くファイルから入れ、既存のファイルはテストを用意したうえで1ファイルずつ進めるのがおすすめです。

Q. 関数の中から外の変数を使うにはどうすればよいですか? A. 引数として渡すのが基本です。無名関数なら use ($変数)、アロー関数なら自動で取り込まれます。global $変数; という書き方もありますが、どこで値が変わるのかを追いにくくなるので、引数と戻り値でやり取りする形にすると読みやすくなります。

Q. デフォルト値のある引数を飛ばして、その後ろの引数だけ指定できますか? A. 名前付き引数を使えばできます。createUser('佐藤', lang: 'en') のように書くと、間の $age と $admin はデフォルト値が使われます。

まとめ

  • 関数は function 名前(型 $引数 = デフォルト値): 戻り値の型 { ... } の形で書く。型は省略できるが、書いておくと渡し間違いをその場で知らせてくれる
  • 戻り値型は、返さないなら void、戻らないなら never、null もあり得るなら ?型、複数の型なら union を使う
  • strict_types=1 の有無で、'1200' や true を int に渡したときの結果が変わる。float に int を渡す場合だけは両方で通る
  • 任意引数は必須引数の後ろに置く。逆にすると非推奨の通知が出て、デフォルト値は使われない
  • 引数の不足は ArgumentCountError、型の不一致は TypeError。エラー文の「called in」が直す場所
  • 可変長引数は ...$args、参照渡しは &$arg、名前付き引数は 名前: 値。位置引数を先に書く
  • 無名関数は use、アロー関数は自動で外の変数を取り込む。既存の関数は strlen(...) の形で値として渡せる

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「PHP 関数の書き方|引数・デフォルト値・型宣言・戻り値を一覧で解説」

株式会社RJCでは、PHPの関数の書き方や型宣言の使い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?