のむログ

技術メモ / 車 / 音楽 / 雑記 / etc...

こちらは旧ブログになります。

新ブログはこちらに移行しました🙇

PHPの無名関数

f:id:nomunomu0504:20190411144524j:plain:w0

無名関数

無名関数とはクロージャとも呼ばれ、関数名を指定することなく関数を作成することができます。コールバックのパラメータとして使用する際に便利ですが、それ以外にも用途としてあります。無名関数の実装にはClosureクラスを使用します。

無名関数の例

<?php
echo preg_replace_callback('~-([a-z])~', function($match) {
    return strtoupper($match[1]);
}, 'hello-world');
// helloWorld が出力される

また、クロージャは変数の値として使用することができます。そのため、変数に代入して利用することができます。以下のような記述があると自動的にClosureのインスタンスに変換されます。変数へのクロージャ代入は、他の代入と同じように記述することができ、セミコロンをつけます。

変数への無名関数の代入

<?php
$addEcho = function($value) {
    echo 'Hello, ' .$value. '!!';
};

$addEcho("nomunomu0504"); // Hello, nomunomu0504!!
$addEcho("Hatena"); // Hello, Hatena!!

クロージャで親のスコープ内に存在する変数を利用する場合にはuseでクロージャに渡す必要があります。またPHP7.1からは、use変数にはスーパーグローバル、$this、またはパラメータと同じ名前の変数を含めてはいけないことになりました。

親スコープからの変数の引き継ぎ

<?php
$message = 'hello';

// "use"を使わない場合
$example = function() {
    var_dump($message);
};
$example();

// "use"で引き継ぐ場合
$example = function() use ($message) {
    var_dump($message);
};
$example();
Notice: Undefined variable: message in /example.php on line 6
NULL
string(5) "hello"

しかし、この場合のuse で引き継がれている$messageは参照渡しではないので、無名関数の定義後に変更されても変更後の値を使用することができません。そのため。useで渡す変数を参照渡しにする必要があります。

<?php
$message = 'hello';

// "use"で引き継ぐ場合
$example = function() use ($message) {
    var_dump($message);
};
$example(); // hello

// 関数が定義されたのは $message = 'hello'のとき
// 関数を呼び出す前に変更されても適応されない
$message = 'world';
$example(); // hello

$message = 'hello';
$example = function () use (&$message) {
    var_dumo($message);
};
$example(); // hello

$message = 'wolrd';
$example(); // world

$example = function($arg) use ($message) {
    var_dump($arg . ' ' . $message);
};
$example("hello"); // hello world

クロージャのスコープ

コールバック

<?php
// 基本的なショッピングカートで、追加した商品の一覧や各商品の
// 数量を表示します。カート内の商品の合計金額を計算するメソッド
// では、クロージャをコールバックとして使用します。
class Cart {

    const PRICE_BUTTER  = 1.00;
    const PRICE_MILK    = 3.00;
    const PRICE_EGGS    = 6.95;

    protected $products = array();
    
    public function add($product, $quantity) {
        $this->products[$product] = $quantity;
    }
    
    public function getQuantity($product) {
        return isset($this->products[$product])
                   ? $this->products[$product] : FALSE;
    }
    
    public function getTotal($tax) {
        $total = 0.00;
        
        $callback = function($quantity, $product) use ($tax, &$total) {
            $pricePerItem = constant(
                __CLASS__ . "::PRICE_" . strtoupper($product)
            );
            $total += ($pricePerItem * $quantity) * ($tax + 1.0);
        };
        
        array_walk($this->products, $callback);
        return round($total, 2);
    }
}

$my_cart = new Cart;

// 商品を追加します
$my_cart->add('butter', 1);
$my_cart->add('milk', 3);
$my_cart->add('eggs', 6);

// 合計に 5% を付加した金額を表示します
print $my_cart->getTotal(0.05) . "\n";
//  54.29

$thisの自動バインド

PHP5.4.0以降では、クラスのコンテキストで無名関数を宣言した場合は現在のクラスが自動的にバインドされて、関数のスコープで$thisが利用できるようになります。現在のクラスへの自動バインドを利用したくない場合には、静的無名関数を利用します。

<?php
class Test
{
    public function testing()
    {
        return function() {
            var_dump($this);
        };
    }
}

$object = new Test;
$function = $object->testing();
$function();
object(Test)#1 (0) {
}

しかし、PHP5.4以前の出力は以下のようになります

Notice: Undefined variable: this in script.php on line 8
NULL

静的無名関数

PHP5.4以降では、自動的に無名関数がバインドされてしまうため、__construct()を用いて自動バインドを防ぎます。

<?php
class Foo
{
    function __construct()
    {
        $func = static function() {
            var_dump($this);
        };
        $func();
    }
};
new Foo();
Notice: Undefined variable: this in %s on line %d
NULL

静的無名関数へのオブジェクトバインド

<?php
$func = static function() {
    // 関数の本体
};
$func = $func->bindTo(new StdClass);
$func();
Warning: Cannot bind an instance to a static closure in %s on line %d