PHPの言語仕様の理解を深める特殊な立ち回り

こんにちは！

先日の年始に、
[FFmpegを使ってルパン三世タイトルジェネレーターをPHPで書いてみた](https://qiita.com/huuya/items/a4a8022e19bfe87322db)
を書いたばかりですが、今日はPHPの言語仕様を追った特殊な立ち回りについてお話します！

`この記事で扱う使用例はあくまで言語仕様の理解を深めるためのサンプルコードとして記述しています。`

## 環境

### 動作確認済環境

- macOS Catalina : ``10.15.2``
- PHP : ``7.3.1``

## 言語仕様の理解を深める

### 目次一覧

- [String型の文字列からfor文で1文字ずつ取得する](#string型の文字列からfor文で1文字ずつ取得する)
- [条件式の評価](#条件式の評価)
- [論理演算子の仕組み](#論理演算子の仕組み)
- [可変変数](#可変変数)
- [可変関数](#可変関数)
- [可変クラス](#可変クラス)
- [三項演算子](#三項演算子)
- [エルビス演算子・エラー制御演算子](#エルビス演算子エラー制御演算子)
- [無名関数](#無名関数)

### String型の文字列からfor文で1文字ずつ取得する

#### 前提知識

PHP公式マニュアルの[文字列（string型）](https://www.php.net/manual/ja/language.types.string.php)のページ冒頭には、

```
stringは、文字が連結されたものです。
```

とあります。

そして、stringは

```
PHPでは、文字は1バイトと同じです。
```

とあります。

これにより、PHP上での文字列はマルチバイト文字が混合していたとしても、

- 文字を1バイトずつ連結

されたものになります。

上記で把握出来る通り、実はPHPでは下記のような立ち回りが可能です。

```php

$str = "abcdefghi";

for($i = 0;$i < strlen($str);$i++)
{
    echo $str[$i]."\n";
}
```

出力結果は、

```terminal
a
b
c
d
e
f
g
h
i
```

となります。

しかし、文字列は1バイト単位の文字で連結されているためマルチバイト文字が混合している場合は、上記コードで一文字ずつ取得は不可能です。

例えば、3バイト文字のUTF-8の場合に対象文字列が、

```
こんにちは
```

である場合は、[UTF8 文字コード表 3byte (e3)](http://orange-factory.com/sample/utf8/code3/e3.html#Hiragana)にあるように、

```
こ => e38193
ん => e38293
に => e381ab
ち => e381a1
は => e381af
```

となります。
これはPHP上で、下記のようにバイナリデータを16進表現にコンバートを行えば確認出来ます。

```php
echo bin2hex("こ")."\n";
echo bin2hex("ん")."\n";
echo bin2hex("に")."\n";
echo bin2hex("ち")."\n";
echo bin2hex("は")."\n";
```

そして、先程のfor文を用いて確認すると、

```php

$str = "こんにちは";

for($i = 0;$i < strlen($str);$i++){
    echo bin2hex($str[$i])."\n";
}

```

出力結果は、

```
e3
81
93
e3
82
93
e3
81
ab
e3
81
a1
e3
81
af
```

となり1バイトずつの文字が連結された文字列だと確認できます。

要するに、文字列が

- 3バイト文字のUTF-8のみ

である場合、3バイトずつの取得を行えば本来取得したい文字が期待できるわけです。

```php

$str = "こんにちは";

for($i = 0;$i < strlen($str);$i += 3)
{
    $first_byte = $str[$i];
    $second_byte = $str[($i + 1)];
    $third_byte = $str[($i + 2)];
    
    $char = $first_byte . $second_byte . $third_byte;
    echo $char."\n";
}
```

↓以下出力結果↓

```terminal
こ
ん
に
ち
は
```

よって、各文字のバイト数は例の通り、

```php
echo strlen("こ")."\n"; //3
echo strlen("a")."\n"; //1
```

で確認できますね！

シングルバイト文字とマルチバイト文字(UTF-8)が混合する想定で、文字列から一文字ずつ取得する場合は配列に一旦格納したほうが利便性が高いです。

```php

$str = "こんにちは！World.";

$str_array = array_values(
    array_filter(
        preg_split("//u",$str),
        "strlen"
    )
);

print_r($str_array);
// Array ( [0] => こ [1] => ん [2] => に [3] => ち [4] => は [5] => ！ [6] => W [7] => o [8] => r [9] => l [10] => d [11] => . )
```

[前回の記事](https://qiita.com/huuya/items/a4a8022e19bfe87322db)でも解説しましたが、まずはシングルバイト文字やマルチバイト文字の混合する文字列を、

```php
preg_split("//u",$str)
```
で配列にします。

[preg_split](https://www.php.net/manual/ja/function.preg-split.php)は、正規表現で文字列を分割する組み込み関数で、

- 第一引数に検索パターン文字列
- 第二引数に対象文字列

を指定します。

上記の検索パターンのうち、

```
//
```

は[デリミタ](https://www.php.net/manual/ja/regexp.reference.delimiters.php)です。
そして、

```
u
```

は[パターン修飾子](https://www.php.net/manual/ja/reference.pcre.pattern.modifiers.php)で、UTF-8として処理を行います。

要するに、検索パターンの

```
//u
```

はシングルバイト文字やマルチバイト文字等関係なく、UTF-8の文字全てにマッチすることになります。

この処理において生成された配列には、空文字（0バイト）のValueも含むIndexが含まれるため、

```php
array_filter(
    preg_split("//u",$str),
    "strlen"
);
```

[array_filter](https://www.php.net/manual/ja/function.array-filter)で配列を走査しています。

array_filterは、

- 第一引数に対象配列
- 第二引数にコールバック関数

を指定して、コールバーク関数の返り値がtrueのValue(Key名固定)のみの配列を返却します。

Valueが0バイトのIndexを参照した場合、コールバック関数のstrlenはint型の0を返り値とします。
int型の0は[PHPの型の比較表](https://www.php.net/manual/ja/types.comparisons.php)から、bool値として扱う場合`false`と評価するので、結果よりこのarray_filterの処理は、空文字のIndexを除去した配列を返却します。

最後に、上記よりKey名が歯抜け状態になった配列の添字を[array_values](https://www.php.net/manual/ja/function.array-values)で整えています。

#### 使用例

シングルバイトのみの文字列であれば、そのまま文字を1字ずつ取得可能です。

```php
$str = "abcdefg";

echo $str[0]; //a

echo $str[3]; //d
```

シングルバイトとマルチバイトの文字を許容する場合は、

```php

function multi_byte_string_to_array($str)
{
    return array_values(
        array_filter(
            preg_split("//u",$str),
            "strlen"
        )
    );
}

$str = "こんにちは！World.";

$str_array = multi_byte_string_to_array($str);

echo $str_array[0]."\n"; //こ

echo $str_array[7]."\n"; //o
```

とし取得可能です。

### 条件式の評価

#### 前提知識

条件式と言えば、PHPのif文を思い浮かべますが、

```php
$number = 5;

if($number === 5)
{
    echo "数字は5です";
}
```

単純な上記のコード例では、[PHPの公式マニュアル](https://www.php.net/manual/ja/control-structures.if.php)にある通り

```
式が TRUE と評価された場合、PHPは文を実行します。FALSE と評価された場合は、これを無視します。
```

という処理がif文に対して走っています。

ここで重要なのは、`式`が`$number === 5`であることです。
当たり前のように感じますが、`$number === 5`は`TRUE`もしくは`FALSE`と評価されることです。
よって、

```php

$is_number_5 = $number === 5;

var_dump($is_number_5); //bool(true)
```

となり`$is_number_5`には`$number === 5`が`true`評価されたbool値が格納されています。

なので、

```php
$number = 5;

$is_number_5 = $number === 5;

if($is_number_5)
{
    echo "数字は5です";
}
```

と同様です。

また[論理演算子](https://www.php.net/manual/ja/language.operators.logical.php)を用いて、

```php

$number = 5;

if($number !== 4 && $number !== 6)
{
    echo "数字は4でも6でもありません";
}
```

のように書くことがありますが、これも

```php
$number = 5;

$is_not_number_4_and_6 = $number !== 4 && $number !== 6;

if($is_not_number_4_and_6)
{
    echo "数字は4でも6でもありません";
}
```

と同様です。
これは、条件式を評価するとbool値を取得できる簡単な例です。

#### 利用例

```php
$number = 5;

$is_not_number_4 = $number !== 4;
$is_not_number_6 = $number !== 6;

if($is_not_number_4 && $is_not_number_6)
{
    echo "数字は4でも6でもありません";
}
```

条件式が長くなってしまう際に、変数に予めbool値を格納して利用する場合があります。

他にも、

```php
$number = 0;

$bool = true;

while($bool)
{
    echo $number."\n";
    $number++;
    $bool = $number < 5;
}
```

のように、while文の条件式の`$bool`に予め`true`と格納しておきます。
そして、走査のたびに`$numberが5より小さい`かを評価したbool値を条件式として扱う`$bool`に再代入します。`$bool`が`false`になるまで、すなわち`$number`が`5`になるまでwhile文は走査されることになります。

### 論理演算子の仕組み

ここでは[論理演算子](https://www.php.net/manual/ja/language.operators.logical.php)の`論理和`と`論理積`についてお話します。

#### 前提知識

```php
$number = 5;

$number !== 5 || $number = 1;

echo $number; //1
```

上記のコードを実行すると、`$number`が`1`になっていますね！

これは、論理和が`左式または右式のどちらかがtrueであればtrueと評価する`仕様に基づいた書き方です。

まず、`左式がtrue`であれば論理和の評価としてtrueを与えられることが確定しますよね！
そして、少なくともPHPでは論理和の`左式がtrue`であれば`右式を評価しない`という仕様になっています。

なので、`$number = 1`が評価されるのは`$number !== 5`の評価が`false`の時のみです。

```php

$number = 5;

if( !( $number !== 5 ) )
{
    $number = 1;
}

echo $number;
```

と同様の処理ですね！

上記で述べたとおり左式がtrueであれば、

```php

$number = 5;

$number === 5 || $number = 1;

echo $number; //5
```

`$number`には`1`が再代入されません。

`論理積`に関しては、予想できる通りに`左式がfalse`であれば、`論理式の評価がfalseと確定する`ため右式は評価されません。
`左式がtrue`であれば`右式`は評価されます。

#### 使用例

仮に、

```
.
├── core
│   ├── core.php
│   └── functions.php
│   └── function
│       └── utility.php
└── config
    └── config.php
```

と言ったディレクトリ構造の`core.php`で

- core/functions.php
- core/function/utility.php
- config/config.php

の3ファイルをincludeする際に、

```php

$include_files_array = [
	"../config/config.php",
    "./functions.php",
	"function/utility.php"
];

function include_files($files)
{
    is_array($files) || $files = [$files];
    foreach($files as $path)
    {
        include_once(__DIR__."/".$path);
    }
}

include_files($include_files_array);
```

のように利用します。

上記コード中の`include_files関数`は引数として、includeを行うファイルのパス一覧の一次元配列もしくは文字列のみのパスを許容可能です。

それは、

```php
is_array($files) || $files = [$files];
```

によって、`$files`が配列でなければ1次元配列として引数を再宣言するためです。
文字列であっても一次元配列にするため、その後のforeachで問題なく走査処理が可能です。

また、`htmlspecialchars`を使って

- 渡された引数が文字列であれば、サニタイズされた文字列を返り値とする
- 渡された引数が一次元配列であれば、全てのValueをサニタイズした一次元配列を返り値とする

といった関数であれば、

```php
function h($stringer){
    if(is_array($stringer) || !$stringer = htmlspecialchars($stringer))
    {
        foreach($stringer as $key => $string)
        {
            $stringer[$key] = htmlspecialchars($string);
        }
    }
    return $stringer;
}
```

こう書くことが可能です。

```php
is_array($stringer) || !$stringer = htmlspecialchars($stringer)
```

上記の条件式では、`$stringer`が

- 配列であれば`true`
- 文字列であれば`false`

と評価されます。

### 可変変数

#### 前提知識

PHPには動的に変数の参照をできる[可変関数](https://www.php.net/manual/ja/language.variables.variable.php)というものがあります。

これは、

```php
$orange_price = "300円";

$var = "orange_price";

echo $$var; //300円
```

変数名(`orange_price`)を文字列として定義した変数(`$var`)から300円(`$orange_price`)という文字列を参照できるものです。

また、

```php
echo $$var; //300円

echo ${$var}; //300円
```

のようにも書けるため、配列を参照する際に少しリーダブルになります笑

```php
$price_list = [
    "orange" => "300円"
];

$var = "price_list";

// echo $$var["orange"]; //300円

echo ${$var}["orange"]; //300円
```

`※クラス内のメンバ変数（プロパティ）にも扱えます`

#### 使用例

例として、Bootstrap等でフロントエンドのフォームを作成する際に下記のViewを生成する必要があったとします。

```php

<div class="form-group">
    <label for="name-sei">
        名前（姓）：
    </label>
    <input type="text" class="form-control" id="name-sei" value="<?php echo $sei; ?>" required>
</div>
<div class="form-group">
    <label for="name-mei">
        名前（名）：
    </label>
    <input type="text" class="form-control" id="name-mei" value="<?php echo $mei; ?>" required>
</div>
```

このViewの情報を格納した連想配列として、

```php

$form_info = [
    [
        "id" => "name-sei",
        "slug" => "名前（姓）：",
        "value" => "sei"
    ],
    [
        "id" => "name-mei",
        "slug" => "名前（名）：",
        "value" => "mei"
    ]
];
```

があったとします。

この際に同様のViewを生成するために、

```php

<?php foreach($form_info as $form): ?>
    <div class="form-group">
        <label for="<?php echo $form["id"]; ?>">
            <?php echo $form["slug"]; ?>
        </label>
        <input type="text" class="form-control" id="<?php echo $form["id"]; ?>" value="<?php echo ${$form["value"]}; ?>" required>
    </div>
<?php endforeach; ?>
```

と可変変数を利用して書くことが出来ます。

### 可変関数

#### 前提知識

[可変関数](https://www.php.net/manual/ja/functions.variable-functions.php)は動的に関数を呼び出せるものです。

例として、

```php

$html = '<h1 class="page-title">ページタイトル</h1>';

$func = "h";

function h($str)
{
    return htmlspecialchars($str);
}

echo $func($html);
```

のように扱えます。

`※クラス内のメソッドにも扱えます`

#### 使用例

`可変変数`の使用例と同様に連想配列中に関数名を文字列として定義しておき、任意のタイミング(Controller等)で発火させる等です。

### 可変クラス

#### 前提知識

可変クラスはPHPの公式マニュアルとしてページの存在を確認できませんでしたが、例として`Sample_Class`というクラスのインスタンスを生成できる状態で、

```php

$class_name = "Sample_Class";

$sample_class = new $class_name();
```

とすれば、

```php

$sample_class = new Sample_Class();
```

と同様の処理が実現できて、動的にインスタンスを生成することが出来ます。

#### 使用例

- `__Debugger`
- `__Database`

上記２つのクラスのインスタンスが生成出来る状態で、

```php

class __Loader{

    private $class_mapper = [
        [
            "slug" => "debug",
            "class_name" => "__Debugger"
        ],
        [
            "slug" => "db",
            "class_name" => "__Database"
        ]
    ];

    public function __construct()
    {
        $this->assign_class();
    }

    protected function assign_class()
    {
        foreach($this->class_mapper as $class)
        {
            $this->{$class["slug"]} = new $class["class_name"]();
        }
    }
}
```

というクラスがあったとします。
そして、

- `__Debugger`クラスにはpublicなプロパティとして`$mode = "Developer"`
- `__Database`クラスにはデータベースに接続処理を行う`connect`というメソッド

が存在する前提として、

```php

$loader = new __Loader();

echo $loader->debug->mode; //Developer

$loader->db->connect(); //DBへの接続処理
```

といった処理を行う際に扱います。`※可変プロパティも例として扱っています`

フレームワークの開発等をする際は扱うと思います。

### 三項演算子

#### 前提知識

[三項演算子](https://www.php.net/manual/ja/language.operators.comparison.php#language.operators.comparison.ternary)は、

```php

$high = true;

if($high)
{
    echo "オレンジ1個の値段は400円です";
}
else
{
    echo "オレンジ1個の値段は100円です";
}
```

等の処理を、

```php
$orange_price = $high ? "400" : "100";

echo "オレンジ1個の値段は".$orange_price."円です";
```

と書くことができます。

### エルビス演算子・エラー制御演算子

エルビス演算子は、

```php

$bool = true;
var_dump( true ?: "falseの場合に評価される" ); //bool(true)
```

のように、`a ?: b`に従った構文で書きます。

この時、

- `a`が`true`であれば、`a`がエルビス演算子の評価
- `a`が`false`であれば、`b`がエルビス演算子の評価

であります。

[エラー制御演算子](https://www.php.net/manual/ja/language.operators.errorcontrol.php)は、

```php

@$name;
```

未定義のプロパティ（エラー）にアクセスをすると、返り値が0となります。

#### 使用例

エルビス演算子とエラー制御演算子を用いて、

```php
echo @$_POST['name'] ?: "Qiita君"; //Qiita君
```

`$_POST['name']`にアクセスできない場合は、エラー制御演算子が0を返り値とし、これはbool型で扱うと`false`であるため、`Qiita君`がechoされます。

### 無名関数

#### 前提知識

[無名関数](https://www.php.net/manual/ja/functions.anonymous.php)はその名の通り、関数名を宣言しません。

#### 使用例

よって、関数を定義する程でも無い時に扱い、array_filter等の引数として必要なコールバック関数で簡易処理として扱うことが多いです。

```php
$sample_array = [
    200,
    300,
    50,
    400,
    500
];

$result = array_filter(
    $sample_array,
    function($value)
    {
        return $value > 250;
    }
);

print_r($result);
//Array ( [1] => 300 [3] => 400 [4] => 500 )
```

## 最後に

後半はかなり殴り書きになってしまいました！笑
ですが、アウトプットと同時に知識の理解確認とこれまで以上に言語仕様の深堀りに興味が湧きました！
こういった言語仕様の理解をもっと深めたい方は、[code golf](https://qiita.com/rana_kualu/items/948c284a5e8ba7f94b55)がとてもオススメです！
`code golf`というのは如何に少ないバイト数で実現したい処理のコードを書けるか競うようなものです。
ここまで深堀りすると、言語のバージョンによって立ち回りに互換性が無かったりするのですが、その言語仕様の先に見える特殊な立ち回りは病みつきになったりします笑

※リーダブルなコードが求められる業務では`code golf`してはいけません

私自身まだまだ未熟者でありますので、チョットデキル方々お手柔らかにお願いします笑