お前は php の歴史的な理由の数を覚えているのか

78
お前は PHP の歴史的な 理由の数を覚えているのか Kousuke Ebihara (海老原昂輔) < [email protected] >

Upload: kousuke-ebihara

Post on 15-Jan-2015

20.062 views

Category:

Technology


2 download

DESCRIPTION

PHP といえば印象的なのは「歴史的な理由」 (≒黒歴史) の数々ですね。 このセッションでは、普段闇にこもっていてスポットの当たることの少ない「歴史的な理由」たちを引きずり出し、徹底追及し、頭を抱えていこうと思います。

TRANSCRIPT

Page 1: お前は PHP の歴史的な理由の数を覚えているのか

お前は PHP の歴史的な 理由の数を覚えているのか

Kousuke Ebihara (海老原昂輔)

<[email protected]>

Page 2: お前は PHP の歴史的な理由の数を覚えているのか

Page 3: お前は PHP の歴史的な理由の数を覚えているのか

5 個

Page 4: お前は PHP の歴史的な理由の数を覚えているのか

5 個_人人人人人人人人人人_

> 言うほどなかった <

 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

※ドキュメントから数えただけなので実体としてはもっとありそうですが、今回は考えません

Page 5: お前は PHP の歴史的な理由の数を覚えているのか

自己紹介

•Kousuke Ebihara (海老原昂輔) a.k.a. @co3k

•株式会社 VOYAGE GROUP (2014/02 より)

•スマホコミュケーション事業室で Python 書いてます

•そういえば PHP 全然書いてないです

•セキュリティ周り

•以前は某 OSS の SNS エンジンとかその辺やってました

Page 6: お前は PHP の歴史的な理由の数を覚えているのか

歴史的な理由のある機能

• implode()

•urlencode() / rawurlencode()

•double 型 / float 型 と、 gettype() の返り値

•Phar アーカイブのマニフェスト情報

•Zend Engine の HashTable (間に合わず)

Page 7: お前は PHP の歴史的な理由の数を覚えているのか

調査方法

•PHP 4 以降については普通に Git で潜っていく

•php-src : 公式の Git リポジトリ

•ドキュメント: git-svn で公式のリポジトリを変換

•PHP 3 以前

•museum.php.net (かなり重いので注意)

•ML

•1996/07 - 1998/01: PHP/FI Mailing List

•1996/12 以降: php-internals (marc.info なら旧 php-dev 時代も追える)

Page 8: お前は PHP の歴史的な理由の数を覚えているのか

implode()

Page 9: お前は PHP の歴史的な理由の数を覚えているのか

implode() の歴史的な理由

• 「implode() は、歴史的な理由により、引数をどちらの順番でも受けつけることが可能です」

•おそらく PHP で一番有名な「歴史的な理由」

Page 10: お前は PHP の歴史的な理由の数を覚えているのか

implode() の実装PHP_FUNCTION(implode)!{!********************** SNIP *********************!

if (arg2 == NULL) {!********************** SNIP *********************!

} else {! if (Z_TYPE_PP(arg1) == IS_ARRAY) {! arr = *arg1;! convert_to_string_ex(arg2);! delim = *arg2;! } else if (Z_TYPE_PP(arg2) == IS_ARRAY) {! arr = *arg2;! convert_to_string_ex(arg1);! delim = *arg1;! } else {!********************** SNIP *********************!

}! }!! php_implode(delim, arr, return_value TSRMLS_CC);

Page 11: お前は PHP の歴史的な理由の数を覚えているのか

implode() の実装PHP_FUNCTION(implode)!{!********************** SNIP *********************!

if (arg2 == NULL) {!********************** SNIP *********************!

} else {! if (Z_TYPE_PP(arg1) == IS_ARRAY) {! arr = *arg1;! convert_to_string_ex(arg2);! delim = *arg2;! } else if (Z_TYPE_PP(arg2) == IS_ARRAY) {! arr = *arg2;! convert_to_string_ex(arg1);! delim = *arg1;! } else {!********************** SNIP *********************!

}! }!! php_implode(delim, arr, return_value TSRMLS_CC);

第 2 引数が指定されている

Page 12: お前は PHP の歴史的な理由の数を覚えているのか

implode() の実装PHP_FUNCTION(implode)!{!********************** SNIP *********************!

if (arg2 == NULL) {!********************** SNIP *********************!

} else {! if (Z_TYPE_PP(arg1) == IS_ARRAY) {! arr = *arg1;! convert_to_string_ex(arg2);! delim = *arg2;! } else if (Z_TYPE_PP(arg2) == IS_ARRAY) {! arr = *arg2;! convert_to_string_ex(arg1);! delim = *arg1;! } else {!********************** SNIP *********************!

}! }!! php_implode(delim, arr, return_value TSRMLS_CC);

第 2 引数が指定されている

配列が第 1 引数に指定されていればデリミタは第 2 引数

Page 13: お前は PHP の歴史的な理由の数を覚えているのか

implode() の実装PHP_FUNCTION(implode)!{!********************** SNIP *********************!

if (arg2 == NULL) {!********************** SNIP *********************!

} else {! if (Z_TYPE_PP(arg1) == IS_ARRAY) {! arr = *arg1;! convert_to_string_ex(arg2);! delim = *arg2;! } else if (Z_TYPE_PP(arg2) == IS_ARRAY) {! arr = *arg2;! convert_to_string_ex(arg1);! delim = *arg1;! } else {!********************** SNIP *********************!

}! }!! php_implode(delim, arr, return_value TSRMLS_CC);

第 2 引数が指定されている

配列が第 1 引数に指定されていればデリミタは第 2 引数

配列が第 2 引数に指定されていればデリミタは第 1 引数

Page 14: お前は PHP の歴史的な理由の数を覚えているのか

implode() の実装PHP_FUNCTION(implode)!{!********************** SNIP *********************!

if (arg2 == NULL) {!********************** SNIP *********************!

} else {! if (Z_TYPE_PP(arg1) == IS_ARRAY) {! arr = *arg1;! convert_to_string_ex(arg2);! delim = *arg2;! } else if (Z_TYPE_PP(arg2) == IS_ARRAY) {! arr = *arg2;! convert_to_string_ex(arg1);! delim = *arg1;! } else {!********************** SNIP *********************!

}! }!! php_implode(delim, arr, return_value TSRMLS_CC);

第 2 引数が指定されている

配列が第 1 引数に指定されていればデリミタは第 2 引数

配列が第 2 引数に指定されていればデリミタは第 1 引数

配列が指定されていなければエラー

Page 15: お前は PHP の歴史的な理由の数を覚えているのか

implode() の歴史

•PHP/FI 2 には implode() も explode() も存在しない

•PHP 3 にはある

•PHP 3 時点では implode() は現在と同じ挙動に

•つまりこの「歴史的な理由」は PHP/FI 2 → PHP 3 の開発中に生まれたものと思われる

Page 16: お前は PHP の歴史的な理由の数を覚えているのか

PHP 3.0a3 (November 23 1997)

•Switched between the 1st and 2nd parameters to explode(), so that it acts like split() (拙訳: explode() の第 1 引数と第 2 引数を交換したので、 split() と同じように動作するようになりました)

Page 17: お前は PHP の歴史的な理由の数を覚えているのか

split() と explode()

Page 18: お前は PHP の歴史的な理由の数を覚えているのか

split() と explode()

元々は逆 (implode() が歴史的な理由により受け付ける順序と同じ�)

Page 19: お前は PHP の歴史的な理由の数を覚えているのか

PHP 3.0b5 (February 24 1998)

•Made implode() accept arguments in the order used by explode() as well (拙訳: implode() が explode() で使われているような引数順も受け付けるようにしました)

Page 20: お前は PHP の歴史的な理由の数を覚えているのか

explode() と implode() に 何が起こったか

• PHP 3.0 開発中に explode(), implode(), split() が追加された

• このタイミングで追加、変更された機能は多いので当時の CHANGELOG を眺めているだけでも結構楽しい

• PHP 3.0a3 にて、 explode() の引数順を split() に合わせた

•この結果、 implode() との統一性が取れなくなったので、PHP 3.0b5 にて、 implode() では両方の引数順を受け付けるようにした

• explode() が据え置きだったのは、引数が両方とも文字列型だから?

•わずか 3 ヶ月の「歴史」

Page 21: お前は PHP の歴史的な理由の数を覚えているのか

urlencode() / rawurlencode()

Page 22: お前は PHP の歴史的な理由の数を覚えているのか

URL エンコード用の 2 つの関数

•urlencode()

•文字列を URL エンコード

• rawurlencode()

•文字列を URL エンコード

Page 23: お前は PHP の歴史的な理由の数を覚えているのか

URL エンコード用の 2 つの関数

•urlencode()

•文字列を URL エンコード

• rawurlencode()

•文字列を URL エンコード

(RFC 3986 に基づかない)

Page 24: お前は PHP の歴史的な理由の数を覚えているのか

URL エンコード用の 2 つの関数

•urlencode()

•文字列を URL エンコード

• rawurlencode()

•文字列を URL エンコード

(RFC 3986 に基づかない)

(RFC 3986 に基づく)

Page 25: お前は PHP の歴史的な理由の数を覚えているのか

どのような違いがあるか?•urlencode()

•空白 (U+0020) を + (U+003B) に置き換える

•~ (U+007E) をエンコードする (RFC 1738 に基づく)

• rawurlencode()

•空白 (U+0020) をパーセントエンコードする

•~ (U+007E) をエンコードしない (RFC 3986 に基づく)

Page 26: お前は PHP の歴史的な理由の数を覚えているのか

どのような違いがあるか?•urlencode()

•空白 (U+0020) を + (U+003B) に置き換える

•~ (U+007E) をエンコードする (RFC 1738 に基づく)

• rawurlencode()

•空白 (U+0020) をパーセントエンコードする

•~ (U+007E) をエンコードしない (RFC 3986 に基づく)

追従漏れじゃね……?

Page 27: お前は PHP の歴史的な理由の数を覚えているのか

urlencode() の歴史的な理由

• 「歴史的な理由により、この関数は RFC 3986 エンコード (rawurlencode() を参照してください) とは異なり、空白を + 記号にエンコードします」

Page 28: お前は PHP の歴史的な理由の数を覚えているのか

urlencode() の実装 (EBCDIC モード時の処理は省略)

PHPAPI char *php_url_encode(char const *s, int len, int *new_length)!{!

********************** SNIP *********************! while (from < end) {! c = *from++;!! if (c == ' ') {! *to++ = '+';! } else if ((c < '0' && c != '-' && c != '.') ||! (c < 'A' && c > '9') ||! (c > 'Z' && c < 'a' && c != '_') ||! (c > 'z')) {! to[0] = '%';! to[1] = hexchars[c >> 4];! to[2] = hexchars[c & 15];! to += 3;! } else {! *to++ = c;! }! }

Page 29: お前は PHP の歴史的な理由の数を覚えているのか

urlencode() の実装 (EBCDIC モード時の処理は省略)

PHPAPI char *php_url_encode(char const *s, int len, int *new_length)!{!

********************** SNIP *********************! while (from < end) {! c = *from++;!! if (c == ' ') {! *to++ = '+';! } else if ((c < '0' && c != '-' && c != '.') ||! (c < 'A' && c > '9') ||! (c > 'Z' && c < 'a' && c != '_') ||! (c > 'z')) {! to[0] = '%';! to[1] = hexchars[c >> 4];! to[2] = hexchars[c & 15];! to += 3;! } else {! *to++ = c;! }! }

while ループで文字列終端まで from を 1 文字ずつ走査

Page 30: お前は PHP の歴史的な理由の数を覚えているのか

urlencode() の実装 (EBCDIC モード時の処理は省略)

PHPAPI char *php_url_encode(char const *s, int len, int *new_length)!{!

********************** SNIP *********************! while (from < end) {! c = *from++;!! if (c == ' ') {! *to++ = '+';! } else if ((c < '0' && c != '-' && c != '.') ||! (c < 'A' && c > '9') ||! (c > 'Z' && c < 'a' && c != '_') ||! (c > 'z')) {! to[0] = '%';! to[1] = hexchars[c >> 4];! to[2] = hexchars[c & 15];! to += 3;! } else {! *to++ = c;! }! }

while ループで文字列終端まで from を 1 文字ずつ走査

スペースを + に置換して to に格納

Page 31: お前は PHP の歴史的な理由の数を覚えているのか

urlencode() の実装 (EBCDIC モード時の処理は省略)

PHPAPI char *php_url_encode(char const *s, int len, int *new_length)!{!

********************** SNIP *********************! while (from < end) {! c = *from++;!! if (c == ' ') {! *to++ = '+';! } else if ((c < '0' && c != '-' && c != '.') ||! (c < 'A' && c > '9') ||! (c > 'Z' && c < 'a' && c != '_') ||! (c > 'z')) {! to[0] = '%';! to[1] = hexchars[c >> 4];! to[2] = hexchars[c & 15];! to += 3;! } else {! *to++ = c;! }! }

while ループで文字列終端まで from を 1 文字ずつ走査

スペースを + に置換して to に格納

それ以外のエンコード対象の文字はパーセントエンコードして to に格納

Page 32: お前は PHP の歴史的な理由の数を覚えているのか

urlencode() の実装 (EBCDIC モード時の処理は省略)

PHPAPI char *php_url_encode(char const *s, int len, int *new_length)!{!

********************** SNIP *********************! while (from < end) {! c = *from++;!! if (c == ' ') {! *to++ = '+';! } else if ((c < '0' && c != '-' && c != '.') ||! (c < 'A' && c > '9') ||! (c > 'Z' && c < 'a' && c != '_') ||! (c > 'z')) {! to[0] = '%';! to[1] = hexchars[c >> 4];! to[2] = hexchars[c & 15];! to += 3;! } else {! *to++ = c;! }! }

while ループで文字列終端まで from を 1 文字ずつ走査

スペースを + に置換して to に格納

それ以外のエンコード対象の文字はパーセントエンコードして to に格納

エンコードしない文字はそのまま to に格納

Page 33: お前は PHP の歴史的な理由の数を覚えているのか

PHPAPI char *php_raw_url_encode(char const *s, int len, int *new_length)!{! register int x, y;! unsigned char *str;!! str = (unsigned char *) safe_emalloc(3, len, 1);! for (x = 0, y = 0; len--; x++, y++) {! str[y] = (unsigned char) s[x];! if ((str[y] < '0' && str[y] != '-' && str[y] != '.') ||! (str[y] < 'A' && str[y] > '9') ||! (str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||! (str[y] > 'z' && str[y] != '~')) {! str[y++] = '%';! str[y++] = hexchars[(unsigned char) s[x] >> 4];! str[y] = hexchars[(unsigned char) s[x] & 15];! }! }

rawurlencode() の実装 (EBCDIC モード時の処理は省略)

Page 34: お前は PHP の歴史的な理由の数を覚えているのか

PHPAPI char *php_raw_url_encode(char const *s, int len, int *new_length)!{! register int x, y;! unsigned char *str;!! str = (unsigned char *) safe_emalloc(3, len, 1);! for (x = 0, y = 0; len--; x++, y++) {! str[y] = (unsigned char) s[x];! if ((str[y] < '0' && str[y] != '-' && str[y] != '.') ||! (str[y] < 'A' && str[y] > '9') ||! (str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||! (str[y] > 'z' && str[y] != '~')) {! str[y++] = '%';! str[y++] = hexchars[(unsigned char) s[x] >> 4];! str[y] = hexchars[(unsigned char) s[x] & 15];! }! }

rawurlencode() の実装 (EBCDIC モード時の処理は省略)

for ループで文字列終端まで 1 文字ずつ走査

Page 35: お前は PHP の歴史的な理由の数を覚えているのか

PHPAPI char *php_raw_url_encode(char const *s, int len, int *new_length)!{! register int x, y;! unsigned char *str;!! str = (unsigned char *) safe_emalloc(3, len, 1);! for (x = 0, y = 0; len--; x++, y++) {! str[y] = (unsigned char) s[x];! if ((str[y] < '0' && str[y] != '-' && str[y] != '.') ||! (str[y] < 'A' && str[y] > '9') ||! (str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||! (str[y] > 'z' && str[y] != '~')) {! str[y++] = '%';! str[y++] = hexchars[(unsigned char) s[x] >> 4];! str[y] = hexchars[(unsigned char) s[x] & 15];! }! }

rawurlencode() の実装 (EBCDIC モード時の処理は省略)

for ループで文字列終端まで 1 文字ずつ走査

とりあえず str に文字を格納

Page 36: お前は PHP の歴史的な理由の数を覚えているのか

PHPAPI char *php_raw_url_encode(char const *s, int len, int *new_length)!{! register int x, y;! unsigned char *str;!! str = (unsigned char *) safe_emalloc(3, len, 1);! for (x = 0, y = 0; len--; x++, y++) {! str[y] = (unsigned char) s[x];! if ((str[y] < '0' && str[y] != '-' && str[y] != '.') ||! (str[y] < 'A' && str[y] > '9') ||! (str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||! (str[y] > 'z' && str[y] != '~')) {! str[y++] = '%';! str[y++] = hexchars[(unsigned char) s[x] >> 4];! str[y] = hexchars[(unsigned char) s[x] & 15];! }! }

rawurlencode() の実装 (EBCDIC モード時の処理は省略)

for ループで文字列終端まで 1 文字ずつ走査

エンコード対象の文字ならパーセントエンコード

とりあえず str に文字を格納

Page 37: お前は PHP の歴史的な理由の数を覚えているのか

(つд⊂)ゴシゴシゴシ

Page 38: お前は PHP の歴史的な理由の数を覚えているのか

(;゚Д゚) 共通化されてねぇ……!

Page 39: お前は PHP の歴史的な理由の数を覚えているのか

なぜ空白を + に置き換えるか

技術/HTTP/URLエンコードで 0x20(スペース) を "+" にすべきか "%20" にすべきか - Glamenv-Septzen.net http://www.glamenv-septzen.net/view/1170※PHP に関する記述 (rawurlencode() が用意された経緯など) は若干事実と異なる部分がある。本スライドで詳述

Page 40: お前は PHP の歴史的な理由の数を覚えているのか

なぜ空白を + に置き換えるか

• application/x-www-form-urlencoded のため

•W3C の規格 (たとえば HTML 5) などに含まれる (単独の規格は存在しない)

• form が submit された場合のレスポンスボディのエンコード方式

•空白を + に置き換えるほかはだいたいパーセントエンコード

Page 41: お前は PHP の歴史的な理由の数を覚えているのか

他の言語の状況•Python 2 (Python 3 では urllib.parse)

•RFC 3986 の URL エンコード: urllib.quote()

• application/x-www-form-urlencoded: urllib.quote_plus()

•Ruby

•RFC 3986 の URL エンコード: ERB::Util.u(), URI.encode()

• application/x-www-form-urlencoded: URI.encode_www_form(), CGI.escape()

•ただし URI.encode() は obsolete で、 ERB::Util.u() とかが代替となっている模様

Page 42: お前は PHP の歴史的な理由の数を覚えているのか

urlencode() の歴史

•PHP/FI 2.0 から存在

•この当時から空白 (U+0020) を + (U+003B) に置き換える実装になっていた

char *php_urlencode(char *s) {!********************** SNIP *********************! for(x=0,y=0; s[x]; x++,y++) {! str[y] = s[x];! if(str[y]==' ') {! str[y]='+';

Page 43: お前は PHP の歴史的な理由の数を覚えているのか

rawurlencode() の歴史

•PHP 3.0b3 から存在 (当時は RFC 1738 ベース)

•まーた PHP 3.0 か!

•RFC 3986 は 2005 年 1 月

•PHP 3.0 は 1999 年 (PHP 3.0b3 は 1998 年)

•PHP 5.0 のタイミングで RFC 3986 ベースに変更

Page 44: お前は PHP の歴史的な理由の数を覚えているのか

rawurlencode() 誕生秘話 (序章)

•1997/06/16 (PHP/FI 2.0b12)

•UrlEncode() がスペースを + に置換するようになる

•1997/11/12 (PHP/FI 2.0)

•UrlEncode() が / をエンコードしないようになる (後に撤回)

•URL 文字列全体のエンコードを意図した議論のスレッドは http://marc.info/?t=90279138700001&r=1&w=4 http://marc.info/?t=90279138700002&r=1&w=2

Page 45: お前は PHP の歴史的な理由の数を覚えているのか

rawurlencode() 誕生秘話 (事件篇)

•1998/01/15 頃 (PHP 3.0b3 開発中)

• Jaakko Hyvätti が urlencode() で & をエンコードしないように変更? (意図の説明を user ML にポストしたようだが入手できず)

•PHP/FI 2.0 での変更の意図に合わせたもの

•おそらくここで rawurlencode() と formencode() (おそらくリリース前に削除) が入ったと思われる ([PHP-DEV] ML で my rawurlencode() などと説明しているので)

•PHP 3.0b3-dev の urlencode() が壊れたと報告がくる議論のスレッドは http://marc.info/?t=90279138700001&r=1&w=4 http://marc.info/?t=90279138700002&r=1&w=2

Page 46: お前は PHP の歴史的な理由の数を覚えているのか

rawurlencode() 誕生秘話 (解決篇)

•1998/01/16

•PHP 2.0 の変更が不適切ということになり、スペースを + に置換する版まで戻される

•ちなみに Jaakko は「rawurl*code() っていい名前ない?�urlpath*code() とか?」とも言ってるが Rasmus 華麗にこれをスルー

•スペースを + にする件も戻した方がいいのでは、という Jaakko の提案に Rasmus は反対

• 「スペースが + になっていれば urldecode() なしで POST data が使える」、「URL 中の + は自動的にスペースにデコードされる」(= つまり + なら URL と POST data どっちもいける)

•既存のコードが壊れる議論のスレッドは http://marc.info/?t=90279138700001&r=1&w=4 http://marc.info/?t=90279138700002&r=1&w=2

Page 47: お前は PHP の歴史的な理由の数を覚えているのか

urlencode() と rawurlencode() についてのまとめ

•URL 中の文字列のエンコードをおこなう際は、 rawurlencode() を使ったほうがよい

•urlencode() を使うべき場面は滅多にない

•名前的に urlencode() の方が正しそうなので多用されがちだが……

•自力で POST データのエンコードをしたい場合くらい

•そもそも RFC�3986 にも追従していない

Page 48: お前は PHP の歴史的な理由の数を覚えているのか

問題のあった事例

•Bug(バグ) #3383: mail_to 関数を用いるときに空白が + に変換されてしまう - OpenPNE 3 https://redmine.openpne.jp/issues/3383

•ここで気がついた (symfony の link_to() は urlencode() を使っている)

•空白を意図して渡していても + をスペースに展開しないメーラがあったと思われる

Page 49: お前は PHP の歴史的な理由の数を覚えているのか

DOUBLE 型と FLOAT 型

Page 50: お前は PHP の歴史的な理由の数を覚えているのか

PHP における浮動小数点数

•精度は環境依存だが、通常 IEEE 754 の double (倍精度浮動小数点数)

•PHP では float 型ということで統一されている

•型キャストの際に (double) や (real) しても float になる

Page 51: お前は PHP の歴史的な理由の数を覚えているのか

浮動小数点数の (?) 歴史的な理由

• 「double は float と同じものだと考えてください。 2 種類の名前が存在するのは、歴史的な理由によるものです」

Page 52: お前は PHP の歴史的な理由の数を覚えているのか

float / double 型の歴史

•PHP/FI 2.0 (1997/11/12) : double

•PHP 3.0 (1998/06/06) : double (float, real でもキャストできるように)

•PHP 4.1.0 (2001/12/10): float……?

•2c275bf793f70ad2a38bbf4a0f7ad12fecaca095, 03f7406711d3706af0f237e1ea03974616dd2139 など

Page 53: お前は PHP の歴史的な理由の数を覚えているのか

なんで double -> float に なったか

•不明……

•ML では議論されてない?

•突如として float 派が出現したように見える [要出典]

•無理に double を float に置換する必要があったのかどうか疑問

Page 54: お前は PHP の歴史的な理由の数を覚えているのか

float 派の闘いの記録

•Hartmut Holzgraefe

•double -> float への置換をおこなった最初の人物

•多くの double -> float の置換に貢献

• Jeroen van Wolffelaar

•ドキュメントに存在するほとんどの double を float に置換した

•Gabor Hojtsy

Page 55: お前は PHP の歴史的な理由の数を覚えているのか

float 派の登場

•Hartmut Holzgraefe による 2001/09/21 のコミット (2c275bf7) で、 floatval(), is_float() のエイリアスとして doubleval(), is_double() を使うように変更 (それまでは逆)

•さらに (03f74067) で関数定義部分のコメント (返り値や引数型などが書かれている) の double を float に置換

Page 56: お前は PHP の歴史的な理由の数を覚えているのか

float 派の登場

Page 57: お前は PHP の歴史的な理由の数を覚えているのか

新たな double の出現

Page 58: お前は PHP の歴史的な理由の数を覚えているのか

float 派の反撃

php has no ‘double’, only ’float’

しかし止まらない double の追加

Page 59: お前は PHP の歴史的な理由の数を覚えているのか

そして 1 年が経ったある日

“php has no ‘double’” とはなんだったのか

Page 60: お前は PHP の歴史的な理由の数を覚えているのか

ドキュメントの置換も忘れない

• r53773 (2001/08/07) by Jeroen van Wolffelaar

• r54456 (2001/08/12) by Jeroen van Wolffelaar

• r54918 (2001/08/14) by Jeroen van Wolffelaar

• r57972 (2001/09/21) by Hartmut Holzgraefe

• r57997 (2001/09/21) by Jeroen van Wolffelaar

• r57999 (2001/09/21) by Jeroen van Wolffelaar

Page 61: お前は PHP の歴史的な理由の数を覚えているのか

ぜんぶ歴史のせいだ。

Page 62: お前は PHP の歴史的な理由の数を覚えているのか

未だ残る double の痕跡

• 「歴史的な理由により、 float の場合には “double” が返されます。 “float” とはなりません」

Page 63: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブのマニフェスト情報

Page 64: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブのマニフェスト情報における歴史的な理由

• 「Phar マニフェストは高度に最適化された書式で (略) 1 バイトをこえる大きさの値はリトルエンディアン形式のバイト順で保存されます。ただし API バージョンだけは例外です。これは 3 ニブルのデータですが、歴史的な理由によりビッグエンディアン形式のバイト順で保存されます」

Page 65: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブの構造

スタブ

マニフェスト

コンテンツ

シグネチャ (optional)

Page 66: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブの構造

スタブ

マニフェスト

コンテンツ

シグネチャ (optional)

Phar アーカイブの起動時に実行される PHP スクリプト。 __HALT_COMPILER(); で終了

Page 67: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブの構造

スタブ

マニフェスト

コンテンツ

シグネチャ (optional)

Phar アーカイブの起動時に実行される PHP スクリプト。 __HALT_COMPILER(); で終了

バージョン情報などのメタ情報

Page 68: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブの構造

スタブ

マニフェスト

コンテンツ

シグネチャ (optional)

Phar アーカイブの起動時に実行される PHP スクリプト。 __HALT_COMPILER(); で終了

バージョン情報などのメタ情報

アーカイブの内容

Page 69: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブの構造

スタブ

マニフェスト

コンテンツ

シグネチャ (optional)

Phar アーカイブの起動時に実行される PHP スクリプト。 __HALT_COMPILER(); で終了

バージョン情報などのメタ情報

アーカイブの内容

パッケージ検証用のシグネチャ。 Phar 形式のみ

Page 70: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブのマニフェストマニフェストの長さ (Little Endian)

格納するファイルの数 (Little Endian)

ビットマップフラグ (Little Endian)

API バージョン (Big Endian)

エイリアスの長さ (Little Endian)

エイリアス (※任意桁)

メタデータの長さ (Little Endian)

メタデータ (※任意桁)

ファイルのリスト (※任意桁)

Page 71: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブのマニフェストマニフェストの長さ (Little Endian)

格納するファイルの数 (Little Endian)

ビットマップフラグ (Little Endian)

API バージョン (Big Endian)

エイリアスの長さ (Little Endian)

エイリアス (※任意桁)

メタデータの長さ (Little Endian)

メタデータ (※任意桁)

ファイルのリスト (※任意桁)

ニブル単位でバージョンを表現

Page 72: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブのマニフェストマニフェストの長さ (Little Endian)

格納するファイルの数 (Little Endian)

ビットマップフラグ (Little Endian)

API バージョン (Big Endian)

エイリアスの長さ (Little Endian)

エイリアス (※任意桁)

メタデータの長さ (Little Endian)

メタデータ (※任意桁)

ファイルのリスト (※任意桁)

ニブル単位でバージョンを表現 検証用のシグネチャが含まれているか、圧縮されたファイルが存在するかなどのフラグ

Page 73: お前は PHP の歴史的な理由の数を覚えているのか

Phar アーカイブのマニフェスト (composer.phar (1.0.0-alpha8) の例)

0x17F-82 : マニフェストの長さ (26489)

0x183-86 : ファイル数 (322)

0x187-88: API バージョン (1.1.0) ※最後の 4bit は未使用

0x189-8C : ビットマップフラグ (0x00010000 : この Phar には検証用シグネチャが含まれる)

0x18D-90 : この Phar のエイリアスの長さ (13)

0x191-9D : エイリアス (composer.phar)

Page 74: お前は PHP の歴史的な理由の数を覚えているのか

バージョン情報のみ ビッグエンディアンである理由

•3842b67 には元になった PHP_Archive に由来する理由とある

•PHP_Archive の最新の実装もバージョン情報だけビッグエンディアンで格納している (PHP_Archive_Creator::serializeManifest())

•PHP_Archive の 2f41f8f48 ではアーカイブ作成時にビッグエンディアンで格納しているが、展開時にはリトルエンディアンでパースしようとしている

•PHP_Archive の 8931abf6 で展開時にもビッグエンディアンでパースするよう修正された

•つまり、間違えてビッグエンディアンで格納してしまったために後に引けなくなったのでは……

Page 75: お前は PHP の歴史的な理由の数を覚えているのか

ZEND ENGINE の HASHTABLE

Page 76: お前は PHP の歴史的な理由の数を覚えているのか

Zend Engine の HashTable API における歴史的な理由

• 「hash exists for historical reasons and is always ignored」(拙訳: 引数 hash は歴史的な理由のために存在し、常に無視される)

Page 77: お前は PHP の歴史的な理由の数を覚えているのか

Zend Engine の HashTable API における歴史的な理由

•時間切れで追い切れず

•まあなんとなくわかる

Page 78: お前は PHP の歴史的な理由の数を覚えているのか

まとめ

•身近な機能とかを深追いしていくのは結構楽しい

•PHP 3.0 時代のチェンジログと ML は本当にオススメ

•たとえば「昔の PHP は + 演算子で文字列結合できたんだよー」とか無駄知識を披露してドヤ顔できる

•5 個がっつり深追いしていくだけでも結構時間が埋まるので助かった

•Phar とか Zend Engine 周りの歴史的な理由が出てきたおかげで割と闇 PHP っぽくなった気がする