2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-01-06

こんにちは!

先日の年始に、
FFmpegを使ってルパン三世タイトルジェネレーターをPHPで書いてみた
を書いたばかりですが、今日はPHPの言語仕様を追った特殊な立ち回りについてお話します!

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

環境

動作確認済環境

  • macOS Catalina : 10.15.2
  • PHP : 7.3.1

言語仕様の理解を深める

目次一覧

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

前提知識

PHP公式マニュアルの文字列(string型)のページ冒頭には、

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

とあります。

そして、stringは

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

とあります。

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

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

されたものになります。

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


$str = "abcdefghi";

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

出力結果は、

a
b
c
d
e
f
g
h
i

となります。

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

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

こんにちは

である場合は、UTF8 文字コード表 3byte (e3)にあるように、

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

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

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

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


$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バイトずつの取得を行えば本来取得したい文字が期待できるわけです。


$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";
}

↓以下出力結果↓

こ
ん
に
ち
は

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

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

で確認できますね!

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


$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] => . )

前回の記事でも解説しましたが、まずはシングルバイト文字やマルチバイト文字の混合する文字列を、

preg_split("//u",$str)

で配列にします。

preg_splitは、正規表現で文字列を分割する組み込み関数で、

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

を指定します。

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

//

デリミタです。
そして、

u

パターン修飾子で、UTF-8として処理を行います。

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

//u

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

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

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

array_filterで配列を走査しています。

array_filterは、

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

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

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

最後に、上記よりKey名が歯抜け状態になった配列の添字をarray_valuesで整えています。

使用例

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

$str = "abcdefg";

echo $str[0]; //a

echo $str[3]; //d

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


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文を思い浮かべますが、

$number = 5;

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

単純な上記のコード例では、PHPの公式マニュアルにある通り

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

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

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


$is_number_5 = $number === 5;

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

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

なので、

$number = 5;

$is_number_5 = $number === 5;

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

と同様です。

また論理演算子を用いて、


$number = 5;

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

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

$number = 5;

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

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

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

利用例

$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値を格納して利用する場合があります。

他にも、

$number = 0;

$bool = true;

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

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

論理演算子の仕組み

ここでは論理演算子論理和論理積についてお話します。

前提知識

$number = 5;

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

echo $number; //1

上記のコードを実行すると、$number1になっていますね!

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

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

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


$number = 5;

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

echo $number;

と同様の処理ですね!

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


$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する際に、


$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を行うファイルのパス一覧の一次元配列もしくは文字列のみのパスを許容可能です。

それは、

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

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

また、htmlspecialcharsを使って

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

といった関数であれば、

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

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

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

上記の条件式では、$stringer

  • 配列であればtrue
  • 文字列であればfalse

と評価されます。

可変変数

前提知識

PHPには動的に変数の参照をできる可変関数というものがあります。

これは、

$orange_price = "300円";

$var = "orange_price";

echo $$var; //300円

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

また、

echo $$var; //300円

echo ${$var}; //300円

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

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

$var = "price_list";

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

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

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

使用例

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


<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の情報を格納した連想配列として、


$form_info = [
    [
        "id" => "name-sei",
        "slug" => "名前(姓):",
        "value" => "sei"
    ],
    [
        "id" => "name-mei",
        "slug" => "名前(名):",
        "value" => "mei"
    ]
];

があったとします。

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


<?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; ?>

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

可変関数

前提知識

可変関数は動的に関数を呼び出せるものです。

例として、


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

$func = "h";

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

echo $func($html);

のように扱えます。

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

使用例

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

可変クラス

前提知識

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


$class_name = "Sample_Class";

$sample_class = new $class_name();

とすれば、


$sample_class = new Sample_Class();

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

使用例

  • __Debugger
  • __Database

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


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というメソッド

が存在する前提として、


$loader = new __Loader();

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

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

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

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

三項演算子

前提知識

三項演算子は、


$high = true;

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

等の処理を、

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

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

と書くことができます。

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

エルビス演算子は、


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

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

この時、

  • atrueであれば、aがエルビス演算子の評価
  • afalseであれば、bがエルビス演算子の評価

であります。

エラー制御演算子は、


@$name;

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

使用例

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

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

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

無名関数

前提知識

無名関数はその名の通り、関数名を宣言しません。

使用例

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

$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がとてもオススメです!
code golfというのは如何に少ないバイト数で実現したい処理のコードを書けるか競うようなものです。
ここまで深堀りすると、言語のバージョンによって立ち回りに互換性が無かったりするのですが、その言語仕様の先に見える特殊な立ち回りは病みつきになったりします笑

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

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

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?