PHP
JavaScript
generator
ReactPHP
RecoilPHP

ReactPHP?時代はRecoilPHPだ!

More than 1 year has passed since last update.


時代変遷


JavaScript では

JavaScriptで非同期処理の時代変遷と言えば,こんな感じでやってきたと思います.

(例)

1秒後に+1した値をコールバック引数に渡す関数を使って,以下の2つの流れを同時に処理し,両方終わった段階で「All done !!」と表示することにします.


  • 1→2→3

  • 4→5→6


callback (旧石器時代)

function plusOneLater(a, callback) {

setTimeout(function () {
callback(a + 1);
}, 1000);
}

var active = 2;
plusOneLater(0, function (x) {
console.log(x);
plusOneLater(x, function (y) {
console.log(y);
plusOneLater(y, function (z) {
console.log(z);
if (--active <= 0) {
console.log('All done !!');
}
});
});
});
plusOneLater(3, function (x) {
console.log(x);
plusOneLater(x, function (y) {
console.log(y);
plusOneLater(y, function (z) {
console.log(z);
if (--active <= 0) {
console.log('All done !!');
}
});
});
});


Promise (ES6)

function plusOneLater(a) {

return new Promise(function (resolve) {
setTimeout(function () {
resolve(a + 1);
}, 1000);
});
}

Promise.all([
plusOneLater(0).then((x) => { console.log(x); return plusOneLater(x); })
.then((y) => { console.log(y); return plusOneLater(y); })
.then((z) => { console.log(z); }),
plusOneLater(3).then((x) => { console.log(x); return plusOneLater(x); })
.then((y) => { console.log(y); return plusOneLater(y); })
.then((z) => { console.log(z); }),
]).then(() => console.log('All done !!'));


Promise + Generator + co (ES6)

var co = require('co');

function plusOneLater(a) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(a + 1);
}, 1000);
});
}

co(function *() {
yield [
function *() {
var x = yield plusOneLater(0);
console.log(x);
var y = yield plusOneLater(x);
console.log(y);
var z = yield plusOneLater(y);
console.log(z);
},
function *() {
var x = yield plusOneLater(3);
console.log(x);
var y = yield plusOneLater(x);
console.log(y);
var z = yield plusOneLater(y);
console.log(z);
},
];
console.log('All done !!');
});

(ES7のasync/awaitは省略)


PHP では


ReactPHP

PHPでも数年前からReactPHPというものが登場し,言語ネイティブでのイベントループは実現できませんが,工夫をこらせばJavaScriptっぽい書き方も出来るようになってきました.


ReactPHP (PHP5, PHP7)

<?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();
}

React\Promise\all([
plusOneLater(0)->then(function ($x) { echo "$x\n"; return plusOneLater($x); })
->then(function ($y) { echo "$y\n"; return plusOneLater($y); })
->then(function ($z) { echo "$z\n"; }),
plusOneLater(3)->then(function ($x) { echo "$x\n"; return plusOneLater($x); })
->then(function ($y) { echo "$y\n"; return plusOneLater($y); })
->then(function ($z) { echo "$z\n"; }),
])->then(function () { echo "All done !!\n"; });

$GLOBALS['loop']->run();



RecoilPHP

最近 RecoilPHP というものも出てきたようで,ジェネレータを使った書き方も出来るようになってました!以下の点で優位性があると思います.



  • functionの数が少なくて済む

  • ReactPHPではグローバル変数としていたイベントループが,RecoilPHPではクラス内部にstatic変数としてあらかじめ用意されている (推測)


PHP7.0+

<?php

require 'vendor/autoload.php';

function plusOneLater($a) {
yield Recoil\Recoil::sleep(1);
return $a + 1; // PHP7限定
}

Recoil\Recoil::run(function () {
yield Recoil\Recoil::all([
(function () {
$x = yield plusOneLater(0); // PHP7限定
echo "$x\n";
$y = yield plusOneLater($x); // PHP7限定
echo "$y\n";
$z = yield plusOneLater($y); // PHP7限定
echo "$z\n";
})(), // PHP7限定
(function () {
$x = yield plusOneLater(3); // PHP7限定
echo "$x\n";
$y = yield plusOneLater($x); // PHP7限定
echo "$y\n";
$z = yield plusOneLater($y); // PHP7限定
echo "$z\n";
})(), // PHP7限定
]);
echo "All done !!\n";
});


ただし,


  • JavaScriptのcoのように,ジェネレータ関数そのもののyieldには(一番外側を除いて)対応してくれないことに注意してください.クロージャの()による即時実行はPHP7でしか出来ません

  • PHP5ではジェネレータ関数で値つきのreturnが出来ません

  • PHP5ではyieldの評価優先順位がおかしなことになってるので,代入が伴う場合括弧が必要です.

このため,PHP5だと少し残念なことになります(笑)


PHP5.5+

<?php

require 'vendor/autoload.php';

function plusOneLater($a) {
yield Recoil\Recoil::sleep(1);
yield Recoil\Recoil::return_($a + 1); // ダサい
}

Recoil\Recoil::run(function () {
yield Recoil\Recoil::all([
call_user_func(function () { // ダサい
$x = (yield plusOneLater(0)); // ダサい
echo "$x\n";
$y = (yield plusOneLater($x)); // ダサい
echo "$y\n";
$z = (yield plusOneLater($y)); // ダサい
echo "$z\n";
}),
call_user_func(function () { // ダサい
$x = (yield plusOneLater(3)); // ダサい
echo "$x\n";
$y = (yield plusOneLater($x)); // ダサい
echo "$y\n";
$z = (yield plusOneLater($y)); // ダサい
echo "$z\n";
}),
]);
echo "All done !!\n";
});



まとめ

ReactPHPとの親和性も高くて,なかなか面白いライブラリだと思うので,機会があれば使ってみては如何でしょうか.ReactPHP単体よりはRecoilPHPを織り交ぜた方が断然使いやすいと思います.

関連投稿: 「PHPでクロージャのuse地獄を回避する裏ワザ」


2016/08/04 追記: mpyw/co を使った場合

このライブラリは


  • cURLリソースを用いたHTTP通信

  • コルーチンごとの擬似スリープ

しか取り扱えませんが,いくらか RecoilPHP よりもシンプルに書くことができます.


PHP7.0+

<?php

require 'vendor/autoload.php';

use mpyw\Co\Co;

function plusOneLater($a) {
yield Co::DELAY => 1;
return $a + 1; // PHP7限定
}

Co::wait(function () {
yield [
function () {
$x = yield plusOneLater(0); // PHP7限定
echo "$x\n";
$y = yield plusOneLater($x); // PHP7限定
echo "$y\n";
$z = yield plusOneLater($y); // PHP7限定
echo "$z\n";
},
function () {
$x = yield plusOneLater(3); // PHP7限定
echo "$x\n";
$y = yield plusOneLater($x); // PHP7限定
echo "$y\n";
$z = yield plusOneLater($y); // PHP7限定
echo "$z\n";
},
];
echo "All done !!\n";
});



PHP5.5+

<?php

require 'vendor/autoload.php';

use mpyw\Co\Co;

function plusOneLater($a) {
yield Co::DELAY => 1;
yield CO::RETURN_WITH => $a + 1;
}

Co::wait(function () {
yield [
function () {
$x = (yield plusOneLater(0));
echo "$x\n";
$y = (yield plusOneLater($x));
echo "$y\n";
$z = (yield plusOneLater($y));
echo "$z\n";
},
function () {
$x = (yield plusOneLater(3));
echo "$x\n";
$y = (yield plusOneLater($x));
echo "$y\n";
$z = (yield plusOneLater($y));
echo "$z\n";
},
];
echo "All done !!\n";
});