Symfony Advent Calendar 2016 11日目の記事です。 昨日は @kalibora さんの Symfony Console のコマンド名を自動的に Monolog のログに出そう でした。

Symfony\Component\ExpressionLanguageとは

式言語と訳されますが、一言で言うと条件式を評価することができるライブラリです。 Symfony ExpressionLanguage公式ドキュメント

通常、条件式の内容を書く人として想定されているのはプログラマではなくユーザーです。ユーザー(エンドユーザーなり管理者なり)が直接条件式を編集できることでいちいちプログラマの手を煩わせる必要がないという利便性があり、生のPHPコードを入力させてevalするのに比べて、 できることを制限しているため に安全に実行することができます。

例えば下記のようなコードを予め書いておきます(実際は $_POST を直接使ってはいけません :sweat_smile:

<?php
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$condition = $_POST['condition'];

$lang = new ExpressionLanguage();
$isSatisfied = $lang->evaluate($condition, ['stock' => $stock]); 

if ($isSatisfied) {
    // 条件が満たされた場合の処理
} else {
    // 条件が満たされなかった場合の処理
}

ユーザーは、$_POST[‘condition’]に

  • stock == 0 を指定すれば、stockが0の時に条件が満たされた場合の処理を実行させることができます
  • stock < 5 を指定すれば、stockが5より小さい時に条件が満たされた場合の処理を実行させることができます

つまり、ユーザーはプログラマを急かせることなく自分の手で、stockの値に対して必要に応じて様々な条件を指定して、後続の処理を制御できるのです。

上の例では変数を使ってみましたが、基本的な四則計算やand/or条件指定、正規表現、オブジェクトのメソッド呼び出し等も実装されています。 詳しい使い方は Expression syntax を参照してください。

ExpressionLanguageでPHP関数を使う

前述のように、ExpressionLanguageでは、できることを制限してあるため、通常のPHP関数を使うことはできません。ユーザーが条件式として unlink('/') なんて入力してきたら大変ですからね :smirk:

初期状態で使えるPHP関数

初期状態で使えるようになっているのは、 constant() 関数だけです。

PHPの関数をユーザー関数として登録する

PHPの関数を使いたい場合は、ユーザー定義関数として登録してから利用します。 式言語の趣旨を考えると、状態を変える関数は使わず、単に値を返す関数だけにしたほうが良いでしょう。

一例として、 ucfirst() 関数を登録してみます。

<?php
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$lang = new ExpressionLanguage();

// 登録してない関数を使う
//$lang->evaluate('ucfirst("quartet")'); // Symfony\Component\ExpressionLanguage\SyntaxError がスローされる

// 関数を登録
$lang->register('ucfirst', function($value){
    // コンパイルするときの動作を定義
    return sprintf('ucfirst(%s)', $value);
}, function($arguments, $value){
    // 評価するときの動作を定義
    return ucfirst($value);
});

// 関数を利用できるようになる
$lang->evaluate('ucfirst("quartet")'); // Quartet

// コンパイルするとPHPコードが文字列で書き出される
$lang->compile('ucfirst("quartet")'); // ucfirst("quartet")

ExpressionLanguage::register() の第一引数は、関数名です。 式言語の中で利用するための関数名なので、PHPの関数名をそのまま使う必要はありません。自由に決めることができます。 PHPの省略されすぎてわかりにくい標準関数名をわかりやすく変えて登録するのもOKです。 ただし、後から同名の違う関数を登録すると最後に登録したもので上書きされてしまうので注意してください。

第二引数と第三引数には callable を指定します(クロージャーなど)。

第二引数には、式言語をコンパイルするときの動作を定義します。 上の例では、 ucfirst("quartet") をコンパイルしたときに ucfirst("quartet") が返ってくるクロージャーを書いてあります。

第三引数には、式言語を評価するときの動作を定義します。 上の例では、 ucfirst("quartet") を評価したときに "Quartet" が返ってくるクロージャーを書いておけば良いですね。

まとめ

ExpressionLanguageを使うと、できることをシステムに危険がない範囲に制限した上で、細かい仕様のコントロールをユーザーに任せることも可能になります。 うまく利用して、開発スピードとユーザー満足度をどちらも高められるようにしたいですね。

明日は @eretica さんです。