【PHP】コンテクストによる型変換

今回はコンテクスト(文脈)による型変換の具体的な動作について確認していこうと思います。
PHPマニュアルによると、コンテクストによる型変換が起こる
可能性があるのは以下の6パターンとなっています。
それぞれの型変換について発生条件や仕様を見ていきます。

* 数値のコンテクスト
* 文字列のコンテクスト
* 論理コンテクスト
* 整数と文字列のコンテクスト
* 比較のコンテクスト
* 関数のコンテクスト


:::note info
[型の相互変換](https://www.php.net/manual/ja/language.types.type-juggling.php)
:::

## 数値のコンテクスト
算術演算子を使った場合に型変換が発生します。
以下の例では、var_dump の中で $b が 
**boolean true -> int 1** に変換されてから演算されています。

```php
$a = '5';
$b = true;
var_dump($a + $b);  // int 6
```

:::note warn
マニュアルにも「注意」とありますが、値を別の型として解釈する場合でも、
値そのものの型が変わるわけではないことに注意が必要です。
上記の例では、\\$a の型が自動的に変換されていますが、
$a の値は変わらず string '5' のままです。
:::

## 文字列のコンテクスト
echo, print や 文字列中の変数のパース、または 文字列演算子 を
使った場合に型変換が発生します。以下の例では、var_dump の中で 
$a が **int 5 -> string '5'** に変換されてから結合されています。

```php
$a = 5;
$b = '30000';
var_dump($a . $b);  // string '530000'
```

## 論理コンテクスト
制御構文や 三項演算子, 論理演算子 を使った場合に型変換が発生します。
以下の例では、$a が var_dump の中の三項演算子で 
**array [0 => int 0, 1 => int 1, 2 => int 2] -> boolean true** に
変換されてから判定が行われています。

```php
$a = [0, 1, 2];
var_dump($a ? 'trueです' : 'falseです');  // string 'trueです'
```
:::note info
[PHP 型の比較表](https://www.php.net/manual/ja/types.comparisons.php)
:::

## 整数と文字列のコンテクスト
ビット演算子を使った場合に型変換が発生します。
以下の例では、var_dump の中のビット和の演算子を使っている部分で
$b が **string '8' -> int 8** に変換されています。
ビット和を求めるため、結果は 1010(10進数では 10) になります。

```php
$a = 2;   //   (10)₂
$b = '8'; // (1000)₂
var_dump($a | $b);  // int 10 -> (1010)₂
```
知らなかったのですが、[ビット演算子](https://www.php.net/manual/ja/language.operators.bitwise.php)を使用する際、
全てのオペランドが文字列の場合にはASCII 値が使用され結果は文字列、
それ以外の場合はオペランドと結果は整数として扱われるようです。
(5.5 >> 1) のような演算をした場合、実際には (5 >> 1) が実行され、結果は 2 となります。


## 比較のコンテクスト
比較演算子を使った場合に型変換が発生します。
以下の例では、var_dump の中で \\$a が **null -> string ''** (空文字) に変換されて比較されています。

```php
$a = null;
$b = 'moji';
var_dump($a < $b); // boolean true
```

:::note info
[さまざまな型の比較](https://www.php.net/manual/ja/language.operators.comparison.php#language.operators.comparison.types)
:::

## 関数のコンテクスト
値が型付きの引数、型付きプロパティに渡された場合や、
戻り値の型を宣言している関数から値が返される場合に型変換が発生します。
以下の例では、dump 関数の引数は string $a と宣言されているため、
dump 関数に渡した **int -1** -> **string '-1'** に変換されています。

```php
function dump(string $a)
{
    var_dump($a);
}

dump(-1); // string '-1'
```

## まとめ

今回動作を見ていったコンテクストによる型変換を全て盛り込んだら、
怒られること必至のコードが出来上がりました。
こんなにめちゃくちゃな演算や比較を行ってもエラーになっていませんが、
逆に言えば変数に入ってくる値を正しく制御していなければエラーは発生しないものの、
予期せぬ動作をしてしまう可能性があるということです。
そのため、型宣言やバリデーションを駆使して想定外の動作を
未然に防げるようにしていきたいと思います。


```php
class Piyo 
{
    function foo(string $string)
    {
        return $string;
    }
}

$int   = 10;
$float = 0.75;
$array = [1, 2, 3];
$obj   = new Piyo(); 
$bool  = true;
$null  = null;

var_dump((!$null + ($array > $obj) . $float) ^ ($int >> $obj->foo($bool)));

/**
 * !null                                
 * -> 論理コンテクストにより boolean true に変換
 * 
 * ($array > $obj)                      
 * -> 比較のコンテクストにより object の方が大きいと判定され boolean false
 * 
 * !$null + ($array > $obj)             
 * -> 数値のコンテクストにより true が 1, false が 0 にそれぞれ変換され int 1
 * 
 * (!$null + ($array > $obj) . $float)  
 * -> 文字列のコンテクストにより '.' より前が '1', 後が '0.75' にそれぞれ変換され string '10.75'
 * 
 * $obj->foo($bool)                     
 * -> 関数のコンテクストにより string '1' に変換
 * 
 * ($int >> $obj->foo($bool))           
 * -> 整数と文字列のコンテクストにより string '1' が int 1 に変換され int 5
 * 
 * (!$null + ($array > $obj) . $float) ^ ($int >> $obj->foo($bool))
 * -> 整数と文字列のコンテクストにより string '10.75' が int 10 に変換
 * -> (1010)₂ と (101)₂ の排他的論理和で (1111)₂ = int 15
 */

```