PHP7系を覚えよう＆PHPを思い出そう（自分宛）

# 概要

* PHP7の新機能の確認
* を今更しつつPHPのおさらい
* をして7系に移行できるようにする

# 参考

[PHP中級者を目指す 〜言語を使いこなすための本〜](https://www.amazon.co.jp/exec/obidos/ASIN/B07HYR3R2C/hatena-gd-22)
[PHP: PHP 5.6.x から PHP 7.0.x への移行](http://php.net/manual/ja/migration70.php)
[PHP: PHP 7.0.x から PHP 7.1.x への移行](http://php.net/manual/ja/migration71.php)
[PHP: PHP 7.1.x から PHP 7.2.x への移行](http://php.net/manual/ja/migration72.php)
[PHPバージョン変更点をピックアップ(5.4.x ～ 7.3.x)](https://qiita.com/RyutaKojima/items/d9a968ab6a1721736938)

# 前提

* PHP7.2

# 留意事項

個人的に見直したところ、PHP7系で追加された機能を中心に覚えたところを書き残しています。  

# 型宣言

PHP7になり文字列型や整数型、その他多くのスカラー型がサポートされました。少し古い言い方をするとタイプヒンティング、でしょうか。ただし、この型宣言も完全ではありません。

```php
function sampleFunc(int $num) {
  echo $num;
}
sampleFunc(1); // 1
sampleFunc(true); // 1
sampleFunc("1"); // 1
sampleFunc("hoge"); // Argument 1 passed to sampleIntFunc() must be of the type integer, string given
```

この例では、文字列ははじけていますが、自動的に変換できるものは変換を行って受け取ってしまっています。型宣言と言うには少し弱いですね。PHP7でサポートされた型宣言はこれをより一層、強いものにできます。

```php
declare(strict_types=1);
function sampleFunc(int $num) {
    echo $num;
}
sampleFunc(1); // 1
sampleFunc(true); // Uncaught TypeError: Argument 1 passed to sampleFunc() must be of the type integer, boolean given
sampleFunc("1"); // Uncaught TypeError: Argument 1 passed to sampleFunc() must be of the type integer, string given
sampleFunc("hoge"); // Uncaught TypeError: Argument 1 passed to sampleFunc() must be of the type integer, string given
```

ファイルの先頭に`declare(strict_types=1);`を追加することでより厳密な型宣言をすることが可能です。  
declareについてはこちらをご覧ください。  
[PHP: declare - Manual](http://php.net/manual/ja/control-structures.declare.php)

NULLを許容したい場合は以下のように`?`を型の前に追加します。

```php
declare(strict_types=1);
function sampleFunc(?int $num) {
    echo $num;
}
sampleFunc(1); // 1
sampleFunc(null); // エラーは発生しない
```

戻り値にも方の定義は可能で、以下のように記述します。

```php
declare(strict_types=1);
function sampleFunc(int $num) :int {
    return $num * 2;
}
echo sampleFunc(1); // 2
```

もちろん変数と同様にNULLを許容する（戻り値にNULLがあり得ることを伝える）ことも可能です。

```php
declare(strict_types=1);
function sampleFunc(int $num) :?int {
    return $num * 2;
}
```

また、戻り値が無いことを明確に（保証）する場合は以下のように記述します。

```php
declare(strict_types=1);
function sampleFunc(int $num) :void {
    echo $num;
}
sampleFunc(1); // 1
```

# 演算子

## NULL合体演算子（??）

未定義変数に初期値を与える場合に利用、って書くとよくわからない。

```php
$hoge;
if(isset($piyo)) {
    $hoge = $piyo;
} else {
    $hoge = 'hoge';
}
echo $hoge;
```

```php
$hoge = isset($piyo)? $piyo: 'hoge';
echo $hoge;
```

```php
$hoge = $piyo ?? 'hoge';
echo $hoge;
```

上記の3つのコードは等価。NULL合体演算子って名前は覚え辛いけどコードが読めれば便利そう。  


## エルビス演算子(?:)

?:  
エルビスに見えなくもないですね。エルビス演算子はPHP5.3で追加された演算子です。今まで使ったことありませんでした。
NULL合体演算子がissetであれば、エルビス演算子はif($hoge)になります。

```php
$hoge;
if($piyo) {
    $hoge = $piyo;
} else {
    $hoge = 'hoge';
}
echo $hoge;
```

```php
$hoge = $piyo? $piyo: 'hoge';
echo $hoge;
```

```php
$hoge = $piyo ?: 'hoge';
echo $hoge;
```

上記の3つのコードは等価。エルビス演算子は名前にパワーがあるから覚えられそう。ifで判定されるので挙動に関しては少し注意が必要ではあります。

## 宇宙船演算子

なんかこう...あまりかっこいい名前じゃないですね（個人的に）。NULL合体演算子の方がかっこいい...。  
宇宙船演算子自体はcompareを書くような場面で使われる場合が多そうです。宇宙船演算子の説明自体はPHPの公式リファレンスが非常にわかりやすいです。

> 宇宙船演算子は、二つの式を比較するために使うものです。 $a が $b より大きい場合は 1、 $a と $b が等しい場合は 0、 $a が $b より小さい場合は -1 をそれぞれ返します。 比較の際には、 PHP 型の比較表 のルールを用います。

```php
// 整数値
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

// 浮動小数点数値
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

// 文字列
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
```

# 定数に配列が利用可能

あれ？5系の頃できなかったでしたっけ。使ったことが無いだけな気もします。

```php
define('HOGE', [
    'foo' => 'bar',
    'key' => 'value'
]);
echo HOGE['foo']; // bar
```

こういうことですね。

# クラス内定数のアクセス権

クラス内に定義した定数にアクセス権限を定義できるようになりました。使い所はありそうな気がします。

```php
class sampleClass {
    public const HOGE = [
        'foo' => 'bar',
        'key' => 'value'
    ];
    private const PIYO = [
        'foo' => 'bar',
        'key' => 'value'
    ];
}
echo sampleClass::HOGE['key']; // value
echo sampleClass::PIYO['foo']; // Fatal error: Uncaught Error: Cannot access private const sampleClass::PIYO
```

# 無名クラス

無名関数じゃなくて無名クラスです。無名関数的な感じで使えるようです。

```php
function sampleFunc(iHoge $obj) {
    echo $obj->pubFunc('hoge'); // foo
    echo $obj::HOGE['foo']; // bar
}

interface iHoge {
    public function pubFunc(string $param1);
}
sampleFunc(new class implements iHoge {
    public const HOGE = [
        'foo' => 'bar',
        'key' => 'value'
    ];
    public function pubFunc(string $param1) {
        echo $param1;
    }
});
```

わかりやすいように書くとこんな感じでしょうか。テストケースを書く時に多用しそうな印象がありますね。何となくですが。

# assert

結構昔からあった気がしますが、7系で扱いやすくなったとのことなので、簡単なテストを書いてみます。

```php
define('TAX', 1.08);

function tax($v1) {
    return $v1 * TAX;
}
$price = -1000;
assert($price >= 0, "price値が0未満");
assert(tax(1000) == 1050, "tax計算が異常");
```

ちょっと雑ですが、こんな感じでしょうか。上記の例で言えばTAXの値が変わった際に、assertを差し込んでおけば「1080になるはずがなっていないので何かしら想定外の異常が起きている」ことに気が付ける、という感じですかね。あくまでも異常発見に使うのであればassert内で副作用が起こるようなことはしてはいけませんね。  
assertion（assertの第一引数の条件）が「false」になった場合、description（assertの第二引数）が出力されるので慣れていない方はもしかしたら戸惑うかも。異常を検知するって考えて、assetionには正常系を書いて、descriptionには「正常系が通らないから異常」って内容を書くと覚えると良いのかも。assertはあくまでも開発時にバグやデグレにいち早く気が付くためのツールであってそれ自体がバリデーションや値の正当性の担保をしてくれるものではないことも忘れてはいけないですね。  
assertが便利とは言え、本番環境でも動いてしまうとパフォーマンスに影響が出るおそれがあります。その場合は、assert用ディレクティブ（zend.assertions）で設定します。  
ちょこちょこでも書いておくと良さそうです。

# 最後に

7系をキャッチアップすることなく気が付いたら7.3になってました。少しずつ少しずつキャッチアップして、コードを書いて身に付けていこうと思います。