Before
(function () {
$name = 'John';
(function () use ($name) {
$greet = 'Hello';
(function () use ($name, $greet) {
echo "{$greet}, {$name}\n";
})();
})();
})();
call_user_func(function () {
$name = 'John';
call_user_func(function () use ($name) {
$greet = 'Hello';
call_user_func(function () use ($name, $greet) {
echo "{$greet}, {$name}\n";
});
});
});
After
PHP7.0以降は new class{}
で無名クラスが使えます.全てのクロージャを無名クラスに属させることで,$this
を通じた共有が実現できます.call()
は第1引数で $this
を指定することが出来ます.
(function () {
$this->name = 'John';
(function () {
$this->greet = 'Hello';
(function () {
echo "{$this->greet}, {$this->name}\n";
})();
})();
})->call(new class{});
(function () {
$this->name = 'John';
(function () {
$this->greet = 'Hello';
(function () {
echo "{$this->greet}, {$this->name}\n";
})();
})();
})->bindTo(new class{})();
\Closure::bind(function () {
$this->name = 'John';
(function () {
$this->greet = 'Hello';
(function () {
echo "{$this->greet}, {$this->name}\n";
})();
})();
}, new class{})();
PHP5.4でも new \stdClass
とすることで無名クラスの代わりに使えます.call()
もPHP7.0以降の機能ですが,bind()
と__invoke()
を組み合わせることで解決できます.call_user_func()
を使っても構いません.
\Closure::bind(function () {
$this->name = 'John';
call_user_func(function () {
$this->greet = 'Hello';
call_user_func(function () {
echo "{$this->greet}, {$this->name}\n";
});
});
}, new \stdClass)->__invoke();
call_user_func(\Closure::bind(function () {
$this->name = 'John';
call_user_func(function () {
$this->greet = 'Hello';
call_user_func(function () {
echo "{$this->greet}, {$this->name}\n";
});
});
}, new \stdClass));
備考
クラスメソッド編
クラスメソッドで本来の$this
も参照しつつ…という場合も無理矢理ですが対応可能です.
プロパティを簡潔に初期化するために(object)[]
という記法を使っていますが,これはstdClass
にキャストされます.stdClass
に対してcall()
は使用出来ないため,PHP7.0以降でもPHP5.4のときと同様にbindTo()()
で代用する必要があることに注意してください.
<?php
class Klass {
private $greet = 'Hello';
public function hello() {
(function () {
$this->name = 'John';
(function () {
(function () {
echo "{$this->self->greet}, {$this->name}\n";
})();
})();
})->bindTo((object)['self' => $this])();
}
}
(new Klass)->hello();
<?php
class Klass {
private $greet = 'Hello';
public function hello() {
\Closure::bind(function () {
$this->name = 'John';
call_user_func(function () {
call_user_func(function () {
echo "{$this->self->greet}, {$this->name}\n";
});
});
}, (object)['self' => $this])->__invoke();
}
}
(new Klass)->hello();
別のアプローチ
持ち回したい変数を全てあらかじめ定義したオブジェクトまたは配列に入れるようにし,最小限のuse
で済ませる,というのもアリです.現実的にはこちらのほうが周囲の理解を得やすいかもしれません.
$s = new class{};
(function () use ($s) {
$s->name = 'John';
(function () use ($s) {
$s->greet = 'Hello';
(function () use ($s) {
echo "{$s->greet}, {$s->name}\n";
})();
})();
})();
$s = [];
(function () use (&$s) {
$s['name'] = 'John';
(function () use (&$s) {
$s['greet'] = 'Hello';
(function () use (&$s) {
echo "{$s['greet']}, {$s['name']}\n";
})();
})();
})();
非同期処理ライブラリとの併用
関連投稿: 「ReactPHP?時代はRecoilPHPだ!」
この書き方が真価を発揮するのはReactPHPやRecoilPHPといった,非同期処理のためのライブラリを使う場合です.クロージャが頻繁に登場するのでuse
地獄に陥りやすいです.この裏ワザを使うとかなり気持ちよく書けますね.
<?php
require 'vendor/autoload.php';
$GLOBALS['loop'] = React\EventLoop\Factory::create();
function plusOneLater($a) {
$deferred = new React\Promise\Deferred();
$GLOBALS['loop']->addTimer(1, function () use ($deferred, $a) {
$deferred->resolve($a + 1);
});
return $deferred->promise();
}
(function () {
$this->greet = 'Hello';
React\Promise\all([
plusOneLater(0)->then(function ($x) { echo "$this->greet, $x\n"; return plusOneLater($x); })
->then(function ($y) { echo "$this->greet, $y\n"; return plusOneLater($y); })
->then(function ($z) { echo "$this->greet, $z\n"; }),
plusOneLater(3)->then(function ($x) { echo "$this->greet, $x\n"; return plusOneLater($x); })
->then(function ($y) { echo "$this->greet, $y\n"; return plusOneLater($y); })
->then(function ($z) { echo "$this->greet, $z\n"; }),
])->then(function () { echo "All done !!\n"; });
})->call(new class{});
$GLOBALS['loop']->run();
<?php
require 'vendor/autoload.php';
function plusOneLater($a) {
yield Recoil\Recoil::sleep(1);
return $a + 1;
}
Recoil\Recoil::run((function () {
$this->greet = "Hello";
yield Recoil\Recoil::all([
(function () {
$x = yield plusOneLater(0);
echo "$this->greet, $x\n";
$y = yield plusOneLater($x);
echo "$this->greet, $y\n";
$z = yield plusOneLater($y);
echo "$this->greet, $z\n";
})(),
(function () {
$x = yield plusOneLater(3);
echo "$this->greet, $x\n";
$y = yield plusOneLater($x);
echo "$this->greet, $y\n";
$z = yield plusOneLater($y);
echo "$this->greet, $z\n";
})(),
]);
echo "All done !!\n";
})->bindTo(new class{}));