http://d.hatena.ne.jp/satosystems/20121228/1356655565
こちらのサイト、多種の言語でフィボナッチ数F38を求めています。
PHPは85.417秒と、残念ながらかなり遅い方です。
これを最適化してみましょう。
fib1はフィボナッチ数列の定義そのものです。
何も考えずに実装したらこうなることでしょう。
しかしこちらは、計算時に自分自身を2回呼び出します。
つまり引数が1増えるにつれ、fib1()を呼び出す回数がおよそ2倍になります。
計算量はO(2n)となり、効率の非常に悪いオーダーです。
もうfib1(100)なんて求めようとすると、反応がなくなってしまいます。
fib2は少々わかりにくいですが、上から順ではなく下から計算しています。
fib2_sub()の1回目に与えている1,1という引数は、順にF2、F1の計算結果です。
あとはfib2_sub()の再帰呼び出しで計算します。
fib2_sub()の2回目の引数はF2+F1つまりF3、F2、数値の37です。
fib2_sub()の3回目の引数はF3+F2つまりF4、F3、数値の36です。
…と順に下っていき、最後のfib2_sub()の引数はF38、F37、数値の2となります。
これによって引数が1増えても、fib2()の呼び出しは1回しか増えません。
計算量はO(n)となり、劇的に改善どころかとんでもない速度になりました。
というかこの時点で早すぎてfib2(38)では計算する意味がないレベル。
fib2(1000)でも余裕で計算可能です。
正直既にこの時点で終わりでいいんじゃね、という気もしますが、よく見てみたらfib2_sub()の再帰って実は計算には全く使わず、ループ回数をカウントしてるだけです。
カウントしてるだけならfor()でいいだろう、ということでfib3()です。
こちらも単純に下から足してるだけで、やってることはfib2()とほぼ同じです。
ただ再帰の関数呼び出しが無くなったせいか、速度もさらに上がっています。
ぱっと見ループを1回削れそうな気がしますが、削ろうとするとfib3(0)あたりの計算がうまくいかないのであえてこうしています。
ifで分岐すると却って遅くなるようでした。
さて、実は再帰もループも使わずにフィボナッチ数を一発で求める公式が存在します。
Fn = ( φn - (-φ)-n ) / sqrt(5) = [φn / sqrt(5) + 1/2 ]
ただしφは黄金比
といってもn乗とかの計算は出てきますけどね。
ルートとか割り算とか入ってるくせに計算すると必ず自然数になるとか意味がわからない。
計算量はO(1)でいいのかな、これ?
早すぎてfib3()と違いが分かりませんが、F2000とかを求めようとするとfib4()のほうが早くなるようです。
どちらにしろ0.1秒以下なので実用上はほとんど変わらないレベルですが。
まあともかく、結論としては、フィボナッチ数を求めるアルゴリズムでfib1()だけを書いてるサイトは投げ捨ててしまえ、ってことでいいですかね。
こちらのサイト、多種の言語でフィボナッチ数F38を求めています。
PHPは85.417秒と、残念ながらかなり遅い方です。
これを最適化してみましょう。
<?php
// by satosystems
// http://d.hatena.ne.jp/satosystems/20121228/1356655565
function fib1($n){
if ($n < 2){ return $n; }
return fib1($n - 2) + fib1($n - 1);
}
// by Dan Kogai
// http://blog.livedoor.jp/dankogai/archives/50958771.html
function fib2($n){
if($n < 2){return $n;}
return fib2_sub(1, 1, $n);
}
function fib2_sub($a, $b, $c){
if ($c <= 2){ return $a; }
return fib2_sub($a+$b, $a, $c-1);
}
// by NurseAngel
function fib3($n){
$fib0 = 0;
$fib1 = 1;
$ret = 0;
for($i=0; $i<$n; $i++){
$ret = $fib1;
$fib1 = $fib0 + $fib1;
$fib0 = $ret;
}
return $ret;
}
// by C言語による最新アルゴリズム事典
function fib4($n){
return floor( pow((1+sqrt(5))/2, $n) / sqrt(5) + 1/2 );
}
| 関数 | 処理時間 | F(38)の値 |
|---|---|---|
| fib1() | 34.29149秒 | 39088169 |
| fib2() | 0.00005秒 | 39088169 |
| fib3() | 0.00002秒 | 39088169 |
| fib4() | 0.00002秒 | 39088169 |
fib1はフィボナッチ数列の定義そのものです。
何も考えずに実装したらこうなることでしょう。
しかしこちらは、計算時に自分自身を2回呼び出します。
つまり引数が1増えるにつれ、fib1()を呼び出す回数がおよそ2倍になります。
計算量はO(2n)となり、効率の非常に悪いオーダーです。
もうfib1(100)なんて求めようとすると、反応がなくなってしまいます。
fib2は少々わかりにくいですが、上から順ではなく下から計算しています。
fib2_sub()の1回目に与えている1,1という引数は、順にF2、F1の計算結果です。
あとはfib2_sub()の再帰呼び出しで計算します。
fib2_sub()の2回目の引数はF2+F1つまりF3、F2、数値の37です。
fib2_sub()の3回目の引数はF3+F2つまりF4、F3、数値の36です。
…と順に下っていき、最後のfib2_sub()の引数はF38、F37、数値の2となります。
これによって引数が1増えても、fib2()の呼び出しは1回しか増えません。
計算量はO(n)となり、劇的に改善どころかとんでもない速度になりました。
というかこの時点で早すぎてfib2(38)では計算する意味がないレベル。
fib2(1000)でも余裕で計算可能です。
正直既にこの時点で終わりでいいんじゃね、という気もしますが、よく見てみたらfib2_sub()の再帰って実は計算には全く使わず、ループ回数をカウントしてるだけです。
カウントしてるだけならfor()でいいだろう、ということでfib3()です。
こちらも単純に下から足してるだけで、やってることはfib2()とほぼ同じです。
ただ再帰の関数呼び出しが無くなったせいか、速度もさらに上がっています。
ぱっと見ループを1回削れそうな気がしますが、削ろうとするとfib3(0)あたりの計算がうまくいかないのであえてこうしています。
ifで分岐すると却って遅くなるようでした。
さて、実は再帰もループも使わずにフィボナッチ数を一発で求める公式が存在します。
Fn = ( φn - (-φ)-n ) / sqrt(5) = [φn / sqrt(5) + 1/2 ]
ただしφは黄金比
といってもn乗とかの計算は出てきますけどね。
ルートとか割り算とか入ってるくせに計算すると必ず自然数になるとか意味がわからない。
計算量はO(1)でいいのかな、これ?
早すぎてfib3()と違いが分かりませんが、F2000とかを求めようとするとfib4()のほうが早くなるようです。
どちらにしろ0.1秒以下なので実用上はほとんど変わらないレベルですが。
まあともかく、結論としては、フィボナッチ数を求めるアルゴリズムでfib1()だけを書いてるサイトは投げ捨ててしまえ、ってことでいいですかね。
PR
前回、'index.php/2013/1/2/3'でルーティングした際にはcontrollerやactionがありませんでした。
これでは'index.php/a/b/1'の場合と揃えて、
$controller = new {$route->values['controller']};
$controller->{$route->values['action']};
とか書けません。
そこでパラメータやパース方法については別途設定することができます。
前者はadd()の第三引数にvaluesを指定しています。
match()で引っかかったときに、valuesを指定した値も一緒に帰ってくるようになります。
URLにコントローラやアクションを指定せずとも、任意のコントローラに振り分けることができるようになりました。
後者は、URLの形式と正規表現を別々に指定しています。
こちらの表記の方が、普通の正規表現なのでわかりやすいと思います。
最後、ブログの閲覧、更新など複数のルーティングを一括登録する方法です。
#全然関係ないけどSymfonyは今まで使ったことがあるFWの中で最も使いにくい
AuraPHPの記事
これでは'index.php/a/b/1'の場合と揃えて、
$controller = new {$route->values['controller']};
$controller->{$route->values['action']};
とか書けません。
そこでパラメータやパース方法については別途設定することができます。
<?php
require_once('path/to/channel/vendor/autoload.php');
// Router
$router = new Aura\Router\Map(
new Aura\Router\DefinitionFactory()
,new Aura\Router\RouteFactory()
);
// パラメータの追加
$router->add(
'date1'
,'/{:year:(\d+)}/{:month:(\d+)}/{:day:(\d+)}'
,array('values' => array(
'controller' => 'date', 'action' => 'read'
))
);
// パース方法を別途設定
$router->add(
'date2'
,'/{:year}{:month}{:day}'
,array('params' => array(
'year' => '(\d\d\d\d)', 'month' => '(\d\d)', 'day' => '(\d\d)'
))
);
// パース
$route = $router->match($_SERVER['PATH_INFO'], $_SERVER);
// 'index.php/2013/1/2' でアクセスした場合
$route->name; // date1
$route->values; // array('controller'=>'date', 'action'=>'read', 'year'=>'2013', 'month'=>'1', 'day'=>'2')
// 'index.php/20130102' でアクセスした場合
$route->name; // date2
$route->values; // array('year'=>'2013', 'month'=>'01', 'day'=>'02')
まずadd()の使用方法の拡張をふたつ。前者はadd()の第三引数にvaluesを指定しています。
match()で引っかかったときに、valuesを指定した値も一緒に帰ってくるようになります。
URLにコントローラやアクションを指定せずとも、任意のコントローラに振り分けることができるようになりました。
後者は、URLの形式と正規表現を別々に指定しています。
こちらの表記の方が、普通の正規表現なのでわかりやすいと思います。
最後、ブログの閲覧、更新など複数のルーティングを一括登録する方法です。
<?php
// ルーティンググループの登録
$router->attach('/blog', array(
'routes' =>array(
'blog_top' => '/'
,'blog_edit' => '/{:id:(\d+)}/edit'
,'blog_read' => array(
'path' => '/{:id}{:format}'
,'params' => array(
'id' => '(\d+)'
,'format' => '(\.json|\.atom)?'
)
,'values' => array(
'format' => '.html'
)
)
)
));
// パース
$route = $router->match($_SERVER['PATH_INFO'], $_SERVER);
// 'index.php/blog/' でアクセス
$route->name; // blog_top
$route->values; // array('action'=>'blog_top')
// 'index.php/blog/1' でアクセス
$route->name; // blog_read
$route->values; // array('action'=>'blog_read', 'format'=>'.html', 'id'=>'1')
// 'index.php/blog/1/edit' でアクセス
$route->name; // blog_edit
$route->values; // array('action'=>'blog_edit', 'id'=>'1')
sfDoctrineRouteCollectionみたいな便利おまとめルートはないので結局個別に設定が必要ですが、これでブログの作成/閲覧ルーティングがひととおりできあがりました。#全然関係ないけどSymfonyは今まで使ったことがあるFWの中で最も使いにくい
AuraPHPの記事
PHPは最近の他の言語とは違ってシングルスレッドなので、一回のリクエストで複数の処理を同時に実行したりといったことは通常はできません。
常に上から順に処理を進めていきます。
どうしてもやりたいなら`php hoge.php &`などと別プロセスで動かすといった手段しかありませんでした。
ところでなにやらpthreadsとかいうモジュールを見つけたので使ってみます。
LinuxであればPECLからインストールしましょう。
Windowsではバイナリが落ちてたので拾ってきます。
php_pthreads.dllをエクステンションのディレクトリに、pthreadVC2.dllは環境変数Pathが通っているところに置きます。
そしてphp.iniに以下を記述して再起動。
extension=php_pthreads.dll
これでpthreadsが使用可能になります。
phpinfo()にpthreadsという項目が追加されるのを確認しましょう。
それではさっそく実行。
TestThread::start()は、TestThread::run()を呼び出しますが、その処理の終了を待たずにすぐに次に進みます。
これでスレッド処理が簡単に行えます。
これで並列処理なんかも簡単に書けるようになりましたね。
まあスレッドが必要になるような処理をPHPで書くなよという話ではありますが。
常に上から順に処理を進めていきます。
どうしてもやりたいなら`php hoge.php &`などと別プロセスで動かすといった手段しかありませんでした。
ところでなにやらpthreadsとかいうモジュールを見つけたので使ってみます。
LinuxであればPECLからインストールしましょう。
Windowsではバイナリが落ちてたので拾ってきます。
php_pthreads.dllをエクステンションのディレクトリに、pthreadVC2.dllは環境変数Pathが通っているところに置きます。
そしてphp.iniに以下を記述して再起動。
extension=php_pthreads.dll
これでpthreadsが使用可能になります。
phpinfo()にpthreadsという項目が追加されるのを確認しましょう。
それではさっそく実行。
<?php
class TestThread extends Thread{
/**
* @Overide
* startしたときに呼ばれる
*/
public function run(){
// 時間のかかる処理
sleep(10);
// このスレッドのIDを確認
print($this->getThreadId());
// これを呼び出したスレッドのIDを確認
print($this->getCreatorId());
}
}
// TestThreadを別スレッドで起動
$testThread1 = new TestThread();
$testThread1->start();
$testThread2 = new TestThread();
$testThread2->start();
$testThread3 = new TestThread();
$testThread3->start();
$testThread4 = new TestThread();
$testThread4->start();
$testThread5 = new TestThread();
$testThread5->start();
10秒かかる処理が5個あるということで一見50秒かかりそうですが、実際は10秒で処理が完了します。TestThread::start()は、TestThread::run()を呼び出しますが、その処理の終了を待たずにすぐに次に進みます。
これでスレッド処理が簡単に行えます。
<?php
class TestThread extends Thread{
public function run(){
sleep(10);
}
}
// TestThreadを起動
$testThread = new TestThread();
$testThread->start();
// Thread::run()がまだ走っているか → true
var_dump($testThread->isRunning());
// Thread::run()が終了するまで待機
$testThread->join();
// Thread::run()がまだ走っているか → false
var_dump($testThread->isRunning());
join()はスレッドが終了するまで待機してくれます。これで並列処理なんかも簡単に書けるようになりましたね。
まあスレッドが必要になるような処理をPHPで書くなよという話ではありますが。
前回の続き。
http://bloggdgd.blog28.fc2.com/blog-entry-278.html
http://bloggdgd.blog28.fc2.com/blog-entry-279.html
http://php.net/manual/ja/function.array-splice.php
http://php.net/manual/ja/function.array-merge.php
数々の証言によりarray_merge()よりarray_splice()のほうが早いのは確定的に明らかですが、では実際前回やった測定では何故array_splice()が異常に遅かったのかというお話。
http://bloggdgd.blog28.fc2.com/blog-entry-272.html
http://okwave.jp/qa/q8109749.html
私も全然気付いてなかったのですが、実はarray_flatten2()にはそもそも「フラット化できないことがある」というバグがありました。
ということで
> array_flatten3のarray_mergeをarray_spliceに書き換え
てみました。
array_splice()使ったこと無いからこの使い方が適切なのかはわかりませんが、想定通りarray_merge()より早くなりました。
んで、毎回array_shift()してからarray_splice()って意味ないよな、と少しだけ書き換えてみたのがこちら。
微妙に早くなりましたが、やはりarray_walk_recursive()には全然敵いませんね。
原因は全くわかりませんが、array_flatten2()と同じような気がします。
さて、私の頭ではこれ以上の関数は思いつきませんでした。
再帰を使わずにより早く動作する手段は果たして見つかるでしょうか?
というかarray_flatten2()が遅かった原因は不明のままかよ。
http://bloggdgd.blog28.fc2.com/blog-entry-278.html
http://bloggdgd.blog28.fc2.com/blog-entry-279.html
http://php.net/manual/ja/function.array-splice.php
http://php.net/manual/ja/function.array-merge.php
数々の証言によりarray_merge()よりarray_splice()のほうが早いのは確定的に明らかですが、では実際前回やった測定では何故array_splice()が異常に遅かったのかというお話。
http://bloggdgd.blog28.fc2.com/blog-entry-272.html
http://okwave.jp/qa/q8109749.html
私も全然気付いてなかったのですが、実はarray_flatten2()にはそもそも「フラット化できないことがある」というバグがありました。
ということで
> array_flatten3のarray_mergeをarray_spliceに書き換え
てみました。
<?php
function array_flatten7($array) {
$tmp = array();
while (($val = array_shift($array)) !== null) {
if (is_array($val)){
array_splice($array, 0, 0, $val);
}else{
$tmp[] = $val;
}
}
return $tmp;
}
| 関数 | 処理時間 | 展開後の個数 |
|---|---|---|
| array_flatten7 | 0.16667秒 | 68228 |
array_splice()使ったこと無いからこの使い方が適切なのかはわかりませんが、想定通りarray_merge()より早くなりました。
んで、毎回array_shift()してからarray_splice()って意味ないよな、と少しだけ書き換えてみたのがこちら。
<?php
function array_flatten8($array) {
$tmp = array();
$array = array_values($array);
while (isset($array[0])){
if (is_array($array[0])){
array_splice($array, 0, 1, $array[0]);
}else{
$tmp[] = array_shift($array);
}
}
return $tmp;
}
| 関数 | 処理時間 | 展開後の個数 |
|---|---|---|
| array_flatten8 | 0.14735秒 | 68228 |
微妙に早くなりましたが、やはりarray_walk_recursive()には全然敵いませんね。
<?php
function array_flatten9($array) {
$array = array_values($array);
$i=0;
while (isset($array[$i])){
if (is_array($array[$i])){
array_splice($array, $i, 1, $array[$i]);
}else{
$i++;
}
}
return $array;
}
というかもうarray_shift()とか$tmpとか要らなくね、と思って上記のようにしてみたら、こちらは30秒を超える問題外の遅さでした。原因は全くわかりませんが、array_flatten2()と同じような気がします。
さて、私の頭ではこれ以上の関数は思いつきませんでした。
再帰を使わずにより早く動作する手段は果たして見つかるでしょうか?
というかarray_flatten2()が遅かった原因は不明のままかよ。
| 関数 | 処理時間 | 展開後の個数 |
|---|---|---|
| array_flatten5 (array_walk_recursive) | 0.06959秒 | 68228 |
| array_flatten1 (iterator_to_array+RecursiveIteratorIterator) | 0.08642秒 | 68228 |
| array_flatten6 (foreach+RecursiveIteratorIterator) | 0.11780秒 | 68228 |
| array_flatten8 (while (isset($array[0]))) | 0.14735秒 | 68228 |
| array_flatten7 (array_shift+array_splice) | 0.16667秒 | 68228 |
| array_flatten3 (array_shift+array_merge) | 0.19846秒 | 68228 |
| array_flatten4 (再帰呼出し) | 0.32668秒 | 68228 |
| array_flatten2 (each) | Maximum execution time of 30 seconds exceeded | |
| array_flatten9 ($i++) | Maximum execution time of 30 seconds exceeded | |
ZF2では少し便利なSPLの拡張があります。
配列で取り出すメソッド、シリアライズするメソッドが追加されています。
これで便利になったかと思いきや、実はこいつら、全く使いどころがありません。
元々iterator_to_array()で配列に取り出すことが可能でした。
なんでメソッドじゃなくて関数なのかは分かりませんが。
ちなみにtoArray()はiterator_to_array()を使わずforeachで中身を取り出しています。何故か。
で、PHP5.4で素のSPLにserialize()が実装されてしまいました。
さらに困ったことにserialize()の互換性がありません。
つまり、Zend\Stdlib\SplStackとSplQueueを使う必要性が全く無くなってしまったということです。
Zend\Stdlib\SplPriorityQueueについては、何故かまだtoArray()やserialize()が無いので今のところ使い道はありますが、今回のZend\Stdlib\SplStackについては、もはや存在意義が終了したと言っていいでしょう。
では何故紹介した。
<?php
require_once('path/to/channel/vendor/autoload.php');
$sample = array(1,2,3,4,5);
// キューとスタックの拡張
$queue = new Zend\Stdlib\SplQueue();
$stack = new Zend\Stdlib\SplStack();
foreach($sample as $key=>$val){
$queue->push($val);
$stack->push($val);
}
// 配列に取り出す
$array = $queue->toArray();
$array = $stack->toArray();
// シリアライズ
$queueSerial = $queue->serialize();
// シリアライズから元に戻す
$queue2 = new Zend\Stdlib\SplQueue();
$queue2->unserialize($queueSerial);
配列で取り出すメソッド、シリアライズするメソッドが追加されています。
これで便利になったかと思いきや、実はこいつら、全く使いどころがありません。
<?php
$sample = array(1,2,3,4,5);
// デフォルトのSplQueue
$default = new \SplQueue();
foreach($sample as $key=>$val){
$default->push($val);
}
// iterator_to_arrayで配列に取り出せる
$array = iterator_to_array($default);
// 実はPHP5.4で素のSPLにserialize()が実装された
$defaultSerial = $default->serialize();
$default2 = new \SplQueue();
$default2->unserialize($defaultSerial);
// 互換性はない
$default2->unserialize($queueSerial); // UnexpectedValueException
元々iterator_to_array()で配列に取り出すことが可能でした。
なんでメソッドじゃなくて関数なのかは分かりませんが。
ちなみにtoArray()はiterator_to_array()を使わずforeachで中身を取り出しています。何故か。
で、PHP5.4で素のSPLにserialize()が実装されてしまいました。
さらに困ったことにserialize()の互換性がありません。
つまり、Zend\Stdlib\SplStackとSplQueueを使う必要性が全く無くなってしまったということです。
Zend\Stdlib\SplPriorityQueueについては、何故かまだtoArray()やserialize()が無いので今のところ使い道はありますが、今回のZend\Stdlib\SplStackについては、もはや存在意義が終了したと言っていいでしょう。
では何故紹介した。
ずっと前、色々と半端なPear::Text_Figletを紹介しましたが、今回はZend謹製のZend\Text\Figletを使ってみます。
このように文字がAAとして表示されます。
このAAは特にフォントを解析したりているわけではなく、単に辞書ファイルで置換しているだけです。
Pear::Text_Figletと同じ仕組みでした。がっくり。
辞書ファイルはflfという拡張子で規格化されています。
まとめサイトはありますが、やはりというか日本語はありません。残念。
setSmushModeに設定できる値はSM_SMUSH以外にも色々あるのですが、ほとんど変わらないレベルで、正直なんのためにあるのか分かりませんでした。
なんかソースがいまいちZendっぽくない感もあって微妙。
<?php
header('Content-type: text/html; charset=UTF-8');
require_once('path/to/channel/vendor/autoload.php');
use Zend\Text\Figlet\Figlet;
// Figlet
$figlet = new Figlet();
// デフォルト以外のフォントを使用する場合セット
$figlet->setFont('roman.flf');
// LEFT/CENTER/RIGHT
$figlet->setJustification(Figlet::JUSTIFICATION_LEFT);
// 横幅 超えたら改行
$figlet->setOutputWidth(100);
// 指定するとstrrev (鏡文字ではない)
// $figlet->setRightToLeft(Figlet::DIRECTION_RIGHT_TO_LEFT);
// 指定すると余白を詰める
// $figlet->setSmushMode(Figlet::SM_SMUSH);
// 表示
print($figlet->render('Hello World'));
ooooo ooooo oooo oooo
`888' `888' `888 `888
888 888 .ooooo. 888 888 .ooooo.
888ooooo888 d88' `88b 888 888 d88' `88b
888 888 888ooo888 888 888 888 888
888 888 888 .o 888 888 888 888
o888o o888o `Y8bod8P' o888o o888o `Y8bod8P'
oooooo oooooo oooo oooo .o8
`888. `888. .8' `888 "888
`888. .8888. .8' .ooooo. oooo d8b 888 .oooo888
`888 .8'`888. .8' d88' `88b `888""8P 888 d88' `888
`888.8' `888.8' 888 888 888 888 888 888
`888' `888' 888 888 888 888 888 888
`8' `8' `Y8bod8P' d888b o888o `Y8bod88P"
このように文字がAAとして表示されます。
このAAは特にフォントを解析したりているわけではなく、単に辞書ファイルで置換しているだけです。
Pear::Text_Figletと同じ仕組みでした。がっくり。
辞書ファイルはflfという拡張子で規格化されています。
まとめサイトはありますが、やはりというか日本語はありません。残念。
setSmushModeに設定できる値はSM_SMUSH以外にも色々あるのですが、ほとんど変わらないレベルで、正直なんのためにあるのか分かりませんでした。
なんかソースがいまいちZendっぽくない感もあって微妙。
Zend\Text\Tableは、配列からテーブルタグが作れる…わけではなくテキストでテーブルを作ります。
MySQLのコマンドライン出力みたいなやつです。
出力は以下のようになります。
出力はHTMLタグのない純粋なテキストのため、基本的にコンソールやテキスト出力用です。
また、残念ながら幅の自動調整機能がないため、わざわざTable::setColumnWidthsで設定しなければならず面倒です。
あと配列をそのまま突っ込む機能もないようです。
デコレータとして最初からAscii以外にBlankとUnicodeが用意されており、Blankを指定すると罫線のない出力が得られます。
Unicodeはよくわかりませんでした。
しかし一番欲しい人が多いであろうZend\Text\Table\Decorator\Htmlは何故か存在しませんでした。
また、何気に右端左端の'|'と中間の'|'を区別していないという暴挙のため、ちょっと凝ったデコレータを自作しようとしても作成は不可能です。
なんというファッキンな仕様。
微妙に、というか色々と使い辛いZend\Text\Tableでした。
MySQLのコマンドライン出力みたいなやつです。
<?php
require_once('path/to/channel/vendor/autoload.php');
use Zend\Text\Table\Column;
// テーブル
$table = new Zend\Text\Table\Table();
// 設定
// 描画タイプ
$table->setDecorator('ascii');
// カラムの横幅は指定必須
$table->setColumnWidths(array(4, 6, 8, 10, 10));
// 行をセット
for($i=0; $i<5; $i++){
// 行
$row = new Zend\Text\Table\Row();
// カラムをセット
for($j=0; $j<4; $j++){
// カラム
$column = new Zend\Text\Table\Column();
// 値
$column->setContent('i:' . $i . ' j:' . $j);
// colspan
$column->setColSpan(1);
// align
$column->setAlign(Zend\Text\Table\Column::ALIGN_LEFT);
// 行に追加
$row->appendColumn($column);
}
// カラムを直接行に追加
$row->createColumn('text', array('align'=>Column::ALIGN_CENTER));
// テーブルに追加
$table->appendRow($row);
}
// 表示
print($table->render());
出力は以下のようになります。
+----+------+--------+----------+----------+ |i:0 |i:0 |i:0 j:2 |i:0 j:3 | text | |j:0 |j:1 | | | | +----+------+--------+----------+----------+ |i:1 |i:1 |i:1 j:2 |i:1 j:3 | text | |j:0 |j:1 | | | | +----+------+--------+----------+----------+ |i:2 |i:2 |i:2 j:2 |i:2 j:3 | text | |j:0 |j:1 | | | | +----+------+--------+----------+----------+ |i:3 |i:3 |i:3 j:2 |i:3 j:3 | text | |j:0 |j:1 | | | | +----+------+--------+----------+----------+ |i:4 |i:4 |i:4 j:2 |i:4 j:3 | text | |j:0 |j:1 | | | | +----+------+--------+----------+----------+
出力はHTMLタグのない純粋なテキストのため、基本的にコンソールやテキスト出力用です。
また、残念ながら幅の自動調整機能がないため、わざわざTable::setColumnWidthsで設定しなければならず面倒です。
あと配列をそのまま突っ込む機能もないようです。
デコレータとして最初からAscii以外にBlankとUnicodeが用意されており、Blankを指定すると罫線のない出力が得られます。
Unicodeはよくわかりませんでした。
しかし一番欲しい人が多いであろうZend\Text\Table\Decorator\Htmlは何故か存在しませんでした。
また、何気に右端左端の'|'と中間の'|'を区別していないという暴挙のため、ちょっと凝ったデコレータを自作しようとしても作成は不可能です。
なんというファッキンな仕様。
微妙に、というか色々と使い辛いZend\Text\Tableでした。
名前のとおり文字列操作を行う、と思いきや実は文字コード変換Wrapperのようです。
strlen()やmb_strlen()、iconv_strlen()といった関数を意識せずに使い分けることができます。
これはStrategyパターンだっけ。
まあ、いつだったかのZend\Math\BigIntegerと同じような内容です。
あとは適当に値を突っ込むだけで自動的に適切な関数を使用して変換や取得を行ってくれます。
文字列操作Wrapperの実体はZend\Stdlib\StringWrapper\StringWrapperInterfaceを実装している何らかのクラスですが、その中身について意識する必要は全くありません。
setEncoding()はStringUtils::getWrapper()の際に呼ばれるので基本的には不要です。
getWrapper()およびsetEncoding()の引数に少し注意が必要で、mb_convert_encoding()などと逆で、前の文字コードから後ろの文字コードに変換します。
strlen()やsubstr()などの文字コードも、setEncoding()の第一引数で指定したものが使われます。
またStringUtils::registerWrapper()で自作のWrapperを登録したりもできるみたいですが、そんな機能使う人なんて滅多にいないだろうからまあいいや。
使い道を無理矢理考えてみるとするならば、文字コード変換ではなく暗号化機能として使うとかでしょうか。
strlen()やmb_strlen()、iconv_strlen()といった関数を意識せずに使い分けることができます。
これはStrategyパターンだっけ。
まあ、いつだったかのZend\Math\BigIntegerと同じような内容です。
<?php
require_once('path/to/channel/vendor/autoload.php');
use Zend\Stdlib\StringUtils;
// 対応している文字列操作Wrapper一覧
$wrappers = StringUtils::getRegisteredWrappers();
// 引数がUTF-8か
$isUtf8 = StringUtils::isValidUtf8('あいうえお');
// PCRE正規表現をサポートしているか
$isPcre = StringUtils::hasPcreUnicodeSupport();
// 適切な文字列操作Wrapperを取得
$wrapperClass = StringUtils::getWrapper('UTF-8', 'SJIS-win');
$wrapper = new $wrapperClass();
// 現在の文字列操作Wrapperが対応している文字コード一覧を取得
$encoding = $wrapper->getSupportedEncodings();
// 現在の文字列操作Wrapperが引数の文字コードに対応しているか
$isSupported = $wrapper->isSupported('EUC-JP');
// 変換する文字コードを設定 前→後に変換
$wrapper->setEncoding('UTF-8', 'EUC-JP');
// 変換実行 (UTF-8からSJIS-winに)
$str = $wrapper->convert('あいうえお');
// その他
$len = $wrapper->strlen('あいうえお');
$sub = $wrapper->substr('あいうえお', 1, 3);
$pos = $wrapper->strpos('あいうえお', 'う');
$pad = $wrapper->strPad('text', 10, '埋め', STR_PAD_RIGHT);
$wrap= $wrapper->wordWrap('あいうえお', 3, '。', true);
StringUtils::getWrapper()に変換したい文字コードを渡すと、対応した文字列操作Wrapperを持ってきてくれます。あとは適当に値を突っ込むだけで自動的に適切な関数を使用して変換や取得を行ってくれます。
文字列操作Wrapperの実体はZend\Stdlib\StringWrapper\StringWrapperInterfaceを実装している何らかのクラスですが、その中身について意識する必要は全くありません。
setEncoding()はStringUtils::getWrapper()の際に呼ばれるので基本的には不要です。
getWrapper()およびsetEncoding()の引数に少し注意が必要で、mb_convert_encoding()などと逆で、前の文字コードから後ろの文字コードに変換します。
strlen()やsubstr()などの文字コードも、setEncoding()の第一引数で指定したものが使われます。
またStringUtils::registerWrapper()で自作のWrapperを登録したりもできるみたいですが、そんな機能使う人なんて滅多にいないだろうからまあいいや。
使い道を無理矢理考えてみるとするならば、文字コード変換ではなく暗号化機能として使うとかでしょうか。
Aura.Routerはルーティングを行います。
CakePHPのRouter、Symfonyのrouting.yml、ZendFrameworkのZend\Mvc\Routerのようなルーティング処理を行います。
もっともAura.Routerがやるのはパースだけなので、そこからルートを振り分ける処理は別途書かないといけません。
そしてparse_url()の引数に$_SERVER['REQUEST_URI']を指定していますが、mod_rewriteが無い場合はREQUEST_URIは正しくパースしてくれません。
そのため今回は$_SERVER['PATH_INFO']を突っ込んでます。
なんかここらへん自動判別とかできないんだろうか。
Aura\Router\Mapに対してルーティングを適当にセットし、その後match()メソッドに'/2013/1/2/3'のようなパスを投げることで、ルーティングに従ってパスを分解したAura\Router\Routeインスタンスが帰ってきます。
あとはnameとvaluesを使って適当なコントローラなりアクションなりを呼び出すようにするとよいでしょう。
最後の'/*'はデフォルトルートです。
既存のルートいずれにも当て嵌まらなかった場合にここに来ます。
デフォルトルートは別に書かなくてもかまいませんが、その場合$route = falseになります。
また'/foo/bar/*'と書くと'/foo/bar/baz'や'/foo/bar/baz/qux'など'/foo/bar/'以下全てのURLをルーティング可能です。
これらを組み合わせて、各URLに対するアクション等を振り分けていきましょう。
Aura.Routerを利用することで、URLからのルーティング処理を簡単に行うことができるようになりました。
AuraPHPの記事
CakePHPのRouter、Symfonyのrouting.yml、ZendFrameworkのZend\Mvc\Routerのようなルーティング処理を行います。
もっともAura.Routerがやるのはパースだけなので、そこからルートを振り分ける処理は別途書かないといけません。
<?php
require_once('path/to/channel/vendor/autoload.php');
// Router
$router = new Aura\Router\Map(
new Aura\Router\DefinitionFactory()
,new Aura\Router\RouteFactory()
);
// ルーティングの登録
// /
$router->add('home', '/');
// /2013/01/01
$router->add('date', '/{:year:(\d+)}/{:month:(\d+)}/{:day:(\d+)}');
// /2013/01/01/1
$router->add('dateid', '/{:year:(\d+)}/{:month:(\d+)}/{:day:(\d+)}/{:id:(\d+)}');
// /controller/action/1
$router->add('article', '/{:controller}/{:action}/{:id:(\d+)}');
// 他全て
$router->add('default', '/*');
// パース
$route = $router->match($_SERVER['PATH_INFO'], $_SERVER);
// 'index.php/2013/1/2/3' でアクセスした場合
$route->name; // dateid
$route->values; // array('year'=>'2013', 'month'=>'1', 'day'=>'2', 'id'=>'3' )
$route->regex; // "/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/(?P<id>\d+)"
$route->path; // "/{:year}/{:month}/{:day}/{:id}"
// 'index.php/a/b/1' でアクセスした場合
$route->name; // article
$route->values; // array('controller'=>'a', 'action'=>'b', 'id'=>'1')
$route->regex; // "/(?P[^/]+)/(?P[^/]+)/(?P\d+)"
$route->path; // "/{:controller}/{:action}/{:id}"
// 'index.php/hoge/fuga' でアクセスした場合
$route->name; // default
$route->values; // array('*'=>array('hoge', 'fuga'))
$route->regex; // "/(?P<__wildcard__>.*)"
$route->path; // "/{:__wildcard__}"
公式の例ではmod_rewrite前提で、http://example.jp/a/b/cのようなURLを想定していますが、mod_rewriteを利用しないときはhttp://example.jp/index.php/a/b/cみたいなURLになります。そしてparse_url()の引数に$_SERVER['REQUEST_URI']を指定していますが、mod_rewriteが無い場合はREQUEST_URIは正しくパースしてくれません。
そのため今回は$_SERVER['PATH_INFO']を突っ込んでます。
なんかここらへん自動判別とかできないんだろうか。
Aura\Router\Mapに対してルーティングを適当にセットし、その後match()メソッドに'/2013/1/2/3'のようなパスを投げることで、ルーティングに従ってパスを分解したAura\Router\Routeインスタンスが帰ってきます。
あとはnameとvaluesを使って適当なコントローラなりアクションなりを呼び出すようにするとよいでしょう。
最後の'/*'はデフォルトルートです。
既存のルートいずれにも当て嵌まらなかった場合にここに来ます。
デフォルトルートは別に書かなくてもかまいませんが、その場合$route = falseになります。
また'/foo/bar/*'と書くと'/foo/bar/baz'や'/foo/bar/baz/qux'など'/foo/bar/'以下全てのURLをルーティング可能です。
これらを組み合わせて、各URLに対するアクション等を振り分けていきましょう。
Aura.Routerを利用することで、URLからのルーティング処理を簡単に行うことができるようになりました。
AuraPHPの記事
Aura.Signalはイベントハンドラです。
特定のイベントに対してアクションを紐付けます。
JavaScriptであれば
document.getElementById('hoge').addEventListener('click', handler, false);
のように、内部を一切触らず外部からハンドラを設定することができるわけですが、PHPでは『Test::hoge()が呼ばれたらハンドラを実行する』的な動作を外部から指定することができません。
いちいち内部からハンドラ呼び出しを書かないといけないということで相当微妙。
便利なところとしては、同じシグナル名のハンドラを複数登録しておくと、一回のハンドラ呼び出しで全部実行されます。
また、シグナルが存在しないと何もしません。
普通のメソッド呼び出しだと対象メソッドを書き換えないといけなくなりますが、Aura.Signalはどこからでも好きにコールバックを仕込むことができ、挙動の変更が簡単になります。
まあ、やりすぎるとソース追うのが面倒になりすぎるから程々にしておいてほしいものですが。
AuraPHPの記事
特定のイベントに対してアクションを紐付けます。
<?php
require_once('path/to/channel/vendor/autoload.php');
// 適当なクラス
class Test{
protected $signal;
public function __construct(Aura\Signal\Manager $signal){
$this->signal = $signal;
}
public function hoge(){
// ハンドラ呼び出し
$this->signal->send($this, 'signal1', 'foo');
}
public function fuga(){
// ハンドラ呼び出し
$this->signal->send($this, 'signal2', 'bar');
}
}
// Aura.Signal
$signal = new Aura\Signal\Manager(
new Aura\Signal\HandlerFactory
,new Aura\Signal\ResultFactory
,new Aura\Signal\ResultCollection
);
// ハンドラを登録 (クラス名, シグナル名, コールバック関数, 優先度)
$signal->handler('Test', 'signal1', function($arg){ print($arg); }, 100 );
$signal->handler('Test', 'signal2', function($arg){ print($arg); }, 100 );
$signal->handler('Test', 'signal2', function($arg){ print('ハンドラ複数設定'); }, 200 );
// 実行
$test = new Test($signal);
$test->hoge(); // 'foo'が表示
$test->fuga(); // 'bar''ハンドラ複数設定'の順に表示
なんかこれ、普通にメソッド呼び出してるだけじゃね?JavaScriptであれば
document.getElementById('hoge').addEventListener('click', handler, false);
のように、内部を一切触らず外部からハンドラを設定することができるわけですが、PHPでは『Test::hoge()が呼ばれたらハンドラを実行する』的な動作を外部から指定することができません。
いちいち内部からハンドラ呼び出しを書かないといけないということで相当微妙。
便利なところとしては、同じシグナル名のハンドラを複数登録しておくと、一回のハンドラ呼び出しで全部実行されます。
また、シグナルが存在しないと何もしません。
普通のメソッド呼び出しだと対象メソッドを書き換えないといけなくなりますが、Aura.Signalはどこからでも好きにコールバックを仕込むことができ、挙動の変更が簡単になります。
まあ、やりすぎるとソース追うのが面倒になりすぎるから程々にしておいてほしいものですが。
AuraPHPの記事