Hatena::ブログ(Diary)

風柳メモ このページをアンテナに追加 RSSフィード Twitter

2014-10-18

Amazon.co.jpの正規化URLの構造を調べてみた

承前

【amzRememberOptions】Amazon.co.jpで検索のカテゴリー&並び替えオプションを保存するユーザースクリプト試作 - 風柳メモ

Amazon.co.jpで著者検索 - Hatena::Let

これらを作る前後にて、Amazon.co.jp の正規化URL(link[rel="canonical"] の href 値)等について調べてみたことの覚書。

きちんと検証しているわけではないので注意。誤り等あった場合はご指摘願う。


個別商品ページ

正規化URLの構造
http://www.amazon.co.jp/[商品名]/dp/[ASIN]
  • [商品名] はエンコードされている。また、"[商品名]/"の部分は省略可。
通常の個別商品ページURL例http://www.amazon.co.jp/お前は俺を殺す気か-1-シギサワ-カヤ/dp/4592710533/ref=sr_1_2?s=books&ie=UTF8&qid=1413583689&sr=1-2&keywords=お前は俺を殺す気か
正規化URLhttp://www.amazon.co.jp/お前は俺を殺す気か-1-シギサワ-カヤ/dp/4592710533

検索ページ(カテゴリー指定)

正規化URLの構造
http://www.amazon.co.jp/[キーワード]-[カテゴリー名]/s?ie=UTF8&page=[ページ番号]&rh=[第1要素],k:[キーワード]
  • [キーワード]および[カテゴリー名] はエンコードされている。また、"[キーワード]-[カテゴリー名]/" の部分は省略可。
  • “すべてのカテゴリー”で検索する場合、"-[カテゴリー名]"の部分は入らない。
  • [第1要素]は、カテゴリー名が“すべてのカテゴリー”の場合には、"i:aps"固定、それ以外は"n:[BrowseNode]"が入る。
  • 絞り込みを行うと、rhパラメータに「,[第3要素],[第4要素]…」というふうに追加されていく。
    各要素は“p_*:*”といった形式のようだが、詳細は不明。
通常の検索ページURL例http://www.amazon.co.jp/s/ref=nb_sb_noss_1?__mk_ja_JP=カタカナ&url=search-alias=stripbooks&field-keywords=お前は俺を殺す気か
正規化URLhttp://www.amazon.co.jp/お前は俺を殺す気か-本/s?ie=UTF8&page=1&rh=n:465392,k:お前は俺を殺す気か
BrowseNode一覧
カテゴリー名search-aliasBrowseNode
Kindleストア digital-text2250738051
Amazon インスタント・ビデオinstant-video2351649051
デジタルミュージックdigital-music2128134051
Androidアプリmobile-apps2381130051
stripbooks465392
洋書english-books52033011
ミュージックpopular561956
クラシックclassical701040
DVDdvd561958
TVゲームvideogames637394
PCソフトsoftware637392
パソコン・周辺機器computers2127209051
家電&カメラelectronics3210981
文房具・オフィス用品office-products86731051
ホーム&キッチンkitchen3828871
ペット用品pets2127212051
ヘルス&ビューティーhpc160384011
コスメbeauty52374051
食品&飲料food-beverage57239051
ベビー&マタニティbaby344845011
服&ファッション小物apparel352484011
シューズ&バッグshoes2016926051
腕時計watch324025011
ジュエリーjewelry85895051
おもちゃtoys13299531
ホビーhobby2277721051
楽器mi2123629051
スポーツ&アウトドアsporting14304371
カー・バイク用品automotive2017304051
DIY・工具diy2016929051
大型家電appliances2277724051
クレジットカードfinancial2320455051
ギフト券gift-cards2351652051

SearchIndexとBrowseNode一覧 - 前人未踏の領域へ等を参照するに、BrowseNodeはProduct Advertising APIで使用されているものと共通だと思われる。


検索ページ(著者名等指定)

正規化URLの構造
http://www.amazon.co.jp/[カテゴリー名]-[著者名等*]/s?ie=UTF8&page=[ページ番号]&rh=n:[BrowseNode],[第2要素名]:[著者名等]

  • [カテゴリー名]および[著者名等]はエンコードされている(PATHに含まれる[著者名等*]の方は、スペースが'-'に変換される模様)。また、"[カテゴリー名]-[著者名等*]/" の部分は省略可。
  • [BrowseNode]および[第2要素名]の組み合わせの例は次表参照。
    search-aliasフィールド名BrowseNode第2要素名備考
    (任意)field-keywords(任意)kキーワード
    books-jpfield-author465392p_27著者(和書)
    books_usfield-author52033011p_27著者(洋書)
    music-artistfield-artist561956p_32アーティスト
  • 絞り込みを行うと、rhパラメータに「,[第3要素],[第4要素]…」というふうに追加されていく。
    各要素は“p_*:*”といった形式のようだが、詳細は不明。

例1: 著者(和書)
通常の検索ページURL例http://www.amazon.co.jp/s/ref=dp_byline_sr_book_1?ie=UTF8&field-author=シギサワ+カヤ&search-alias=books-jp&text=シギサワ+カヤ
正規化URLhttp://www.amazon.co.jp/本-シギサワ-カヤ/s?ie=UTF8&page=1&rh=n:465392,p_27:シギサワ カヤ
例2: 著者(洋書)
通常の検索ページURL例http://www.amazon.co.jp/s/ref=dp_byline_sr_book_1?ie=UTF8&field-author=Travis+Swicegood&search-alias=books-us&text=Travis+Swicegood
正規化URLhttp://www.amazon.co.jp/洋書-Travis-Swicegood/s?ie=UTF8&page=1&rh=n:52033011,p_27:Travis Swicegood
例3: アーティスト
通常の検索ページURL例http://www.amazon.co.jp/s/ref=dp_byline_sr_music_3?ie=UTF8&field-artist=山本正之&search-alias=music-artist
正規化URLhttp://www.amazon.co.jp/-アーティスト名-山本正之/s?ie=UTF8&page=1&rh=n:561956,p_32:山本正之

著者ページ(アーティスト等も含む)

正規化URLの構造
http://www.amazon.co.jp/[著者名]/e/[ASIN(?)]
  • [著者名] はエンコードされている。また、"-"一文字に置換可能。
  • [ASIN(?)] は著者毎に割り振られた一意の記号番号だと思われる。
通常の著者ページURL例http://www.amazon.co.jp/秋★枝/e/B00J1Z7E5E/ref=ntt_athr_dp_pel_pop_1
正規化URLhttp://www.amazon.co.jp/秋★枝/e/B00J1Z7E5E

Google ChromeのコンテキストメニューにAmazon著者検索を追加する(Context Menu Search使用)

承前


Context Menu Search に登録してみる

Google Chromeの拡張機能である

Context Menu Search - Chrome Web Store

をインストールし、オプション設定の「Add search engines」メニューで、

f:id:furyu-tei:20141018205818p:image

Display nameLink
Amazon著者検索http://www.amazon.co.jp/s?ie=UTF8&page=1&rh=n:465392,p_27:TESTSEARCH&sort=date-desc-rank&unfiltered=1

のように入力して、[Add new option]ボタンを押す。


あとは、任意のページで、作者名をドラッグして選択→右クリックでコンテキストメニューを出すと、

f:id:furyu-tei:20141018205816p:image

こんな感じで、「Context Menu Search」→「Amazon著者検索」から検索できる。


他にも、この記事を参考にして、例えば、

Display nameLink
Amazon和書検索http://www.amazon.co.jp/s?ie=UTF8&page=1&rh=n:465392,k:TESTSEARCH&sort=date-desc-rank&unfiltered=1

のようなこともできる。

2014-10-17

【amzRememberOptions】Amazon.co.jpで検索のカテゴリー&並び替えオプションを保存するユーザースクリプト試作

Amazon.co.jpのキーワード検索で新規に検索しようとすると、オプションがデフォルトに戻ってしまう(カテゴリーが「すべてのカテゴリー」・並び替えが「キーワードに関連する商品」)。

自分の場合、検索対象は9割方は本なのに、いちいち指定しなおすのが煩わしかったので、これらを保存して次回検索時に反映するようなユーザースクリプトを試作してみた。

ちょっと動作があやしいところもあるけれども。。。


あと、個人的に、著作ページがまだ今一つ使えない(作品が網羅されていない等)ので、著作ページの場合は強制的に著者検索ページに移動するようにしてみた。

スクリプト中のREPLACE_AUTHOR_URLをfalseにすれば、この機能は無効化される。


ダウンロード(GitHubにて公開

【amzRememberOptions】Amazon.co.jpの検索オプションを保存するユーザースクリプト

furyutei/amzRememberOptions ? GitHub

インストール

Firefox + Greasemonkey
  1. 上記のダウンロードリンクをクリックし、指示に従ってインストール。
Google Chrome + Tampermonkey
  1. 上記のダウンロードリンクをクリックし、指示に従ってインストール。
その他ブラウザ

(未確認)


ひとりごと

いつものように勢いで作ってしまったのだけれど、実は標準でこれらのオプションが保存される方法があるとか、あったりする……?

2014-10-01

連結リストになっているテーブルの関連するレコードのみをSELECTするためのSQL文を知りたい

たとえば、MySQL上で次のようなテーブルを定義して、

CREATE TABLE `t_history` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `memo` text NOT NULL,
  `next_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

次のようにレコードがセットされているときに、

mysql> SELECT * FROM t_history ORDER BY id;
+----+-----------------+---------+
| id | memo            | next_id |
+----+-----------------+---------+
|  1 | A               |       4 |
|  2 | B               |       7 |
|  3 | C               |       6 |
|  4 | A 変更その1     |       5 |
|  5 | A 変更その2     |       8 |
|  6 | C 変更その1     |    NULL |
|  7 | B 変更その1     |       9 |
|  8 | A 変更その3     |    NULL |
|  9 | B 変更その2     |    NULL |
+----+-----------------+---------+

id=8(A の変更の最新: next_id = NULL)を指定したら、

+----+-----------------+---------+
| id | memo            | next_id |
+----+-----------------+---------+
|  1 | A               |       4 |
|  4 | A 変更その1     |       5 |
|  5 | A 変更その2     |       8 |
|  8 | A 変更その3     |    NULL |
+----+-----------------+---------+

のような結果(A の履歴のみを抽出)を取得したいのだけれど、これを一発で取り出せるような SQL 文はないだろうか…?

追記(2014/10/13)

コメントで教えて頂きました。

PostgreSQLの with recursive をMySQLでエミュレートする | Hack


うーん…やっぱり連結リストにするより、たとえば

CREATE TABLE `t_history` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `memo` text NOT NULL,
  `root_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

みたいな構造にして、

mysql> SELECT * FROM t_history ORDER BY id;
+----+-----------------+---------+
| id | memo            | root_id |
+----+-----------------+---------+
|  1 | A               |    NULL |
|  2 | B               |    NULL |
|  3 | C               |    NULL |
|  4 | A 変更その1     |       1 |
|  5 | A 変更その2     |       1 |
|  6 | C 変更その1     |       3 |
|  7 | B 変更その1     |       2 |
|  8 | A 変更その3     |       1 |
|  9 | B 変更その2     |       2 |
+----+-----------------+---------+

な感じにレコード挿入していって、

mysql> SELECT * FROM t_history WHERE id = 1 OR root_id = 1 ORDER BY id;
+----+-----------------+---------+
| id | memo            | root_id |
+----+-----------------+---------+
|  1 | A               |    NULL |
|  4 | A 変更その1     |       1 |
|  5 | A 変更その2     |       1 |
|  8 | A 変更その3     |       1 |
+----+-----------------+---------+

で履歴を取得、みたいな方が素直かなぁ…。

これなら途中のレコードを消しても、連結リストみたいに繋げなおす必要もないし。

2014-09-24

PHPのリファレンス(参照)について、自分なりにかみくだいてみる

経緯

最近、PDO で PDOStatement::bindParam を使う処理ではまったため。


bindParam()は、

public bool PDOStatement::bindParam ( mixed $parameter , mixed &$variable [, int $data_type = PDO::PARAM_STR [, int $length [, mixed $driver_options ] )

PHP: PDOStatement::bindParam - Manual

第二引数($variable)が、SQL ステートメントパラメータにバインドする変数名になるので、当然ながらリファレンス(参照)渡しになっている。


それで、はまったときのコードは、

<?php
// (略) ※この部分にデータベースハンドル($dbh)取得処理等

$stmt = $dbh->prepare("SELECT * FROM table_sample WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);

$id_list = array(1,2,3); // 行が存在する id のリスト
foreach ($id_list as &$id) { // ←【問題個所】 &$id を $id に書き換えることで、正常動作するようになる
    $stmt->execute();
    $row = $stmt->fetch(PDO::FETCH_ASSOC); // bool(false) となり、取得できない
    // 以下略
}

のようなもの。


変数 $id にバインドし、foreach でこれを次々と変更しながら実行する、という意図だったのだが、foreach で参照渡しにしていると、動作しない。


参照渡しを止めれば動作するようにはなるのだが、「どうしてなのか?」をきちんと説明できなかったので、そもそもPHPのリファレンスはどういう仕様なのか、というところから調べてみた次第。


さて、PHP におけるリファレンスとは?

まずは、マニュアルを参照。

PHP において、リファレンスとは同じ変数の内容を異なった名前で コールすることを意味します。これは C のポインタとは異なります。 リファレンスを使ってポインタの演算をすることはできませんし、 リファレンスは実メモリのアドレスでもありません。詳細は リファレンスが行わないこと を参照ください。 そうではなく、リファレンスはシンボルテーブルのエイリアスです。 PHP では、変数名と変数の内容は異なっており、 このため、同じ内容は異なった複数の名前を有する事が可能であることに 注意してください。最も良く似ているのは、Unix のファイル名とファイルの 関係です。この場合、変数名はディレクトリエントリ、変数の内容は ファイル自体に対応します。リファレンスは、Unix ファイルシステムの ハードリンクのようなものであると考えられます。

PHP: リファレンスとは? - Manual

むぅ、わかるようなわからないような…やっぱり、いまひとつピンとこない。

なまじ、C言語のポインタを知っているから、混乱しているのだろうか。


具体的に違和感を覚えていたポイント

以下のような PHP プログラムについて考えてみた。

<?php
$no = 1;
function    prn(&$a, &$b, &$c) {
    global $no;
    echo("({$no}) \$a={$a} \$b={$b} \$c={$c}\n");
    $no++;
}
          prn($a, $b, $c);  //  (1) $a= $b= $c=
$b = &$a; prn($a, $b, $c);  //  (2) $a= $b= $c=
$a = 'A'; prn($a, $b, $c);  //  (3) $a=A $b=A $c=
$b = 'B'; prn($a, $b, $c);  //  (4) $a=B $b=B $c=
$c = 'C'; prn($a, $b, $c);  //  (5) $a=B $b=B $c=C
$a = &$c; prn($a, $b, $c);  //  (6) $a=C $b=B $c=C
$a = "X"; prn($a, $b, $c);  //  (7) $a=X $b=B $c=X
$b = "Y"; prn($a, $b, $c);  //  (8) $a=X $b=Y $c=X
$c = "Z"; prn($a, $b, $c);  //  (9) $a=Z $b=Y $c=Z
$a = $b ; prn($a, $b, $c);  //  (10) $a=Y $b=Y $c=Y
$b = "O"; prn($a, $b, $c);  //  (11) $a=Y $b=O $c=Y
$c = $b ; prn($a, $b, $c);  //  (12) $a=O $b=O $c=O

違和感を覚えたのは、

  • (2) で、「$b = &$a」としてリファレンス代入を行い、その後は $a に値を代入すると $b にも反映されるようになっている(3)。
  • ところが、(6) で「$a = &$c」として、$a に対してリファレンス代入を行うと、$b に対しては直接何もしていないにも関わらず、以降は $a を変更しても、$b には反映されなくなってしまう(7)。

というところ。

直感的に「$a にリファレンス代入等の操作を加えたとしても、$b は影響されずに $a を指し示したままであり、$a の内容を書き換えると、そのまま $b にも反映される」ものとばかり思っていた。


自分なりのリファレンス代入の解釈

マニュアルを読み返すなどして、ようやく理解できた(かも)。

上述のプログラムの、動作概要を図に示すと、

http://f.st-hatena.com/images/fotolife/f/furyu-tei/20140924/20140924114654_original.png

のようになると思われる。

ただし、上記プログラム内では表示(echo)をしている関係上、まだ割り当てていない変数も参照され、このときにNULLが割り当てられてしまうので、図とは厳密には異なってくる。


ポイントとしては、

  • (2) で、$a が初めて参照されているが、変数が初めて参照される際には、新規に値"NULL"(もしくは、通常の代入の場合は右辺の値)が入った内容が確保され、変数(図では$a)は当該内容を示すシンボル(同s1)を保持する。
  • (2) の「$b = &$a」というリファレンス代入では、変数 $a の内容を示すシンボル(同s1)を、変数 $b にコピーしている。
    結果として、$a と $b は、同じ内容を指し示すシンボル(s1)*1を持った、完全に等価な変数となる。
  • (3) の「$a = "A"」では、$a が持つシンボル(s1)が示す内容の値を書き換える(NULL→"A")。すると、同じs1を保持する $b も同じ内容を持つことになる。
    また、(4) の「$b = "B"」では、逆に $b の値を書き換えることで、同じs1を保持する $a も同じ内容になる。
    すなわち、この時点での $a と $b とは、実際にまったくの等価であることを示している。
  • (6) の「$a = &$c」というリファレンス代入では、変数 $c の内容を示すシンボル(同s2)を、変数 $a にコピーしている。
    結果として、$c と $a は、同じ内容を指し示すシンボル(s1)を持った、完全に等価な変数となる。
    代わりに、$a(シンボル:s2) と $b(シンボル:s1) とでは、指し示す内容が異なることになり、違う値を示すようになる。

C言語畑の人向けに解説

C言語に慣れている人用に、PHP のリファレンス代入を、疑似的にC言語で表現してみた。

/*
 * PHPの変数代入とリファレンス代入の動作をC言語に置き換えるサンプル
 */

/*
<?php
function prn(&$a, &$b, &$c) {echo("\$a={$a} \$b={$b} \$c={$c}\n");}

$b = &$a;
$a = "A";
$b = "B";
$c = "C"; prn($a, $b, $c);
$a = &$c; prn($a, $b, $c);
$a = "X"; prn($a, $b, $c);
$b = "Y"; prn($a, $b, $c);
$c = "Z"; prn($a, $b, $c);
$a = $b ; prn($a, $b, $c);
$b = "O"; prn($a, $b, $c);
$c = $b ; prn($a, $b, $c);
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE 1024

void    prn(char * a, char * b, char * c) {printf("$a=%s $b=%s $c=%s\n", a, b, c);}

int     main(void) {
    char * a, * b, * c;
    char * s1, * s2;

    /* $b = &$a; */ s1 = calloc(BUFFER_SIZE, 1); a = s1; b = a;
    /* $a = "A"; */ strcpy(a, "A"); // $a = "A";
    /* $b = "B"; */ strcpy(b, "B"); // $b = "B";
    /* $c = "C"; */ s2 = calloc(BUFFER_SIZE, 1); c = s2; strcpy(c, "C"); prn(a, b, c);
    /* $a = &$c; */ a = c;                                               prn(a, b, c);
    /* $a = "X"; */ strcpy(a, "X");                                      prn(a, b, c);
    /* $b = "Y"; */ strcpy(b, "Y");                                      prn(a, b, c);
    /* $c = "Z"; */ strcpy(c, "Z");                                      prn(a, b, c);
    /* $a = $b ; */ strcpy(a, b);                                        prn(a, b, c);
    /* $b = "O"; */ strcpy(b, "O");                                      prn(a, b, c);
    /* $c = $b;  */ strcpy(c, b);                                        prn(a, b, c);

    return 0;
}

これを実行すると、

$a=B $b=B $c=C
$a=C $b=B $c=C
$a=X $b=B $c=X
$a=X $b=Y $c=X
$a=Z $b=Y $c=Z
$a=Y $b=Y $c=Y
$a=Y $b=O $c=Y
$a=O $b=O $c=O

こんな感じで、PHP と同様の結果になる。


ポイントとしては、PHP における変数 $a・$b に該当するものを、C言語におけるポインタ変数(char *) a・b とみなしたとき、

  • PHP の値代入($a = "A")は、C言語では strcpy(a, "A") に相当。
    ※このとき、ポインタ変数 a の値は変わらない。
  • PHP のリファレンス代入($b = &$a)は、C言語では b = a に相当。
    ※このとき、ポインタ変数 b の値が、ポインタ a のものに置き換わる(結果として、指し示す先が直前とは異なってくる)。

ということ。


C言語では、

b = &a;

のように記述する場合、b はポインタ(例えば int *型)であり、変数 a は実体(例えば int型)で、'&a' は a のアドレスを指す。つまり、変数 a と b とでは、そもそもの型が異なっている。


一方で、PHP では、

$b = &$a;

のように記述すると、これはリファレンス代入であり、(変数が内部的にもっている)内容を指し示すシンボルのコピーという意味あいであり、この結果、変数 $a と $b とは、本質的に等価となる。


なので、('&' という記号だけをみて)上記を混同するとはまってしまう。

どちらかといえば、PHP におけるリファレンス代入は、C言語における「(変数等の)アドレスの、ポインタ変数への代入」よりも、「ポインタ変数からポインタ変数への代入」にイメージとしては近いと考えられる。


最初の問題については?

最初に書いた、bindParam() がらみの不具合だが、foreach ループの 1 回目で、

$id = &$id_list[0];

と等価になるが、これでは $id のシンボルが $id_list[0] のシンボルと同じものへと置換されてしまうため、その前の bindParam() 実行時点で指定した $id の保持していたシンボル(=PDOStatementオブジェクト($stmt)内部で保持している変数のシンボル)と異なってしまい、結果として、SQL ステートメントパラメータ(':id') には、bindParam()実行時点での $id の値(=NULL) が使われてしまうので、意図した動作にならなかった、と考えられる。


リファレンスの罠(追記:2014/09/24)

「配列内部のリファレンスは危険もある」、と。

しかし、配列の内部のリファレンスは危険もあるということに気をつけましょう。 通常の (リファレンスではない) 代入の右辺にリファレンスを使っても 左辺はリファレンスには変わりませんが、配列の内部のリファレンスは通常の代入のままとなります。 これは、関数をコールする際に配列をリファレンスで渡すときも同じです。

PHP: リファレンスが行うことは何ですか? - Manual

こ、これはわかりにくい上に、はまりそうだ…。

リファレンスを使用しない場合

単純に、配列(array)$1 を $2 に代入する場合。

<?php
$a1 = array(1);
$a2 = $a1;
$a2[0] ++; // $a1 == array(1), $a2 == array(2)
$a2[0] ++; // $a1 == array(1), $a2 == array(3)

http://f.st-hatena.com/images/fotolife/f/furyu-tei/20140924/20140924182500_original.png

まぁ、これは特におかしいことはない。

問題は次のようなケース。


配列内の要素をリファレンスする場合
<?php
$a1 = array(1);
$b = &$a1[0]; // $a1[0] を $b にリファレンス代入: $a1[0] と $b は同一リファレンスセットになる(共通のシンボルを持つ)
$a2 = $a1;
$a2[0] ++; // $a1 == array(2), $a2 == array(2) ← $a1[0] の方も変化してしまっている!
unset($b); // ← 後から $b を削除しても…
$a2[0] ++; // $a1 == array(3), $a2 == array(3) ← やっぱり $a1[0] も変化してしまう!

http://f.st-hatena.com/images/fotolife/f/furyu-tei/20140924/20140924182945_original.png

よくわからないが、

  • 配列内要素は、配列のコンテナとは独立して、各々がリファレンスセット(同一のシンボルを持つ、他の変数)を持ちうる。
  • 配列内要素が、他の変数との間でリファレンスセットを構成した後で、当該配列を別の変数に代入すると(リファレンス代入ではない)、当該要素のみが元の要素・変数と同一のリファレンスセットとされてしまう。

言い換えると、配列内のリファレンスの挙動はその要素ごとに決まるということです。 個々の要素のリファレンスに関する動きは、 配列コンテナがリファレンスであるかどうかとは独立しています。

PHP: リファレンスが行うことは何ですか? - Manual

うん、よくわかりません…。

foreach でのリファレンスの罠

配列の各要素を順に処理して変更を加えたい時に、ついつい foreach でリファレンス渡しを使ってしまうのだけれど…。

<?php
$list = array(0, 0, 0);
foreach ($list as &$item) {
    $item ++;
}
// この時点では、$list == array(1, 1, 1) で想定通り
// :
// :しばらく処理が続いて…
// :
$item = 'string'; // 忘れたころに、何気なく $item に値を代入すると…
// $list == array(1, 1, 'string') ← 一番最後の要素が置換されてしまう!

このように、一時的にリファレンス代入用に使用していた変数をうっかり解放し忘れると、思わぬわかりにくいバグの元となってしまう。

foreach でリファレンス渡しを行った後は、

unset($item);

解放を忘れずに。


というわけで、結局のところ…

というわけで、参照渡しをカジュアルにやるのが間違いなのです。関数の戻り値の型の整合性がとれず、やむなく出力引数で表さなければならない場合などを除いて、基本的には使わない。使う意味がない。参照の仕様から来る複雑さに関しては、PHPが悪いというより、基礎を押さえずに用途を勘違いして使うほうが悪いと思います。PHPの変数の基礎を知っていれば、ほとんどの場合使わなくていいということが、おのずとわかると思います。

PHPが糞言語なのはどう考えても参照をポインタだと思っているお前らが悪い - なんたらノート第三期ベータ

PHPのリファレンスの仕様はややこしい(特にarrayに使用する場合)ので、極力使わなくて済むように他の方法を検討するべき、ということ、かな。

少なくとも、パフォーマンス的な理由でリファレンスを使用するメリットというのはなさそうではあるし。

うん、勉強になった!…としておこう(哀)。


この辺りの記事も参考に…

PHPは代入と参照の違い - bravewood の日記

PHPでは配列ではなくオブジェクトに状態を持たせよ - なんたらノート第三期ベータ

*1:なお、"シンボル"という呼び名は、この記事中での便宜上のものである。適切な呼称はなんだろう?

2014-09-22

レンタルサーバでlog4phpを使用するための覚書

log4php とは…

Apache Logging ServicesプロジェクトのPHP用・高機能なログフレームワーク(ログ出力ツール)。

Apache log4php™ is a versatile logging framework for PHP.

Apache log4php - Welcome - Apache log4php

出力先には画面やファイル等を指定でき、ログレベル等も使え、一定サイズでログを切り替えたりといった機能も持つ。


レンタルサーバへのインストール(PEAR経由)

PEARがインストールされていて、正常に使用できていることが前提。

log4php のインストール
  1. Channel Management で "pear.apache.org/log4php" を [Discover Channel]
  2. "pear.apache.org/log4php" チャンネルから、"Apache_log4php" をインストール

使用サンプル

log4php用設定ファイル(log4php.properties)
# log4php.properties: log4php用設定ファイル
# 参考: http://logging.apache.org/log4php/docs/configuration.html


# === アペンダ(appenders)定義
# log4php.appender.{appender_name}
# 参考: http://logging.apache.org/log4php/docs/appenders.html
#       http://logging.apache.org/log4php/docs/layouts/pattern.html

# --- 出力無し設定
log4php.appender.dev_null = LoggerAppenderNull

# --- 画面出力設定
log4php.appender.stdout = LoggerAppenderEcho
log4php.appender.stdout.layout = LoggerLayoutPattern
log4php.appender.stdout.layout.conversionPattern = "%date{Y-m-d H:i:s.u} %-14logger %-8level [%-15X{ADDR} %X{HOST}] %message%newline"

# --- ファイル出力設定(一定サイズでログを切替)
#   http://logging.apache.org/log4php/docs/appenders/rolling-file.html
log4php.appender.file = LoggerAppenderRollingFile
# ■ log4php.appender.{name}.file は絶対パスで指定する必要あり
#  参考: http://stackoverflow.com/questions/15666893/log4php-file-size-error
log4php.appender.file.file = /path_to/app.log
log4php.appender.file.append = true
log4php.appender.file.maxFileSize = 5MB
log4php.appender.file.maxBackupIndex = 5
log4php.appender.file.compress = false
log4php.appender.file.layout = LoggerLayoutPattern
log4php.appender.file.layout.conversionPattern = "%date{Y-m-d H:i:s.u} %-14logger %-8level [%-15X{ADDR} %X{HOST}] %message%newline"


# === ロガー(logger)定義
# 参考: http://logging.apache.org/log4php/docs/loggers.html

# --- root ロガー
# log4php.rootLogger = {log_level}, {appender_name}[, {appender_name} ...]
# ※ rootLogger は、全ての Logger::getLogger({logger_name}) の継承元となる
#   {log_level} → http://logging.apache.org/log4php/docs/introduction.html
#   {appender_name) → アペンダ定義で指定した名称(log4php.appender.{appender_name})
#   <?php $root_logger = Logger::getRootLogger(); // RootLogger 取得 ?>
log4php.rootLogger = FATAL, dev_null


# --- 名前付きロガー
# log4php.logger.{logger_name} = {log_level}, {appender_name}[, {appender_name} ...]
# ※ 設定名({logger_name}) をLogger::getLogger({logger_name})で指定すると、log4php.rootLogger の継承+指定した設定のログとなる
# ※ 本設定ファイル内で定義されていない{logger_name}を指定すると、root ロガーと等価になる
#   {logger_name} → Logger::getLogger({logger_name})で指定すると、log4php.rootLogger の継承+指定したアペンダのロガーが取得される
#   <?php $sample_logger = Logger::getLogger('sample'); // 名前を指定して Logger 取得 ?>
log4php.logger.DefaultLogger = WARN, file
log4php.logger.DebugLogger = DEBUG, stdout, file


注意点として、


サンプルPHPファイル(test_log.php)
<?php
require_once('log4php/Logger.php');

// === 設定ファイル読込
Logger::configure('log4php.properties');

// === MDC (_mapped diagnostic contexts_) 設定
// ※ log4php.appender.{appender_name}.layout.conversionPattern で、'%X{ADDR}' のようにして参照可能
// http://logging.apache.org/log4php/apidocs/class-LoggerMDC.html
LoggerMDC::put('ADDR', isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '-');
LoggerMDC::put('HOST', isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : '-');

// === ロガー取得
$default_logger = Logger::getLogger('DefaultLogger');
$debug_logger = Logger::getLogger('DebugLogger');
$separator_logger = Logger::getLogger('separator');
$separator_logger->addAppender($debug_logger->getAppender('stdout'));
$separator_logger->addAppender($debug_logger->getAppender('file'));
$separator_logger->setLevel(LoggerLevel::getLevelDebug());

// === テスト
$separator_logger->debug('1) DefaultLogger<br />');
$default_logger->trace('trace1<br />');
$default_logger->debug('debug1<br />');
$default_logger->info('info1<br />');
$default_logger->warn('warn1<br />');
$default_logger->error('error1<br />');
$default_logger->fatal('fatal1<br />');
$separator_logger->debug('==========<br />');

$separator_logger->debug('2) DebugLogger<br />');
$debug_logger->trace('trace2<br />');
$debug_logger->debug('debug2<br />');
$debug_logger->info('info2<br />');
$debug_logger->warn('warn2<br />');
$debug_logger->error('error2<br />');
$debug_logger->fatal('fatal2<br />');
$separator_logger->debug('==========<br />');

$separator_logger->debug('3) DefaultLogger (TRACE)<br />');
$default_logger->setLevel(LoggerLevel::getLevelTrace());
$default_logger->trace('trace3<br />');
$default_logger->debug('debug3<br />');
$default_logger->info('info3<br />');
$default_logger->warn('warn3<br />');
$default_logger->error('error3<br />');
$default_logger->fatal('fatal3<br />');
$separator_logger->debug('==========<br />');

$separator_logger->debug('4) DebugLogger (TRACE)<br />');
$debug_logger->setLevel(LoggerLevel::getLevelTrace());
$debug_logger->trace('trace4<br />');
$debug_logger->debug('debug4<br />');
$debug_logger->info('info4<br />');
$debug_logger->warn('warn4<br />');
$debug_logger->error('error4<br />');
$debug_logger->fatal('fatal4<br />');
$separator_logger->debug('==========<br />');

$separator_logger->debug('5) DefaultLogger (FATAL)<br />');
$default_logger->setLevel(LoggerLevel::getLevelFatal());
$default_logger->trace('trace5<br />');
$default_logger->debug('debug5<br />');
$default_logger->info('info5<br />');
$default_logger->warn('warn5<br />');
$default_logger->error('error5<br />');
$default_logger->fatal('fatal5<br />');
$separator_logger->debug('==========<br />');

$separator_logger->debug('6) DebugLogger (FATAL)<br />');
$debug_logger->setLevel(LoggerLevel::getLevelFatal());
$debug_logger->trace('trace6<br />');
$debug_logger->debug('debug6<br />');
$debug_logger->info('info6<br />');
$debug_logger->warn('warn6<br />');
$debug_logger->error('error6<br />');
$debug_logger->fatal('fatal6<br />');
$separator_logger->debug('==========<br />');

結果(画面出力)
2014-09-21 23:38:50.836 separator DEBUG [xxx.xxx.xxx.xxx -] 1) DefaultLogger
2014-09-21 23:38:50.838 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
2014-09-21 23:38:50.838 separator DEBUG [xxx.xxx.xxx.xxx -] 2) DebugLogger
2014-09-21 23:38:50.838 DebugLogger DEBUG [xxx.xxx.xxx.xxx -] debug2
2014-09-21 23:38:50.839 DebugLogger INFO [xxx.xxx.xxx.xxx -] info2
2014-09-21 23:38:50.839 DebugLogger WARN [xxx.xxx.xxx.xxx -] warn2
2014-09-21 23:38:50.839 DebugLogger ERROR [xxx.xxx.xxx.xxx -] error2
2014-09-21 23:38:50.840 DebugLogger FATAL [xxx.xxx.xxx.xxx -] fatal2
2014-09-21 23:38:50.840 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
2014-09-21 23:38:50.840 separator DEBUG [xxx.xxx.xxx.xxx -] 3) DefaultLogger (TRACE)
2014-09-21 23:38:50.842 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
2014-09-21 23:38:50.842 separator DEBUG [xxx.xxx.xxx.xxx -] 4) DebugLogger (TRACE)
2014-09-21 23:38:50.842 DebugLogger TRACE [xxx.xxx.xxx.xxx -] trace4
2014-09-21 23:38:50.843 DebugLogger DEBUG [xxx.xxx.xxx.xxx -] debug4
2014-09-21 23:38:50.843 DebugLogger INFO [xxx.xxx.xxx.xxx -] info4
2014-09-21 23:38:50.843 DebugLogger WARN [xxx.xxx.xxx.xxx -] warn4
2014-09-21 23:38:50.844 DebugLogger ERROR [xxx.xxx.xxx.xxx -] error4
2014-09-21 23:38:50.844 DebugLogger FATAL [xxx.xxx.xxx.xxx -] fatal4
2014-09-21 23:38:50.844 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
2014-09-21 23:38:50.845 separator DEBUG [xxx.xxx.xxx.xxx -] 5) DefaultLogger (FATAL)
2014-09-21 23:38:50.845 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
2014-09-21 23:38:50.846 separator DEBUG [xxx.xxx.xxx.xxx -] 6) DebugLogger (FATAL)
2014-09-21 23:38:50.846 DebugLogger FATAL [xxx.xxx.xxx.xxx -] fatal6
2014-09-21 23:38:50.846 separator DEBUG [xxx.xxx.xxx.xxx -] ==========

結果(ログファイル:app.log)
2014-09-21 23:38:50.836 separator      DEBUG    [xxx.xxx.xxx.xxx -] 1) DefaultLogger<br />
2014-09-21 23:38:50.837 DefaultLogger  WARN     [xxx.xxx.xxx.xxx -] warn1<br />
2014-09-21 23:38:50.837 DefaultLogger  ERROR    [xxx.xxx.xxx.xxx -] error1<br />
2014-09-21 23:38:50.837 DefaultLogger  FATAL    [xxx.xxx.xxx.xxx -] fatal1<br />
2014-09-21 23:38:50.838 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />
2014-09-21 23:38:50.838 separator      DEBUG    [xxx.xxx.xxx.xxx -] 2) DebugLogger<br />
2014-09-21 23:38:50.838 DebugLogger    DEBUG    [xxx.xxx.xxx.xxx -] debug2<br />
2014-09-21 23:38:50.839 DebugLogger    INFO     [xxx.xxx.xxx.xxx -] info2<br />
2014-09-21 23:38:50.839 DebugLogger    WARN     [xxx.xxx.xxx.xxx -] warn2<br />
2014-09-21 23:38:50.839 DebugLogger    ERROR    [xxx.xxx.xxx.xxx -] error2<br />
2014-09-21 23:38:50.840 DebugLogger    FATAL    [xxx.xxx.xxx.xxx -] fatal2<br />
2014-09-21 23:38:50.840 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />
2014-09-21 23:38:50.840 separator      DEBUG    [xxx.xxx.xxx.xxx -] 3) DefaultLogger (TRACE)<br />
2014-09-21 23:38:50.841 DefaultLogger  TRACE    [xxx.xxx.xxx.xxx -] trace3<br />
2014-09-21 23:38:50.841 DefaultLogger  DEBUG    [xxx.xxx.xxx.xxx -] debug3<br />
2014-09-21 23:38:50.841 DefaultLogger  INFO     [xxx.xxx.xxx.xxx -] info3<br />
2014-09-21 23:38:50.841 DefaultLogger  WARN     [xxx.xxx.xxx.xxx -] warn3<br />
2014-09-21 23:38:50.841 DefaultLogger  ERROR    [xxx.xxx.xxx.xxx -] error3<br />
2014-09-21 23:38:50.842 DefaultLogger  FATAL    [xxx.xxx.xxx.xxx -] fatal3<br />
2014-09-21 23:38:50.842 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />
2014-09-21 23:38:50.842 separator      DEBUG    [xxx.xxx.xxx.xxx -] 4) DebugLogger (TRACE)<br />
2014-09-21 23:38:50.842 DebugLogger    TRACE    [xxx.xxx.xxx.xxx -] trace4<br />
2014-09-21 23:38:50.843 DebugLogger    DEBUG    [xxx.xxx.xxx.xxx -] debug4<br />
2014-09-21 23:38:50.843 DebugLogger    INFO     [xxx.xxx.xxx.xxx -] info4<br />
2014-09-21 23:38:50.843 DebugLogger    WARN     [xxx.xxx.xxx.xxx -] warn4<br />
2014-09-21 23:38:50.844 DebugLogger    ERROR    [xxx.xxx.xxx.xxx -] error4<br />
2014-09-21 23:38:50.844 DebugLogger    FATAL    [xxx.xxx.xxx.xxx -] fatal4<br />
2014-09-21 23:38:50.844 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />
2014-09-21 23:38:50.845 separator      DEBUG    [xxx.xxx.xxx.xxx -] 5) DefaultLogger (FATAL)<br />
2014-09-21 23:38:50.845 DefaultLogger  FATAL    [xxx.xxx.xxx.xxx -] fatal5<br />
2014-09-21 23:38:50.845 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />
2014-09-21 23:38:50.846 separator      DEBUG    [xxx.xxx.xxx.xxx -] 6) DebugLogger (FATAL)<br />
2014-09-21 23:38:50.846 DebugLogger    FATAL    [xxx.xxx.xxx.xxx -] fatal6<br />
2014-09-21 23:38:50.846 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />