PHPの関数について

## はじめに

日本システム開発株式会社の藤井といいます。
今回は、PHPの関数について、型宣言と引数の概要をまとめています。

### 型宣言

#### 概要

関数のパラメータや戻り値に対して、型を宣言することができます。
これによりパラメータが宣言した型であることが保証されます。
[型宣言で使用可能な型一覧](https://www.php.net/manual/ja/language.types.declarations.php)

```php
<?php
function sum(int $a, int $b): int
{
    return $a + $b;
}
echo sum(1, 2) . PHP_EOL; // 3
?>
```

#### NULLを許容する型宣言(PHP7.1.0)

型の宣言の前にクエスチョンマーク(?)をつけることで、NULLを許容する型宣言ができます。
こうすることで、パラメータはNULLも許容します。

```php
<?php
function sum(int $a, ?int $b): int
{
    $b = $b ?? 10;
    return $a + $b;
}
echo sum(1, 2) . PHP_EOL;    // 3
echo sum(1, null) . PHP_EOL; // 11
?>
```

#### 複合型(PHP8.0.0)

単一の型を組み合わせて複合型にすることができます。以下の2つの複合型が存在します。

- 単一の型の集合を表すunion型(PHP8.0.0)
- クラス/インターフェイスの交差型(PHP8.1.0)

##### union型(PHP8.0.0)

型をunionとして宣言すると、複数の異なる型を受け入れることができます。
unionは`T1|T2...`で宣言します。

```php
<?php
function sum(int $a, int|string $b): int
{
    return $a + $b;
}
echo sum(1, 2) . PHP_EOL;   // 3
echo sum(1, "2") . PHP_EOL; // 3
?>
```

##### 交差型(PHP8.1.0)

型を交差型として宣言すると、クラス/インターフェイスとして宣言された型を全て満たす値を受け入れます。

```php
<?php
interface I { public function sum(int $a, int $b): int; };
class A {};
class B extends A implements I {
    function sum(int $a, int $b): int
    {
        return $a + $b;
    }
};

function callSum(I&A $b): int
{
    return $b->sum(1, 2);
}
echo callSum(new B()) . PHP_EOL; // 3
?>
```

#### 戻り値のみで有効な型

##### void

関数が値を返さないことを示す戻り値の型です。

```php
<?php
function display(string $str): void
{
    echo $str . PHP_EOL;
}
display("Hello World"); // Hello World
?>
```

##### never(PHP8.1.0)

関数が戻ってこないことを示す戻り値の型です。
関数の中で`exit()`が呼び出される、例外がスローされる、無限ループに入るのいずれかであることを示します。

##### static(PHP8.0.0)

戻り値が、メソッドが呼び出されているクラスのインスタンスであることを示す戻り値の型です。

#### 厳密な型付け

デフォルトでは、誤った型の値を期待されたスカラー型の宣言に変換可能あれば、暗黙的に変換(coerciveモード)します。
ファイルごとに厳密な型付けを有効(strictモード)にすることができます。strictモードでは、型宣言に対応する値のみを受け入れ、それ以外の場合はTypeErrorをスローします。

厳密な型付けを有効にするには、`declare(strict_types=1);`と記載します。

厳密な型付けは、strictモードが有効になったファイルの内部から行われる関数呼び出しに適用されます。そのファイルで宣言された関数への呼び出しに対して適用されるわけではありません。
厳密な型付けが有効ではないファイルから、厳密な型付けが有効なファイルの関数を呼び出した場合は、呼び出し側のモード(coerciveモード)が使われ、値は暗黙的に変換されます。

#### unionと型の自動変換(PHP8.0.0)

厳密な型付けが有効になっていない場合に適用されます。
指定された値の型がunionに含まれていない場合、union内から次の順番で変換される型が決まります。

1. int
2. float
3. string
4. bool

```text
型宣言が int|string

値の型がunionに含まれている
値:42   パラメータ:42   // 自動変換されない
値:"42" パラメータ:"42" // 自動変換されない

値の型がunionに含まれていない
値:42.5         パラメータ:42   // floatはintに変換できるためintに自動変換
値:new Object() パラメータ:__toString()の戻り値 // intに変換できないためstringに自動変換
値:[]           パラメータ:TypeErrorがスロー    // int、stringに変換できないためTypeErrorがスロー
```

例外として、指定された値が数値形式の文字列でunionにintとfloatが含まれていた場合、文字列がint形式かfloat形式かで自動変換される型が決まります。
例外により、数値形式の文字列は上の順番に関係なく、intもしくはfloatに自動変換される。

```text
型宣言が int|float
値:"45"   パラメータ:45   // 文字列がint形式のためintに自動変換
値:"45.5" パラメータ:45.5 // 文字列がfloat形式のためfloatに自動変換
```

### 関数の引数

#### 値渡し

関数の値を渡すとデフォルトで値渡しになります。
関数内部で引数の値を変更しても、関数の外側では値が変化しません。

```php
<?php
function addString(string $str): void
{
    $str .= " World";
}
$str = "Hello";
addString($str);

echo $str . PHP_EOL; // Hello
?>
```

#### リファレンス渡し

関数内部で引数の値を変更したい場合に使います。
関数の引数名の前にアンパサンド(&)を付けると、リファレンス渡しになります。

```php
<?php
function addString(string &$str): void
{
    $str .= " World";
}
$str = "Hello";
addString($str);

echo $str . PHP_EOL; // Hello World
?>
```

#### デフォルト引数

引数にデフォルト値を定義します。デフォルト値は引数が指定されなかった場合に使われます。(オプションの引数などに定義します)
引数にNULLを指定した場合デフォルト値は代入されません。

```php
<?php
function display(?string $str = "Hello World"): void
{
    echo $str . PHP_EOL;
}
display();        // Hello World
display("hello"); // hello
display(null);    // ""
?>
```

#### 可変長引数

関数定義で`...`を付けると可変長の引数を受け入れます。

```php
<?php
function avg(...$nums): int|float
{
    $total = 0;
    foreach($nums as $num) {
        $total += $num;
    }
    return $total / count($nums);
}
echo avg(10, 11, 12) . PHP_EOL; // 11
?>
```

関数呼び出しで、配列などに`...`を付けると配列を展開して引数に指定します。

```php
<?php
function multiDisplay(string $str, int $cnt): void
{
    for ($i = 0; $i<$cnt; $i++) {
        echo $str . PHP_EOL;
    }
}
multiDisplay(...["Hello World", 3]);
?>
```

#### 名前付き引数(PHP8.0.0)

引数を位置ではなく、引数名で渡すことができるようになります。
オプションの引数が複数ある場合に、変更したい引数のみを名前付きで指定できます。

```php
<?php
$jsonStr = '{ "hoge": "hoge" }';
var_dump(json_decode($jsonStr, flags: JSON_THROW_ON_ERROR));
var_dump(json_decode($jsonStr, null, 512, JSON_THROW_ON_ERROR));
?>
```

## まとめ

- 引数と戻り値に型宣言が使える
- 型宣言の前に`?`を付けるとnullableになる
- `declare(strict_types=1);`を記載しないと値が自動変換される可能性がある
- 関数には値渡し、リファレンス渡しの2つの渡し方がある
- デフォルト引数、可変長引数、名前付き引数(PHP8.0.0)の機能が利用できる