PHP8の新機能を使ってより良いコードを書こう！

サポートを受けられるPHPのバージョンが2023年以降8以降になりました。そこでこの記事ではバージョン8.0から使用できる10の新機能について、従来の機能と比較しながら紹介していきます！
![PHP supported version.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478883/50dac569-6c18-df73-0fa8-9a79e6f4ca25.png)

https://www.php.net/supported-versions.php


# 紹介する機能

## PHP8.0
- [新たな記述によるコンストラクタの実装](#新たな記述によるコンストラクタの実装)
- [名前付き引数](#名前付き引数)
- [Match式](#match式)
- [Union型](#union型)
- [Nullsafe演算子](#nullsafe演算子)

## PHP8.1
- [ENUM型](#enum型)
- [交差型](#交差型)
- [文字列キー配列のアンパック](#文字列キー配列のアンパック)

## PHP8.2
- [DNF(Disjunctive Normal Form)型](#dnfdisjunctive-normal-form型)
- [読み取り専用クラス](#読み取り専用クラス)

# PHP8.0
## 新たな記述によるコンストラクタの実装
```diff_php
class Construct
{
- PHP ~7.4
-    public string $greeting;
-    public string $lang;
-    public function __construct(string $greeting = 'hello! ', string $lang = 'PHP')
-    {
-        $this->greeting = $greeting;
-        $this->lang = $lang;
-    }
+ PHP 8.0~
+    public function __construct(public string $greeting = 'hello! ', public string $lang = 'PHP')
+    {
+    }
}

$construct = new Construct();
var_dump($construct->greeting.$construct->lang); // string(10) "hello! PHP"
```
機能
- コンストラクタの引数をオブジェクトのプロパティとして設定することができる
- プロパティのアクセス権（public, private, protected）の設定を引数内で行える

## 名前付き引数
```diff_php
- PHP ~7.4
- function calculate(int $first = 0, int $second = 0, int $third = 0)
- {
-    return $first + $second + $third;
- }
- var_dump(calculate(1, 2, 3)); // int(6)
- var_dump(calculate(1, 0, 3)); // int(4)
+ PHP 8.0~
+ function calculate(int $first = 0, int $second = 0, int $third = 0)
+ {
+     return $first + $second + $third;
+ }
+ var_dump(calculate(third: 1, second: 2, first: 3)); // int(6)
+ var_dump(calculate(first: 1, third: 3)); // int(4)
```
機能
- メソッドの引数に名前を付けられる（名前は変数設定時の変数名に依存する）
- 引数の名前と値が合致していれば引数の順序を守る必要がない
- デフォルトの値が設定されている引数は必要がなければメソッド実行時に値を入れる必要がない

:::note alert
名前付き引数を使用する際の注意点
```php
var_dump(calculate(1, second:2, third: 3)); // int(6)

// Error: Cannot use positional argument after named argument
var_dump(calculate(first: 1, 2, third: 3));
```
名前付き引数は部分的に導入することもできるが、その場合は名前付き引数の後の引数も名前付き引数でなくてはならない。
:::

## Match式
```diff_php
$number = 1;
- PHP ~7.4
- $result = '';
- switch($number) {
-     case 1:
-         $result = 'one';
-         break;
-     case 2:
-         $result = 'two';
-         break;
-     case 3:
-         $result = 'three';
-         break;
-     default :
-         $result = 'default';
-         break;
- }
+ PHP 8.0~
+ $result = match($number) {
+     1 => 'one',
+     2 => 'two',
+     3 => 'three',
+     default => 'default'
+ };

var_dump($result); // string(3) "one"
```
機能
- 条件分岐を文（switch）ではなく式（match）として記述でき、より短いコードで条件分岐を実装できる。
- 厳密な値の比較による条件分岐を実行できる
- defaultの未設定などで比較する値がMatch式内で見当たらない場合、エラーを発生させるためswitch文のdefaultの未設定による処理のすり抜けが発生しない

:::note alert
Match式を使用する際の注意点
式であるため、Match式内で値の代入などの式の実行などはできない。またMatch式内の値の比較は厳密な比較であるため、値の比較が緩やかな比較であるswitch文をMatch式に書き換える際は注意が必要。
:::

:::note info
Match式は引数にtrueを入れることで値の比較を緩やかな比較にすることが可能
```php
$number = '1';
$result = match(true) {
    1 == $number => 'one',
    2 == $number => 'two',
    3 == $number => 'three',
    default => 'default'
};

var_dump($result); // string(3) "one"
```
:::

## Union型
```diff_php
- PHP ~7.4
- /**
-  * @param float|int $number
-  */
- function union($number)
- {
-     $result = is_int($number) ? "int" : "float";
-     var_dump("number is ".$result);
- }
+ PHP8.0~
+ function union(float|int $number)
+ {
+    $result = is_int($number) ? "number" : "float";
+     var_dump("number is ".$result);
+ }

union(2.0); // string(15) "number is float"
```
機能
- PHPDoc のアノテーションを使用しなくても、値に複数の型を指定することが可能

## Nullsafe演算子

```diff_php
function nullsafe(object $user): void
{
- PHP ~7.4
-   $country = null;
-   if ($user !== null) {
-       $info = $user->info;
-       if ($info !== null) {
-           $address = $info->getAddress;
-           if ($address !== null) {
-               $country = $address;
-           }
-       }
-   }
+ PHP 8.0~
+   $country = $user?->info?->address;
    
    var_dump($country);
}

$user = new stdClass();
$user->info = new stdClass();
$user->info->getAddress = 'Japan';
nullsafe($user); // string(5) "Japan"

$user = new stdClass();
$user->info = null;
nullsafe($user); // NULL
```
機能
- nullのチェックを演算子とすることで記述を省略できる
- 演算子中にnullがあった場合、後の処理は行わずにnullを返す

# PHP8.1
## ENUM型
```diff_php
- PHP ~8.0
- class Suit
- {
-     private const Hearts = 'Hearts';
-     private const Diamonds = 'Diamonds';
-     private const Clubs = 'Clubs';
-     private const Spades = 'Spades';
- 
-     public function color($card): string
-     {
-         if ($card === self::Hearts || $card === self::Diamonds) {
-             return 'Red';
-         } elseif ($card === self::Clubs || $card === self::Spades) {
-             return 'Black';
-         }
-     }
- }
- 
- function pick_a_card(string $card): void 
- {
-     $suit = new Suit();
-     var_dump($suit->color($card));
- }
- 
- $card = 'Hearts';
+ PHP 8.1~
+ enum Suit
+ {
+     case Hearts;
+     case Diamonds;
+     case Clubs;
+     case Spades;
+ 
+     public function color(): string
+     {
+         if ($this === self::Hearts || $this === self::Diamonds) {
+             return 'Red';
+         } elseif ($this === self::Clubs || $this === self::Spades) {
+             return 'Black';
+         }
+     }
+ }
+ 
+ function pick_a_card(Suit $suit): void
+ {
+     var_dump($suit->color());
+ }
+ 
+ $card = Suit::Hearts;

pick_a_card($card); // string(3) "Red"
```
機能
- 列挙型（値を限定した独自の型）をPHPでも定義できるようになった
- Enum内では独自の型の定義の他にメソッドや定数を作成できる

Enum型には様々な機能や特徴があるため詳しくはドキュメントを参照してください。

https://www.php.net/manual/ja/language.enumerations.php

## 交差型
```diff_php
interface Hello
{
    public function hello(): string;
}

interface Language
{
    public function language(): string;
}

class Greeting implements Hello, Language
{
    public function hello(): string
    {
        return 'Hello';
    }

    public function language(): string
    {
        return 'PHP';
    }
}

- PHP ~8.0
- function greeting(Hello $greeting)
- {
-     if ($greeting instanceof Language) {
-         var_dump($greeting->hello().' '.$greeting->language());
-     }
- }
+ PHP 8.1~
+ function greeting(Hello&Language $greeting)
+ {
+     var_dump($greeting->hello().' '.$greeting->language());
+ }

$greeting = new Greeting();
greeting($greeting);
```
機能
- 複数の型を同時に満たす型の定義ができる

## 文字列キー配列のアンパック
```diff_php
$arrayA = ['a' => 1];
$arrayB = ['b' => 2];
- PHP ~8.0
- $result = array_merge(['a' => 0], $arrayA, $arrayB);
+ PHP 8.1~
+ $result = ['a' => 0, ...$arrayA, ...$arrayB];

var_dump($result);
```
機能
- 配列の結合が数値キーのみならず文字列キーでも行えるようになった（数値キーの配列の結合は7.4から`array_merge`なしでも可能）
- 配列の結合の仕様は`array_merge`と同じ（結合する配列に同じ名前のキーが存在する場合、前に指定された配列の値が後に指定された配列の値へと上書きされるなど）

# PHP8.2
## DNF(Disjunctive Normal Form)型
```diff_php
<?php

interface Hello
{
    public function hello(): string;
}

interface Language
{
    public function language(): string;
}

class Greeting implements Hello, Language
{
    public function hello(): string
    {
        return 'Hello';
    }

    public function language(): string
    {
        return 'PHP';
    }
}

- PHP ~8.1
- function greeting(mixed $greeting): void
- {
-     if ((($greeting instanceof Hello) && ($greeting instanceof Language)) || ($greeting === null)) {
-         $result = $greeting === null ? 'null' : $greeting->hello().' '.$greeting->language();
-         var_dump($result);
-         return;
-     }
-     throw new Exception('Invalid argument');
- }
+ PHP 8.2~
+ function greeting((Hello&Language)|null $greeting): void
+ {
+     $result = $greeting === null ? 'null' : $greeting->hello().' '.$greeting->language();
+     var_dump($result);
+ }

$greeting = new Greeting();
greeting($greeting); // string(9) "Hello PHP"
```
機能
- 交差型とUnion型の併用ができる

## 読み取り専用クラス
```diff_php
<?php

- PHP ~8.1
- class BlogData
- {
-     public readonly string $title;
-     public readonly string $contents;
+ PHP 8.2~     
+ readonly class BlogData
+ {
+     public string $title;
+     public string $contents;


    public function __construct(string $title, string $contents)
    {
        $this->title = $title;
        $this->contents = $contents;
    }
}

$blogData = new BlogData(title: 'title', contents: 'contents');

// Fatal error: Uncaught Error: Cannot modify readonly property BlogData::$title...
$blogData->title = 'new title';
```
機能
- 読み取り専用のプロパティの指定は8.1から行うことができたが、その指定をクラス全体のプロパティにまで拡張できる

https://www.php.net/manual/ja/language.oop5.properties.php#language.oop5.properties.readonly-properties

:::note alert
読み取り専用クラスの注意点
クラス内のプロパティは型の指定がされている他に動的であることが求められる。もしこれらの条件を満たしていないプロパティが一つでもある場合、読み取り専用のクラスの作成はできない。
```php
readonly class BlogData
{
    public $title;  // Readonly property BlogData::$title must have type
    static string $contents; // Static property BlogData::$contents cannot be readonly
}
```
:::

# その他
PHPのバージョンアップには今回紹介した新機能の他にも様々な機能が追加されています。またそれらによって新たな記述方法でPHPのコードが書けるようになっただけではなく、JITコンパイルの導入などによるパフォーマンスの上昇が見込めるようになりました。つまり、バージョンアップしたPHPではよりスマートかつ軽量なプログラムの作成が可能になると言えるでしょう。（他の言語に比べてどうかは自分には分かりません。あくまで当社比です。）

# バージョン変更に対する注意点
バージョンアップする際には注意が必要です。なぜなら、今回のバージョンアップにはPHPの仕様の一部変更がなされているからです。（使用の変更の中には動的プロパティのルール変更など下位互換性のない変更もあります）もしPHP7以前から8へバージョンアップする際には公式のドキュメントを参照することを強くお勧めします。

https://www.php.net/manual/ja/migration80.php

https://www.php.net/manual/ja/migration81.php

https://www.php.net/manual/ja/migration82.php


# 参考資料

https://www.php.net/releases/8.0/ja.php

https://www.php.net/releases/8.1/ja.php

https://www.php.net/releases/8.2/ja.php