こんにちは!
先日の年始に、
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 === 5
はTRUE
もしくはFALSE
と評価されることです。
よって、
$is_number_5 = $number === 5;
var_dump($is_number_5); //bool(true)
となり$is_number_5
には$number === 5
がtrue
評価された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
に再代入します。$bool
がfalse
になるまで、すなわち$number
が5
になるまでwhile文は走査されることになります。
論理演算子の仕組み
ここでは論理演算子の論理和
と論理積
についてお話します。
前提知識
$number = 5;
$number !== 5 || $number = 1;
echo $number; //1
上記のコードを実行すると、$number
が1
になっていますね!
これは、論理和が左式または右式のどちらかが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
に従った構文で書きます。
この時、
-
a
がtrue
であれば、a
がエルビス演算子の評価 -
a
がfalse
であれば、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
してはいけません
私自身まだまだ未熟者でありますので、チョットデキル方々お手柔らかにお願いします笑