PHP

PHPで文字列リテラルに式展開

Rubyでは文字列リテラルに任意の式を書いて展開できますが、PHPには単なる変数展開の機能しか存在しないので、同じことを実現することは利用できないような気がしますが、別にそんなことはなかったぜ。

Rubyでは

#{}で囲むことで任意の式1が書けます。

Rubyの場合
hello = "hello"
world = "world"
puts "#{hello.capitalize}, #{world.upcase}!"
#=> "Hello, WORLD!"

PHPでは

ふつうのPHPの(関数呼び出しを含まない)変数展開は、こうです。

$hello = "hello";
$world = "world";

echo "{$hello}, {$world}!", PHP_EOL;
#=> "hello, world!"

PHPでは、以下のように変数埋め込みができます。

echo "{$hello}, {$world}!", PHP_EOL;
#=> "hello, world!"

式展開を期待して以下のように書いても、何も起きません。

echo "{ucfirst($hello)}, {mb_strtoupper($world)}!", PHP_EOL;
#=> {ucfirst(hello)}, {mb_strtoupper(world)}!

しかし、不思議なことに次のようにすれば式展開として動作します。

$i = function ($v) { return $v; };
echo "{$i(ucfirst($hello))}, {$i(mb_strtoupper($world))}!", PHP_EOL;
#=> "Hello, WORLD!"

$iidentity function恒等関数(引数と同じものをそのまま返す函数)です。

あるいは、次のようにファイルを分けて書くこともできます。

variable.php
namespace variable
{
    const embed = __NAMESPACE__ . '\embed';
    /**
     * @param  mixed $i
     * @return mixed
     */
    function embed($i) {
        return $i;
    }
}
$_ = \variable\embed;
echo "{$_(ucfirst($hello))}, {$_(mb_strtoupper($world))}!", PHP_EOL;

こうすると、ちょっとおもしろ感が出てきますね。

どうしてこんなことができるのか

実はこの種類の変数展開構文には、インスタンスのメソッド呼び出しやプロパティ、配列や文字列のデリファレンス、可変関数可変クラスを利用した静的メソッド・静的プロパティなど、$を使った式はほとんど2書けます

<?php

$d = '2112-09-03';
$DateTime = DateTime::class;
var_dump("ドラえもんが生まれたのは{$DateTime::createFromFormat('Y-m-d', $d)->format('Y年n月j日')}です");
#=> string(57) "ドラえもんが生まれたのは2112年9月3日です"

この仕様の詳細は@do-akiさんのPHP 文字列リテラルにおける変数展開ノ全テ - do_akiの徒然想記で網羅されてるのでお読みください。

可変関数やメソッド呼び出しが可能ってことは何ができるか? そうです。引数にはあらゆるどんな式でも書けます。

echo "{$i((function($s){return $s;})("a"))}";

引数に式が書けるってことはクロージャを悪用して式と文のロンダリングもできます。……つまり、文字列の変数展開の式では事実上かなりいろんなことができます。

これは実用になるのか……?

一般的な方法ではないので、ふつうにprintf()とかsprintf()とか使った方がいいんじゃないですかね…?

$hello = "hello";
$world = "world";
printf('%1$s, %2$s!'.PHP_EOL, ucfirst($hello), mb_strtoupper($world));

まとめ

今回紹介した文法は私が「Rubyの持つ108つのエディタ殺し文法」と呼ぶ式展開です。 PHPでも同等のめんどくささがあることは既知だったのですが、 今回紹介したテクニックを駆使してどこまで複雑な式を書いてエディタのシンタックスハイライト機能をぶっ壊せるかは読者への課題とします。

脚注


  1. 任意の式が書ける… つまり文字列リテラルの中に再帰的に 

  2. なぜかクラス定数は呼び出せません