Aura.Sessionは、セッションのラッパで、セッションをオブジェクト風に扱えます。
使い方もかなり簡単です。
セッションの開始終了など、セッション全体に関連する事柄はAura\Session\Managerに対して設定します。
データの書き込みは、getSegment()でセグメントを取得して、それに対して書き込んでいきます。
今回は引数に'Aura.Session.Test'を指定したので、実際に書き込まれる値は$_SESSION['Aura.Session.Test']['hoge'] = 'fuga';のようになります。
引数にパッケージ名など一位の値を指定することで、別の部署が開発してた機能とセッションキーが重複して動かなくなった、などの事故を未然に防ぐことができます。
また便利な機能として、一度だけ読み込めるフラッシュ機能があります。
setFlash()で保存した値は、getFlash()した時点で消えます。
フォームのリロード防止や、リダイレクト先での警告表示などに気軽に使えそうです。
AuraPHPの記事
使い方もかなり簡単です。
<?php
require_once('path/to/channel/vendor/autoload.php');
// Aura.Session
$session = new Aura\Session\Manager(
new Aura\Session\SegmentFactory(),
new Aura\Session\CsrfTokenFactory()
);
// 設定
$session->setCacheExpire($session->getCacheExpire()); // session_cache_expire()
$session->setCacheLimiter($session->getCacheLimiter()); // session_cache_limiter()
$session->setName($session->getName()); // session_name()
$session->setSavePath($session->getSavePath()); // session_save_path()
// セッション開始
$session->start(); // session_start()
$session->regenerateId(); // session_regenerate_id()
// 状態確認
$session->isStarted(); // 開始していればtrue
$session->getId(); // session_id()
$session->getStatus(); // session_status()
$session->isActive(); // session_status()===PHP_SESSION_ACTIVEならtrue
// セッション書き込みオブジェクト
$segment = $session->getSegment('Aura.Session.Test');
// 読み書き
$segment->hoge = 'fuga';
$hoge = $segment->hoge;
// 読んだら消える
$segment->setFlash('key', 'value');
$segment->hasFlash('key'); // 値が存在すればtrue
$key = $segment->getFlash('key'); // ここで消える
// 終了
$session->commit(); // session_write_close()
$session->clear(); // session_unset()
$session->destroy(); // session_destroy()
コンストラクタは選択の余地がないので、気にせずそのままコピペするだけでいいです。セッションの開始終了など、セッション全体に関連する事柄はAura\Session\Managerに対して設定します。
データの書き込みは、getSegment()でセグメントを取得して、それに対して書き込んでいきます。
今回は引数に'Aura.Session.Test'を指定したので、実際に書き込まれる値は$_SESSION['Aura.Session.Test']['hoge'] = 'fuga';のようになります。
引数にパッケージ名など一位の値を指定することで、別の部署が開発してた機能とセッションキーが重複して動かなくなった、などの事故を未然に防ぐことができます。
また便利な機能として、一度だけ読み込めるフラッシュ機能があります。
setFlash()で保存した値は、getFlash()した時点で消えます。
フォームのリロード防止や、リダイレクト先での警告表示などに気軽に使えそうです。
AuraPHPの記事
PR
ずっと前session_set_save_handler()を使ってセッション制御を行いました。
当時はそれぞれの処理にいちいち関数を設定するという素敵な構造でしたが、PHP5.4からはインターフェイスを使ったまともな設計ができるようになっています。
SessionHandlerInterfaceインターフェイスを使って書き直してみましょう。
というかSessionHandlerクラスが実装されたので、前のやつをそのまま使うとCannot redeclareのFatal errorになってしまいます。
テーブル作成。
ついでにmysql_connectなどの非推奨なものも外しています。
ずいぶんすっきりしました。
なおidの32桁というのはセッションID生成関数のデフォルト、session.hash_function=0の場合の最大値です。
違う生成方法を用いている場合は桁数を合わせましょう。
write()の第二引数に入ってくるデータはセッションをシリアライズしたものですが、通常のserialize()ではなくsession_encode()という関数が使われています。
見た目は似ていますが互換性はないので注意しましょう。
あとどうでもいいけど、SessionHandlerInterface::gc()の引数はstringになっていますが実際はintでした。
当時はそれぞれの処理にいちいち関数を設定するという素敵な構造でしたが、PHP5.4からはインターフェイスを使ったまともな設計ができるようになっています。
SessionHandlerInterfaceインターフェイスを使って書き直してみましょう。
というかSessionHandlerクラスが実装されたので、前のやつをそのまま使うとCannot redeclareのFatal errorになってしまいます。
テーブル作成。
CREATE TABLE `user_session` ( `id` varchar(32) NOT NULL, `data` text, `regist_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8本体。
<?php
class MySessionHandler implements SessionHandlerInterface{
//セッションテーブルの定義
private $sessionDsn = 'mysql:dbname=session;host=localhost';
private $sessionUser = 'testuser';
private $sessionPass = 'testpass';
//PDO
private $session;
//SQL
private $sqlRead = 'SELECT data FROM user_session WHERE id = :id';
private $sqlWrite = 'INSERT INTO user_session (id, data) VALUES (:id, :data) ON DUPLICATE KEY UPDATE data=:data';
private $sqlGc = 'DELETE FROM user_session WHERE regist_date < NOW() - INTERVAL :second SECOND';
private $sqlDestroy= 'DELETE FROM user_session WHERE id = :id';
/**
* @Override
* セッションを開始した
* @param String セッション保存パス
* @param String セッション名
* @return boolean
*/
function open($save_path, $session_name){
//DB接続
$this->session = new PDO($this->sessionDsn, $this->sessionUser, $this->sessionPass);
return true;
}
/**
* @Override
* セッションを閉じる
* @return boolean true
*/
function close(){
$this->session = null;
return true;
}
/**
* @Override
* セッション読み込み
* @param String セッションID
* @return String セッションの内容
*/
function read($id){
$smt = $this->session->prepare($this->sqlRead);
$smt->bindParam(':id', $id, PDO::PARAM_STR);
if(!$smt->execute()){return '';}
return $smt->fetchColumn(0);
}
/**
* @Override
* セッション書き込み
* @param String セッションID
* @param String 保存内容
* @return boolean
*/
function write($id, $data ){
$smt = $this->session->prepare($this->sqlWrite);
$smt->bindParam(':id', $id, PDO::PARAM_STR);
$smt->bindParam(':data', $data, PDO::PARAM_STR);
return $smt->execute();
}
/**
* @Override
* セッション削除
* @param String セッションID
* @return boolean
*/
function destroy($id){
$smt = $this->session->prepare($this->sqlDestroy);
$smt->bindParam(':id', $id, PDO::PARAM_STR);
return $smt->execute();
}
/**
* @Override
* ガベージコレクション
* @param int session.gc_maxlifetime
* @return boolean
*/
function gc($maxlifetime){
$smt = $this->session->prepare($this->sqlGc);
$smt->bindParam(':second', $maxlifetime, PDO::PARAM_INT);
return $smt->execute();
}
}
// セッションハンドラの登録
session_set_save_handler(new MySessionHandler(), true);
// セッションスタート
session_start();
session_regenerate_id(true);
// 以降普段と同じようにセッションを使用可能
$_SESSION['hoge'] = 'fuga';
このようになりました。ついでにmysql_connectなどの非推奨なものも外しています。
ずいぶんすっきりしました。
なおidの32桁というのはセッションID生成関数のデフォルト、session.hash_function=0の場合の最大値です。
違う生成方法を用いている場合は桁数を合わせましょう。
write()の第二引数に入ってくるデータはセッションをシリアライズしたものですが、通常のserialize()ではなくsession_encode()という関数が使われています。
見た目は似ていますが互換性はないので注意しましょう。
あとどうでもいいけど、SessionHandlerInterface::gc()の引数はstringになっていますが実際はintでした。
Aura.Sqlで前回使わなかったメソッドなどを適当に使ってみます。
SELECT文が全くの意味不明ですが、あくまでSQLの例ということで。
SQL用には、他にjoin()もあり、大抵のSQLは書けるようになっています。
便利なのがsetPaging()とpage()で、ページング用のLIMIT句を簡単に作ってくれます。
setPaging()で1ページあたりの件数、page()で表示するページを指定です。
しかし何故わざわざquote、quoteIntoなんてメソッドを実装しているのかは謎。
理由がないかぎり使わないようにしましょう。
AuraPHPの記事
<?php
require_once('path/to/channel/vendor/autoload.php');
// Aura.Sql
$auraSql = new Aura\Sql\ConnectionFactory();
$connection = $auraSql->newInstance('mysql', 'host=localhost;dbname=test;charset=utf8;', 'testuser', 'testpass');
// 接続する
$connection->connect(); // 失敗したらPDOException
// PDOインスタンスを直接得る
$pdo = $connection->getPdo();
// DSN
$dsn = $connection->getDsnString(); // 'mysql:host=localhost;dbname=test;charset=utf8;'
// クォート ただしprepareを使用すべき
$str = $connection->quote("1' OR 1 ");
$str = $connection->quoteInto("foo = ? AND bar = ? ", array("1' OR 1 ", "1' OR 1 "));
// テーブル一覧を取得
$tables = $connection->fetchTableList();
// カラム一覧を取得
$columns = $connection->fetchTableCols('hoge');
// SELECT
$select = $connection->newSelect(); // SELECT
$select->distinct(true); // distinct
$select->cols(['*']); // *
$select->from('hoge'); // FROM hoge
$select->where('id = :id'); // WHERE id = :id
$select->where('id = :id2'); // AND id = :id2
$select->orWhere('id = :id3'); // OR id = :id3
$select->groupBy(array('id')); // GROUP BY id
$select->having('id = :id4'); // HAVING id = :id4
$select->orderBy(array('id DESC')); // ORDER BY id DESC
//$select->limit(1); // LIMIT 1
//$select->offset(0); // OFFSET 1
$select->setPaging(5)->page(3); // LIMIT 5 OFFSET 10
$bind = array('id'=>1, 'id2'=>1, 'id3'=>1, 'id4'=>1);
$data = $connection->fetchAll($select, $bind);
SELECT文が全くの意味不明ですが、あくまでSQLの例ということで。
SQL用には、他にjoin()もあり、大抵のSQLは書けるようになっています。
便利なのがsetPaging()とpage()で、ページング用のLIMIT句を簡単に作ってくれます。
setPaging()で1ページあたりの件数、page()で表示するページを指定です。
しかし何故わざわざquote、quoteIntoなんてメソッドを実装しているのかは謎。
理由がないかぎり使わないようにしましょう。
AuraPHPの記事
Aura.Sqlを使ってSQLを発行してみます。
中身は単にPDOのラッパで、さほど難しく考えずに使用可能です。
マニュアルだとinstance.phpをインクルードしろとなっているのですが、困ったことにinclude.phpにはオートロードが効きません。
中身はConnectionFactoryをnewしてるだけなので普通にやったほうがいい気がします。
DSNの形式はPDOとほぼ同じですが、頭の'mysql'や'pgsql'だけ分けないといけないようです。
使い方としてはぺちぱーお馴染み配列形式の他、クエリオブジェクトによるメソッドチェインでの記述も可能になっています。
ただこのクエリオブジェクト、微妙に使いにくいです。
せっかくメソッドチェイン使ってるのだから、$connection->newInsert()->into('hoge')->col('data', 'でーた')->exec()みたいに使いたかったところ。
マニュアルでは$insert->set('date', 'NOW()')という例がありますが、これは見てのとおりSQLを直接入れたい場合に使います。
うっかり$insert->set('data', $_REQUEST['data'])とするとSQLインジェクションになるので注意しましょう。
Aura.Sqlを使った場合、SQLとしての利点は正直メソッドチェインくらいですが、プロファイラが簡単に使えてボトルネック分析などが非常に楽なのが便利です。
getProfiler()でプロファイラを取得し、発行したSQL、かかった時間などの詳細を確認することができます。
AuraPHPの記事
中身は単にPDOのラッパで、さほど難しく考えずに使用可能です。
<?php
require_once('path/to/channel/vendor/autoload.php');
// Aura.Sql
$auraSql = new Aura\Sql\ConnectionFactory();
$connection = $auraSql->newInstance('mysql', 'host=localhost;dbname=test;charset=utf8;', 'testuser', 'testpass');
// プロファイラを有効にする
$connection->getProfiler()->setActive(true);
// トランザクション開始
$connection->beginTransaction();
try{
// SELECT / SQL
$select = 'SELECT * FROM hoge WHERE id = :id';
$where = array('id'=>1);
$connection->fetchAll($select, $where);
// SELECT / クエリオブジェクト
$select = $connection->newSelect();
$select->cols(['*'])->from('hoge')->where('id = :id');
$where = array('id'=>1);
$connection->fetchAll($select, $where);
// INSERT / 配列
$table = 'hoge';
$bind = array('data'=>'でーた');
$connection->insert($table, $bind);
// INSERT / クエリオブジェクト
$insert = $connection->newInsert();
$insert->into('hoge')->cols(array('data'));
$bind = array('data'=>'でーた');
$connection->query($insert, $bind);
// LAST_INSERT_ID
$lastInsertId = $connection->lastInsertId();
// UPDATE / 配列
$table = 'hoge';
$where = 'id = :id';
$bind = array('id'=>1);
$update = array('data'=>'でーた1');
$connection->update($table, $update, $where, $bind);
// UPDATE / クエリオブジェクト
$update = $connection->newUpdate();
$update->table('hoge')->where('id = :id')->cols(array('data'));
$bind = array('id'=>2, 'data'=>'でーた2');
$connection->query($update, $bind);
// DELETE / 配列
$table = 'hoge';
$where = 'id = :id';
$bind = array('id'=>1);
$ret = $connection->delete($table, $where, $bind);
// DELETE / クエリオブジェクト
$delete = $connection->newDelete();
$delete->from('hoge')->where('id = :id');
$bind = array('id'=>2);
$connection->query($delete, $bind);
// コミット
$connection->commit();
}catch(Exception $e){
// ロールバック
$connection->rollBack();
}
// プロファイラを止める
$connection->getProfiler()->setActive(false);
// プロファイラを取得
$profile = $connection->getProfiler()->getProfiles();
マニュアルだとinstance.phpをインクルードしろとなっているのですが、困ったことにinclude.phpにはオートロードが効きません。
中身はConnectionFactoryをnewしてるだけなので普通にやったほうがいい気がします。
DSNの形式はPDOとほぼ同じですが、頭の'mysql'や'pgsql'だけ分けないといけないようです。
使い方としてはぺちぱーお馴染み配列形式の他、クエリオブジェクトによるメソッドチェインでの記述も可能になっています。
ただこのクエリオブジェクト、微妙に使いにくいです。
せっかくメソッドチェイン使ってるのだから、$connection->newInsert()->into('hoge')->col('data', 'でーた')->exec()みたいに使いたかったところ。
マニュアルでは$insert->set('date', 'NOW()')という例がありますが、これは見てのとおりSQLを直接入れたい場合に使います。
うっかり$insert->set('data', $_REQUEST['data'])とするとSQLインジェクションになるので注意しましょう。
Aura.Sqlを使った場合、SQLとしての利点は正直メソッドチェインくらいですが、プロファイラが簡単に使えてボトルネック分析などが非常に楽なのが便利です。
getProfiler()でプロファイラを取得し、発行したSQL、かかった時間などの詳細を確認することができます。
AuraPHPの記事
前回\SplPriorityQueueを紹介しましたが、今回のZend\Stdlib\SplPriorityQueueはZendFrameworkによる\SplPriorityQueueの拡張です。
使いやすいように、配列出力やシリアライズする機能が追加されています。
結果。
件数:5
一番目:最優先
キー:4 値:最優先
キー:3 値:にばんめその1
キー:2 値:にばんめその2
キー:1 値:低い
キー:0 値:一番低い
件数:0
結果が前回と微妙に違います。
配列のキーが0スタートになっているのと、同プライオリティのデータが順不同ではなくinsertした順に整列されるのが主な違いです。
また、toArray()、serialize()といった便利なメソッドが増えています。
配列にした場合は優先度が消えるため、配列から元に戻すことはできません。
シリアライズした場合は全情報が保存されるため、完全に元の状態に戻すことができます。
これらのためにわざわざ入れるほどの内容でもありませんが、ZFを使っているならついでにZend\Stdlib\SplPriorityQueueを使ってみてもいいかもしれません。
どうでもいいけどZend\Stdlib\SplPriorityQueue::insert()のソース中で、\SplPriorityQueue::insert()の第二引数priorityに配列を突っ込んでいました。
この使い方、\SplPriorityQueue::insert()には全く書かれていません。
何故かSplPriorityQueue::compare()のノートにしれっと書いてあるだけで、他に何処にも情報が見当たりませんでした。
その結果どうなるかというと、配列の複数の要素で優先順位を判断してくれるようです。
使いやすいように、配列出力やシリアライズする機能が追加されています。
<?php
require_once('path/to/channel/vendor/autoload.php');
$queue = new Zend\Stdlib\SplPriorityQueue();
$queue->insert('低い', 10);
$queue->insert('最優先', 100);
$queue->insert('にばんめその1', 50);
$queue->insert('にばんめその2', 50);
$queue->insert('一番低い', 1);
// 個数 → 5
print($queue->count());
// 配列に取り出す
$array = $queue->toArray();
// シリアライズも可能
$serial = $queue->serialize();
// 優先順位の高い順になる
foreach($queue as $key=>$val){
print('キー:' . $key . ' 値:' . $val);
}
// 一度使うとやっぱり消える → 0
print($queue->count());
// シリアライズから元に戻せる
$queue2 = new Zend\Stdlib\SplPriorityQueue();
$queue2->unserialize($serial);
結果。
件数:5
一番目:最優先
キー:4 値:最優先
キー:3 値:にばんめその1
キー:2 値:にばんめその2
キー:1 値:低い
キー:0 値:一番低い
件数:0
結果が前回と微妙に違います。
配列のキーが0スタートになっているのと、同プライオリティのデータが順不同ではなくinsertした順に整列されるのが主な違いです。
また、toArray()、serialize()といった便利なメソッドが増えています。
配列にした場合は優先度が消えるため、配列から元に戻すことはできません。
シリアライズした場合は全情報が保存されるため、完全に元の状態に戻すことができます。
これらのためにわざわざ入れるほどの内容でもありませんが、ZFを使っているならついでにZend\Stdlib\SplPriorityQueueを使ってみてもいいかもしれません。
どうでもいいけどZend\Stdlib\SplPriorityQueue::insert()のソース中で、\SplPriorityQueue::insert()の第二引数priorityに配列を突っ込んでいました。
この使い方、\SplPriorityQueue::insert()には全く書かれていません。
何故かSplPriorityQueue::compare()のノートにしれっと書いてあるだけで、他に何処にも情報が見当たりませんでした。
その結果どうなるかというと、配列の複数の要素で優先順位を判断してくれるようです。
前回の続き。
http://bloggdgd.blog28.fc2.com/blog-entry-272.html
> 「Recursive」って入ってるけど
なんてこった、Recursiveは再帰って発想はなかった。
いや、皮肉とかじゃなくて素で気付いてなかった。
さて、せっかくなので色々見つけたarray_flatten関数のパフォーマンスを測定してみます。
似てるっぽいのは除いて、6種類を適当に試してみました。
array_flatten1()は前回作ったやつで、自力では一切何もせずSPLに任せっきりです。
array_flatten2()はCertaiN氏の「再帰を使わないものでは最も美しい」関数です。
array_splice()なんて初めて見たよ。
array_flatten3()も再帰を使わないパターンですが、array_flatten2()よりは平易に書かれています。
まあループ中で自分自身を更新しててややこしいのはかわらないのですが。
array_flatten4()は、典型的な再帰パターンです。
私が何も参考にせず一から書いたらこうなっていたと思います。
array_flatten5()は再帰にarray_walk_recursive()を用いたパターン。
正直array_walk_recursive()難しいです。
array_flatten6()はおまけです。
中身はarray_flatten1()とほぼ同じで、一番外側のiterator_to_array()だけを手動でやってるわけですが、ビルトイン関数とどちらが早いでしょうか。
展開後の個数は動作確認用です。
最大10階層のランダム構造、変数の個数の合計は見てのとおり68228個の多重配列を1次元に展開しています。
一発取りなので多少の誤差はありますが、何回かやっても時間が逆転するほどの違いはありませんでした。
1位はarray_flatten5。
array_walk_recursive()ちょっぱや!
2位はarray_flatten1。
簡潔で美しい上に速度も速い。
素晴らしい。
--0.1秒の壁--
3位はarray_flatten6。
foreachを手動で回したぶんだけ遅くなっているのでしょうか。
こーゆーのはビルトイン使っとけば基本問題無いんですよ。
4位はarray_flatten3。
array_flatten2とあまり変わらないように見えるのですが早いですね。
やはりarray_shift()やarray_merge()のようなよく使われる関数は最適化も進んでいるのでしょうか。
--0.2秒の壁--
5位はarray_flatten4。
再帰はやはり何度も関数呼び出しが入るぶん遅くなってしまうのでしょうか。
やってることはそんなに変わらないはずのarray_walk_recursive()に比べて4倍近い時間がかかっていますね。
--0.4秒の壁--
残念ながらarray_flatten2は問題外という結果になりました。
幾ら美しくても実用に耐えなくては役に立ちません。
ちなみに計測に用いた配列は以下で生成しました。
2013/05/29追記
http://bloggdgd.blog28.fc2.com/blog-entry-278.html
CertaiN氏の検証によりますと、array_merge()とarray_splice()そのものについてはarray_splice()のほうが早いようです。
Cのソース読めないので本当はどういう実装になっているのかは知らないのですが、マニュアルのコメント欄などでも「array_splice()の方がはえーよ」との声が多いようです。
http://php.net/manual/ja/function.array-splice.php
http://php.net/manual/ja/function.array-merge.php
従ってarray_flatten2()がタイムアウトでarray_flatten4()がそれなりに早い理由は、それ以外の部分(おそらくforeachとwhile)にあるであろうということです。
2013/05/30追記
http://yuubiseiharukana.blog.shinobi.jp/Entry/1174/
array_flatten3()にarray_splice()やったらarray_flatten3()より早くなりました。
クッソってほどではないのでなんか間違ってる気もしますが。
http://bloggdgd.blog28.fc2.com/blog-entry-272.html
> 「Recursive」って入ってるけど
なんてこった、Recursiveは再帰って発想はなかった。
いや、皮肉とかじゃなくて素で気付いてなかった。
さて、せっかくなので色々見つけたarray_flatten関数のパフォーマンスを測定してみます。
<?php
// by NurseAngel
// http://yuubiseiharukana.blog.shinobi.jp/Entry/1155/
function array_flatten1($arr) {
return iterator_to_array(new RecursiveIteratorIterator(new RecursiveArrayIterator($arr)), false);
}
// by CertaiN
// http://bloggdgd.blog28.fc2.com/blog-entry-272.html
function array_flatten2($arr) {
$arr = array_values($arr);
while (list($k,$v)=each($arr)) {
if (is_array($v)) {
array_splice($arr,$k,1,$v);
next($arr);
}
}
return $arr;
}
// by Happy Programming
// http://blog.beanz-net.jp/happy_programming/2008/11/phparray-flatten.html
function array_flatten3($array) {
$tmp = array();
while (($val = array_shift($array)) !== null) {
if (is_array($val)) $array = array_merge($val, $array);
else $tmp[] = $val;
}
return $tmp;
}
// by wellandpower at hotmail.com
// http://www.php.net/manual/en/function.array-values.php#77542
function array_flatten4($array) {
$flat = array();
foreach ($array as $value) {
if (is_array($value)) $flat = array_merge($flat, array_flatten4($value));
else $flat[] = $value;
}
return $flat;
}
// by might
// http://might1976.doorblog.jp/archives/51164023.html#more
function array_flatten5($array){
$result = array();
array_walk_recursive($array, function($v) use (&$result){
$result[] = $v;
});
return $result;
}
// by crashrox at gmail dot com
// http://www.php.net/manual/ja/class.recursiveiteratoriterator.php#87757
function array_flatten6($array) {
if($array) {
$flat = array();
foreach(new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::SELF_FIRST) as $key=>$value) {
if(!is_array($value)) {
$flat[] = $value;
}
}
return $flat;
} else {
return false;
}
}
似てるっぽいのは除いて、6種類を適当に試してみました。
array_flatten1()は前回作ったやつで、自力では一切何もせずSPLに任せっきりです。
array_flatten2()はCertaiN氏の「再帰を使わないものでは最も美しい」関数です。
array_splice()なんて初めて見たよ。
array_flatten3()も再帰を使わないパターンですが、array_flatten2()よりは平易に書かれています。
まあループ中で自分自身を更新しててややこしいのはかわらないのですが。
array_flatten4()は、典型的な再帰パターンです。
私が何も参考にせず一から書いたらこうなっていたと思います。
array_flatten5()は再帰にarray_walk_recursive()を用いたパターン。
正直array_walk_recursive()難しいです。
array_flatten6()はおまけです。
中身はarray_flatten1()とほぼ同じで、一番外側のiterator_to_array()だけを手動でやってるわけですが、ビルトイン関数とどちらが早いでしょうか。
| 関数 | 処理時間 | 展開後の個数 |
|---|---|---|
| array_flatten1 | 0.08642秒 | 68228 |
| array_flatten2 | Maximum execution time of 30 seconds exceeded | |
| array_flatten3 | 0.19846秒 | 68228 |
| array_flatten4 | 0.32668秒 | 68228 |
| array_flatten5 | 0.06959秒 | 68228 |
| array_flatten6 | 0.11780秒 | 68228 |
展開後の個数は動作確認用です。
最大10階層のランダム構造、変数の個数の合計は見てのとおり68228個の多重配列を1次元に展開しています。
一発取りなので多少の誤差はありますが、何回かやっても時間が逆転するほどの違いはありませんでした。
1位はarray_flatten5。
array_walk_recursive()ちょっぱや!
2位はarray_flatten1。
簡潔で美しい上に速度も速い。
素晴らしい。
--0.1秒の壁--
3位はarray_flatten6。
foreachを手動で回したぶんだけ遅くなっているのでしょうか。
こーゆーのはビルトイン使っとけば基本問題無いんですよ。
4位はarray_flatten3。
array_flatten2とあまり変わらないように見えるのですが早いですね。
--0.2秒の壁--
5位はarray_flatten4。
再帰はやはり何度も関数呼び出しが入るぶん遅くなってしまうのでしょうか。
やってることはそんなに変わらないはずのarray_walk_recursive()に比べて4倍近い時間がかかっていますね。
--0.4秒の壁--
残念ながらarray_flatten2は問題外という結果になりました。
幾ら美しくても実用に耐えなくては役に立ちません。
ちなみに計測に用いた配列は以下で生成しました。
function mkarray($depth = 0, $stop = 0){
if($depth <= 0 || $stop !== 0){
return mt_rand(1, 10);
}else{
$ret = array_fill(0, mt_rand(1, 10), '1');
foreach($ret as $key=>$val){
$ret[$key] = mkarray(($depth-1), mt_rand(0, 2));
}
return $ret;
}
}
$arr = mkarray(10);
何度かvar_export()して、大きめの配列が生成されたときにそれを保存して使用しました。2013/05/29追記
http://bloggdgd.blog28.fc2.com/blog-entry-278.html
CertaiN氏の検証によりますと、array_merge()とarray_splice()そのものについてはarray_splice()のほうが早いようです。
Cのソース読めないので本当はどういう実装になっているのかは知らないのですが、マニュアルのコメント欄などでも「array_splice()の方がはえーよ」との声が多いようです。
http://php.net/manual/ja/function.array-splice.php
http://php.net/manual/ja/function.array-merge.php
従ってarray_flatten2()がタイムアウトでarray_flatten4()がそれなりに早い理由は、それ以外の部分(おそらくforeachとwhile)にあるであろうということです。
2013/05/30追記
http://yuubiseiharukana.blog.shinobi.jp/Entry/1174/
array_flatten3()にarray_splice()やったらarray_flatten3()より早くなりました。
クッソってほどではないのでなんか間違ってる気もしますが。
http://bloggdgd.blog28.fc2.com/blog-entry-272.html
> これより美しいコード書いた奴いたら名乗り出てこい!!11
個人的には、リンク先にあるこれが一番美しいと思うんですよね。
というわけで上記を関数に仕立て上げた、自称最も美しいarray_flatten。
iterator_to_array()が微妙だ…
RecursiveIteratorIteratorは値を突っ込んだ時点ではそのままの構造が保持されており、foreach()で取り出さないとフラットになってくれません。
とか書いても元の値が戻ってくるだけです。
こっちみたいに書ければ自称を外していいほど最強だったのにまったくもって非常に残念。
ていうか、せっかく自分で調べたのにAdil Baig @ AIdezignsって人がとっくに回答済だった。
> これより美しいコード書いた奴いたら名乗り出てこい!!11
function array_flatten($arr) {
$arr = array_values($arr);
while (list($k,$v)=each($arr)) {
if (is_array($v)) {
array_splice($arr,$k,1,$v);
next($arr);
}
}
return $arr;
}
個人的には、リンク先にあるこれが一番美しいと思うんですよね。
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($a));
foreach($it as $v) {
echo $v, " ";
}
array_splice()みたいなややこしい関数も使ってなくて圧倒的に見やすいですし。というわけで上記を関数に仕立て上げた、自称最も美しいarray_flatten。
function array_flatten($arr) {
return iterator_to_array(new RecursiveIteratorIterator(new RecursiveArrayIterator($arr)), false);
}
iterator_to_array()が微妙だ…
RecursiveIteratorIteratorは値を突っ込んだ時点ではそのままの構造が保持されており、foreach()で取り出さないとフラットになってくれません。
function array_flatten_fail($arr) {
return (new RecursiveIteratorIterator(new RecursiveArrayIterator($arr)))->getArrayCopy();
}
とか書いても元の値が戻ってくるだけです。
こっちみたいに書ければ自称を外していいほど最強だったのにまったくもって非常に残念。
ていうか、せっかく自分で調べたのにAdil Baig @ AIdezignsって人がとっくに回答済だった。
PHP5.3でSplPriorityQueue、優先度つきキューが実装されました。
取得時、第二引数の値が高いものから順に並べ替えられます。
出力
件数:5
一番目:最優先
キー:5 値:最優先
キー:4 値:にばんめその2
キー:3 値:にばんめその1
キー:2 値:低い
キー:1 値:一番低い
件数:0
優先したいデータから順に表示することができました。
なお、同じ優先度内での順番がどうなるかは保証されていません。
実用としてはわりと困ることに、Wikipediaの定義通りの実装がなされています。
どういうことって一度使ったら無くなってしまうんですよね。
具体的にはSplPriorityQueue::next()したときに、イテレータを進めるかわりにshiftするので値が消滅します。
なので繰り返し使う場合はcloneするか、配列に取り出すなどしてから扱う必要があります。
取得時、第二引数の値が高いものから順に並べ替えられます。
<?php
$queue = new SplPriorityQueue();
$queue->insert('低い', 10);
$queue->insert('最優先', 100);
$queue->insert('にばんめその1', 50);
$queue->insert('にばんめその2', 50);
$queue->insert('一番低い', 1);
// 個数 → 5
print('件数:' . $queue->count());
// 一番目を閲覧。これは使っても無くならない。
print('一番目:' . $queue->top());
// 優先順位の高い順に取り出す
foreach($queue as $key=>$val){
print('キー:' . $key . ' 値:' . $val);
}
// 一度使うと無くなる → 0
print('件数:' . $queue->count());
// rewindは意味がない
$queue->rewind();
出力
件数:5
一番目:最優先
キー:5 値:最優先
キー:4 値:にばんめその2
キー:3 値:にばんめその1
キー:2 値:低い
キー:1 値:一番低い
件数:0
優先したいデータから順に表示することができました。
なお、同じ優先度内での順番がどうなるかは保証されていません。
実用としてはわりと困ることに、Wikipediaの定義通りの実装がなされています。
どういうことって一度使ったら無くなってしまうんですよね。
具体的にはSplPriorityQueue::next()したときに、イテレータを進めるかわりにshiftするので値が消滅します。
なので繰り返し使う場合はcloneするか、配列に取り出すなどしてから扱う必要があります。
http://memocarilog.info/wordpress/5278
まず思ったこと。
「はてなブックマークのタイトルにスクリプトって入れられるかな。」
はてなを一切使用したことがないので仕様とか知らないんですけどね。
なお参照先のWEB Drawer『JavaScriptを使って、自サイトでの「はてなブックマーク」の人気記事一覧を表示する』でも、無邪気に$().append()で追加してますね。
まあタイトルが長ければ途中で切られてしまうようですし、実質的に意味のあるインジェクションを行うのは難しいでしょうけど、外部からの変数を利用する場合は必ずhtmlspecialchars()を使用しましょう。
JavaScriptであれば、文字列連結ではなくinnerTextなどを使うのが安全でしょう。
まあレスポンスはJSONPなくらいだし、この場合はリンク先を完全に信用するのが前提なのかもしれませんが、だからといって今回は信用できる、こっちは信用できない、なんてやってたら間違いなく事故ります。
外部からの値は常に信用できないものとして取り扱うべきです。
ちなみにリンク先のことくらいであれば、わざわざCURL使わなくても@file_get_contents($url)の一行で終わるのは秘密。
まず思ったこと。
「はてなブックマークのタイトルにスクリプトって入れられるかな。」
はてなを一切使用したことがないので仕様とか知らないんですけどね。
なお参照先のWEB Drawer『JavaScriptを使って、自サイトでの「はてなブックマーク」の人気記事一覧を表示する』でも、無邪気に$().append()で追加してますね。
まあタイトルが長ければ途中で切られてしまうようですし、実質的に意味のあるインジェクションを行うのは難しいでしょうけど、外部からの変数を利用する場合は必ずhtmlspecialchars()を使用しましょう。
JavaScriptであれば、文字列連結ではなくinnerTextなどを使うのが安全でしょう。
まあレスポンスはJSONPなくらいだし、この場合はリンク先を完全に信用するのが前提なのかもしれませんが、だからといって今回は信用できる、こっちは信用できない、なんてやってたら間違いなく事故ります。
外部からの値は常に信用できないものとして取り扱うべきです。
ちなみにリンク先のことくらいであれば、わざわざCURL使わなくても@file_get_contents($url)の一行で終わるのは秘密。
Aura.Uriは、URLなどを論理的に作成・分析できますよというクラスです。
ってつい先日やった気がするな。
さて、どちらが優秀か試してみましょう。
Factory::getInstance($http)みたいなstaticメソッドが無く、いちいち無意味なインスタンス化をしないといけないのがよくわかりません。
基本的にZend\Uri\Uriと同等のことができますが、is**の検証系メソッドはありません。
AuraはtoString()のようなメソッドをあまり持たず、マジックメソッド__toString()で文字列変換を実装しています。
なので文字列にしたい場合はStringにキャストしましょう。
プロパティは概ねStringですが、pathとqueryだけクラスになっています。
そしてこのAura\Uri\Queryには問題があります。
まずQuery::buildString()が何故かprotectedなので、Zend\Uri\Uri::setQueryのような配列形式での投入ができません。
そしてヘルプでは$url->query->baz = 'zab';とか書いてありますが、実はこれが正しく動作しません。
Aura\Uri\Queryは\ArrayObjectをextendsしているのですが、呼び出しの引数に\ArrayObject::ARRAY_AS_PROPSを記述していないせいで、プロパティと配列が別の値として扱われます。
Aura\Uri\Queryは内部値を配列形式で取り扱っているため、プロパティで指定した値は反映されません。
思いっきりバグじゃねーかこれ。
指定するときは配列形式で指定するか、setFromString()を使いましょう。
なおsetFromString()は他の値を全部消して上書きするので、上記例ではaとbが消えています。
AuraPHPの記事
ってつい先日やった気がするな。
さて、どちらが優秀か試してみましょう。
<?php
require_once('path/to/channel/vendor/autoload.php');
// Aura.Uri
$http = 'http://example.com/foo/../bar.php?a=1&b=2#hoge';
$factory = new Aura\Uri\Url\Factory($_SERVER);
$uri = $factory->newInstance($http);
// 分析
// スキーマを取得
$uri->scheme; // http
// ユーザ名/パスワードを取得
$uri->user; // NULL
$uri->pass; // NULL
// ホスト名を取得
$uri->host; // example.com
// ポート番号を取得
$uri->port; // NULL
// パスを取得
(string)$uri->path; // /foo/../bar.php
(string)$uri->query; // a=1&b=2
$uri->query->getArrayCopy(); // array('a'=>'1', 'b'=>'2')
$uri->fragment; // hoge
// 参照を解消
$uri->get(); // /fuga.php?c=3&d=4
// 作成
$uri->setScheme('https');
$uri->setUser('user');
$uri->setPass('pass');
$uri->setHost('example.jp');
$uri->setPort(443);
$uri->path->setFromString('/fuga.php');
$uri->setFragment('');
// $uri->query->buildString(array('c'=>3, 'd'=>4)); // Fatal error:Call to protected method
$uri->query->a = 5; // 正しく動作しない
$uri->query['b'] = 6; // OK
$uri->query->setFromString('c=3&d=4'); // OK
(string)$uri; // https://user:pass@example.jp:443/fuga.php?c=3&d=4
Factory::getInstance($http)みたいなstaticメソッドが無く、いちいち無意味なインスタンス化をしないといけないのがよくわかりません。
基本的にZend\Uri\Uriと同等のことができますが、is**の検証系メソッドはありません。
AuraはtoString()のようなメソッドをあまり持たず、マジックメソッド__toString()で文字列変換を実装しています。
なので文字列にしたい場合はStringにキャストしましょう。
プロパティは概ねStringですが、pathとqueryだけクラスになっています。
そしてこのAura\Uri\Queryには問題があります。
まずQuery::buildString()が何故かprotectedなので、Zend\Uri\Uri::setQueryのような配列形式での投入ができません。
そしてヘルプでは$url->query->baz = 'zab';とか書いてありますが、実はこれが正しく動作しません。
Aura\Uri\Queryは\ArrayObjectをextendsしているのですが、呼び出しの引数に\ArrayObject::ARRAY_AS_PROPSを記述していないせいで、プロパティと配列が別の値として扱われます。
Aura\Uri\Queryは内部値を配列形式で取り扱っているため、プロパティで指定した値は反映されません。
思いっきりバグじゃねーかこれ。
指定するときは配列形式で指定するか、setFromString()を使いましょう。
なおsetFromString()は他の値を全部消して上書きするので、上記例ではaとbが消えています。
AuraPHPの記事