Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
お前は PHP の歴史的な
理由の数を覚えているのか
Kousuke Ebihara (海老原昂輔)
<kousuke@co3k.org>
答
答
5 個
答
5 個
_人人人人人人人人人人_	
 
> 言うほどなかった <	
 
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄	
 
※ドキュメントから数えただけなので実体としてはもっとありそうですが、今回は考えません
自己紹介
• Kousuke Ebihara (海老原昂輔) a.k.a. @co3k
• 株式会社 VOYAGE GROUP (2014/02 より)
• スマホコミュケーション事業室で Python 書いてます
• そういえば PHP 全然...
歴史的な理由のある機能
• implode()
• urlencode() / rawurlencode()
• double 型 / float 型 と、 gettype() の返り値
• Phar アーカイブのマニフェスト情報
• Zend...
調査方法
• PHP 4 以降については普通に Git で潜っていく
• php-src : 公式の Git リポジトリ
• ドキュメント: git-svn で公式のリポジトリを変換
• PHP 3 以前
• museum.php.net (か...
implode()
implode() の歴史的な理由
• 「implode() は、歴史的な理由により、引数をどちらの順番
でも受けつけることが可能です」
• おそらく PHP で一番有名な「歴史的な理由」
implode() の実装
PHP_FUNCTION(implode)!
{!
********************** SNIP *********************!
if (arg2 == NULL) {!
**********...
implode() の実装
PHP_FUNCTION(implode)!
{!
********************** SNIP *********************!
if (arg2 == NULL) {!
**********...
implode() の実装
PHP_FUNCTION(implode)!
{!
********************** SNIP *********************!
if (arg2 == NULL) {!
**********...
implode() の実装
PHP_FUNCTION(implode)!
{!
********************** SNIP *********************!
if (arg2 == NULL) {!
**********...
implode() の実装
PHP_FUNCTION(implode)!
{!
********************** SNIP *********************!
if (arg2 == NULL) {!
**********...
implode() の歴史
• PHP/FI 2 には implode() も explode() も存在しない
• PHP 3 にはある
• PHP 3 時点では implode() は現在と同じ挙動に
• つまりこの「歴史的な理由」は PH...
PHP 3.0a3
(November 23 1997)
• Switched between the 1st and 2nd parameters to
explode(), so that it acts like split()

(拙訳...
split() と explode()
split() と explode()
元々は逆 (implode() が歴史的な理由により受け付ける順序と同じ�)
PHP 3.0b5
(February 24 1998)
• Made implode() accept arguments in the order
used by explode() as well

(拙訳: implode() が ex...
explode() と implode() に
何が起こったか
• PHP 3.0 開発中に explode(), implode(), split() が追加された
• このタイミングで追加、変更された機能は多いので当時の CHANGELOG...
urlencode() / rawurlencode()
URL エンコード用の 2 つの関数
• urlencode()
• 文字列を URL エンコード
• rawurlencode()
• 文字列を URL エンコード
URL エンコード用の 2 つの関数
• urlencode()
• 文字列を URL エンコード
• rawurlencode()
• 文字列を URL エンコード
(RFC 3986 に基づかない)
URL エンコード用の 2 つの関数
• urlencode()
• 文字列を URL エンコード
• rawurlencode()
• 文字列を URL エンコード
(RFC 3986 に基づかない)
(RFC 3986 に基づく)
どのような違いがあるか?
• urlencode()
• 空白 (U+0020) を + (U+003B) に置き換える
• ~ (U+007E) をエンコードする (RFC 1738 に基づく)
• rawurlencode()
• 空白 (...
どのような違いがあるか?
• urlencode()
• 空白 (U+0020) を + (U+003B) に置き換える
• ~ (U+007E) をエンコードする (RFC 1738 に基づく)
• rawurlencode()
• 空白 (...
urlencode() の歴史的な理由
• 「歴史的な理由により、この関数は RFC 3986 エンコード
(rawurlencode() を参照してください) とは異なり、空白を
+ 記号にエンコードします」
urlencode() の実装
(EBCDIC モード時の処理は省略)
PHPAPI char *php_url_encode(char const *s, int len, int
*new_length)!
{!
*************...
urlencode() の実装
(EBCDIC モード時の処理は省略)
PHPAPI char *php_url_encode(char const *s, int len, int
*new_length)!
{!
*************...
urlencode() の実装
(EBCDIC モード時の処理は省略)
PHPAPI char *php_url_encode(char const *s, int len, int
*new_length)!
{!
*************...
urlencode() の実装
(EBCDIC モード時の処理は省略)
PHPAPI char *php_url_encode(char const *s, int len, int
*new_length)!
{!
*************...
urlencode() の実装
(EBCDIC モード時の処理は省略)
PHPAPI char *php_url_encode(char const *s, int len, int
*new_length)!
{!
*************...
PHPAPI char *php_raw_url_encode(char const *s, int len, int
*new_length)!
{!
register int x, y;!
unsigned char *str;!
!
st...
PHPAPI char *php_raw_url_encode(char const *s, int len, int
*new_length)!
{!
register int x, y;!
unsigned char *str;!
!
st...
PHPAPI char *php_raw_url_encode(char const *s, int len, int
*new_length)!
{!
register int x, y;!
unsigned char *str;!
!
st...
PHPAPI char *php_raw_url_encode(char const *s, int len, int
*new_length)!
{!
register int x, y;!
unsigned char *str;!
!
st...
(つд⊂)ゴシゴシゴシ
(;゚Д゚)	
 共通化されてねぇ……!
なぜ空白を + に置き換えるか
技術/HTTP/URLエンコードで 0x20(スペース) を "+" にすべきか "%20" にすべきか - Glamenv-Septzen.net

http://www.glamenv-septzen.net...
なぜ空白を + に置き換えるか
• application/x-www-form-urlencoded のため
• W3C の規格 (たとえば HTML 5) などに含まれる (単独の
規格は存在しない)
• form が submit された...
他の言語の状況
• Python 2 (Python 3 では urllib.parse)
• RFC 3986 の URL エンコード: urllib.quote()
• application/x-www-form-urlencoded: ...
urlencode() の歴史
• PHP/FI 2.0 から存在
• この当時から空白 (U+0020) を + (U+003B) に置き
換える実装になっていた
char *php_urlencode(char *s) {!
*******...
rawurlencode() の歴史
• PHP 3.0b3 から存在 (当時は RFC 1738 ベース)
• まーた PHP 3.0 か!
• RFC 3986 は 2005 年 1 月
• PHP 3.0 は 1999 年 (PHP 3....
rawurlencode() 誕生秘話
(序章)
• 1997/06/16 (PHP/FI 2.0b12)
• UrlEncode() がスペースを + に置換するようになる
• 1997/11/12 (PHP/FI 2.0)
• UrlEnc...
rawurlencode() 誕生秘話
(事件篇)
• 1998/01/15 頃 (PHP 3.0b3 開発中)
• Jaakko Hyvätti が urlencode() で & をエンコードしないように変
更? (意図の説明を user ...
rawurlencode() 誕生秘話
(解決篇)
• 1998/01/16
• PHP 2.0 の変更が不適切ということになり、スペースを + に置換する版まで戻される
• ちなみに Jaakko は「rawurl*code() っていい名前...
urlencode() と rawurlencode()
についてのまとめ
• URL 中の文字列のエンコードをおこなう際は、
rawurlencode() を使ったほうがよい
• urlencode() を使うべき場面は滅多にない
• 名前的...
問題のあった事例
• Bug(バグ) #3383: mail_to 関数を用いるときに空白が
+ に変換されてしまう - OpenPNE 3

https://redmine.openpne.jp/issues/3383
• ここで気がついた ...
DOUBLE 型と FLOAT 型
PHP における浮動小数点数
• 精度は環境依存だが、通常 IEEE 754 の double (倍精度
浮動小数点数)
• PHP では float 型ということで統一されている
• 型キャストの際に (double) や (real) して...
浮動小数点数の (?)
歴史的な理由
• 「double は float と同じものだと考えてください。 2 種類
の名前が存在するのは、歴史的な理由によるものです」
float / double 型の歴史
• PHP/FI 2.0 (1997/11/12) : double
• PHP 3.0 (1998/06/06) : double (float, real でもキャスト
できるように)
• PHP 4...
なんで double -> float に
なったか
• 不明……
• ML では議論されてない?
• 突如として float 派が出現したように見える [要出典]
• 無理に double を float に置換する必要があったのかどう
か疑問
float 派の闘いの記録
• Hartmut Holzgraefe
• double -> float への置換をおこなった最初の人物
• 多くの double -> float の置換に貢献
• Jeroen van Wolffelaar
...
float 派の登場
• Hartmut Holzgraefe による 2001/09/21 のコミット
(2c275bf7) で、 floatval(), is_float() のエイリアスとし
て doubleval(), is_doubl...
float 派の登場
新たな double の出現
float 派の反撃
php has no ‘double’, only ’float’
しかし止まらない double の追加
そして 1 年が経ったある日
“php has no ‘double’” とはなんだったのか
ドキュメントの置換も忘れない
• r53773 (2001/08/07) by Jeroen van Wolffelaar
• r54456 (2001/08/12) by Jeroen van Wolffelaar
• r54918 (200...
ぜんぶ歴史のせいだ。
未だ残る double の痕跡
• 「歴史的な理由により、 float の場合には “double” が返
されます。 “float” とはなりません」
Phar アーカイブのマニフェスト情報
Phar アーカイブのマニフェスト
情報における歴史的な理由
• 「Phar マニフェストは高度に最適化された書式で (略) 1
バイトをこえる大きさの値はリトルエンディアン形式のバイト
順で保存されます。ただし API バージョンだけは例外で...
Phar アーカイブの構造
スタブ
マニフェスト
コンテンツ
シグネチャ (optional)
Phar アーカイブの構造
スタブ
マニフェスト
コンテンツ
シグネチャ (optional)
Phar アーカイブの起動時に実行される
PHP スクリプト。
__HALT_COMPILER(); で終了
Phar アーカイブの構造
スタブ
マニフェスト
コンテンツ
シグネチャ (optional)
Phar アーカイブの起動時に実行される
PHP スクリプト。
__HALT_COMPILER(); で終了
バージョン情報などのメタ情報
Phar アーカイブの構造
スタブ
マニフェスト
コンテンツ
シグネチャ (optional)
Phar アーカイブの起動時に実行される
PHP スクリプト。
__HALT_COMPILER(); で終了
バージョン情報などのメタ情報
アーカイ...
Phar アーカイブの構造
スタブ
マニフェスト
コンテンツ
シグネチャ (optional)
Phar アーカイブの起動時に実行される
PHP スクリプト。
__HALT_COMPILER(); で終了
バージョン情報などのメタ情報
アーカイ...
Phar アーカイブのマニフェスト
マニフェストの長さ
(Little Endian)
格納するファイルの数
(Little Endian)
ビットマップフラグ
(Little Endian)
API バージョン
(Big Endian)
エイ...
Phar アーカイブのマニフェスト
マニフェストの長さ
(Little Endian)
格納するファイルの数
(Little Endian)
ビットマップフラグ
(Little Endian)
API バージョン
(Big Endian)
エイ...
Phar アーカイブのマニフェスト
マニフェストの長さ
(Little Endian)
格納するファイルの数
(Little Endian)
ビットマップフラグ
(Little Endian)
API バージョン
(Big Endian)
エイ...
Phar アーカイブのマニフェスト
(composer.phar (1.0.0-alpha8) の例)
0x17F-82 : マニフェストの長さ (26489)
0x183-86 : ファイル数 (322)
0x187-88: API バージョ...
バージョン情報のみ
ビッグエンディアンである理由
• 3842b67 には元になった PHP_Archive に由来する理由とある
• PHP_Archive の最新の実装もバージョン情報だけビッグエンディアンで格納している
(PHP_Arch...
ZEND ENGINE の HASHTABLE
Zend Engine の HashTable
API における歴史的な理由
• 「hash exists for historical reasons and is always
ignored」

(拙訳: 引数 hash は歴史的な理由の...
Zend Engine の HashTable
API における歴史的な理由
• 時間切れで追い切れず
• まあなんとなくわかる
まとめ
• 身近な機能とかを深追いしていくのは結構楽しい
• PHP 3.0 時代のチェンジログと ML は本当にオススメ
• たとえば「昔の PHP は + 演算子で文字列結合できたんだよー」と
か無駄知識を披露してドヤ顔できる
• 5 個が...
Upcoming SlideShare
Loading in …5
×

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

28,926 views

Published on

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

Published in: Technology
  • コメント&ご紹介ありがとうございます。
    坂本さんの URL エンコードの記事は非常に素晴らしかったので、本スライド内で紹介させていただきました。こちらこそ非常に勉強になる記事をどうもありがとうございます。
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • URL中の空白と'+'の扱いについて、こちらのスライド資料を参考資料として追記しました。rawurlencode()/urlencode()の、導入時の詳細な調査、大変勉強になりました。 http://www.glamenv-septzen.net/index.php?mdl=diff&id=1170&new_v=2&old_v=1&text_diff=unified
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

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

  1. 1. お前は PHP の歴史的な 理由の数を覚えているのか Kousuke Ebihara (海老原昂輔) <kousuke@co3k.org>
  2. 2.
  3. 3. 答 5 個
  4. 4. 答 5 個 _人人人人人人人人人人_ > 言うほどなかった <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄ ※ドキュメントから数えただけなので実体としてはもっとありそうですが、今回は考えません
  5. 5. 自己紹介 • Kousuke Ebihara (海老原昂輔) a.k.a. @co3k • 株式会社 VOYAGE GROUP (2014/02 より) • スマホコミュケーション事業室で Python 書いてます • そういえば PHP 全然書いてないです • セキュリティ周り • 以前は某 OSS の SNS エンジンとかその辺やってました
  6. 6. 歴史的な理由のある機能 • implode() • urlencode() / rawurlencode() • double 型 / float 型 と、 gettype() の返り値 • Phar アーカイブのマニフェスト情報 • Zend Engine の HashTable (間に合わず)
  7. 7. 調査方法 • 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 時代も追える)
  8. 8. implode()
  9. 9. implode() の歴史的な理由 • 「implode() は、歴史的な理由により、引数をどちらの順番 でも受けつけることが可能です」 • おそらく PHP で一番有名な「歴史的な理由」
  10. 10. 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);
  11. 11. 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 引数が指定されている
  12. 12. 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 引数
  13. 13. 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 引数
  14. 14. 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 引数 配列が指定されていなければ エラー
  15. 15. implode() の歴史 • PHP/FI 2 には implode() も explode() も存在しない • PHP 3 にはある • PHP 3 時点では implode() は現在と同じ挙動に • つまりこの「歴史的な理由」は PHP/FI 2 → PHP 3 の開 発中に生まれたものと思われる
  16. 16. 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() と同じように動作するようになりました)
  17. 17. split() と explode()
  18. 18. split() と explode() 元々は逆 (implode() が歴史的な理由により受け付ける順序と同じ�)
  19. 19. PHP 3.0b5 (February 24 1998) • Made implode() accept arguments in the order used by explode() as well
 (拙訳: implode() が explode() で使われているような引 数順も受け付けるようにしました)
  20. 20. explode() と implode() に 何が起こったか • PHP 3.0 開発中に explode(), implode(), split() が追加された • このタイミングで追加、変更された機能は多いので当時の CHANGELOG を眺めているだけでも結構楽しい • PHP 3.0a3 にて、 explode() の引数順を split() に合わせた • この結果、 implode() との統一性が取れなくなったので、PHP 3.0b5 にて、 implode() では両方の引数順を受け付けるようにした • explode() が据え置きだったのは、引数が両方とも文字列型だから? • わずか 3 ヶ月の「歴史」
  21. 21. urlencode() / rawurlencode()
  22. 22. URL エンコード用の 2 つの関数 • urlencode() • 文字列を URL エンコード • rawurlencode() • 文字列を URL エンコード
  23. 23. URL エンコード用の 2 つの関数 • urlencode() • 文字列を URL エンコード • rawurlencode() • 文字列を URL エンコード (RFC 3986 に基づかない)
  24. 24. URL エンコード用の 2 つの関数 • urlencode() • 文字列を URL エンコード • rawurlencode() • 文字列を URL エンコード (RFC 3986 に基づかない) (RFC 3986 に基づく)
  25. 25. どのような違いがあるか? • urlencode() • 空白 (U+0020) を + (U+003B) に置き換える • ~ (U+007E) をエンコードする (RFC 1738 に基づく) • rawurlencode() • 空白 (U+0020) をパーセントエンコードする • ~ (U+007E) をエンコードしない (RFC 3986 に基づく)
  26. 26. どのような違いがあるか? • urlencode() • 空白 (U+0020) を + (U+003B) に置き換える • ~ (U+007E) をエンコードする (RFC 1738 に基づく) • rawurlencode() • 空白 (U+0020) をパーセントエンコードする • ~ (U+007E) をエンコードしない (RFC 3986 に基づく) 追従漏れじゃね……?
  27. 27. urlencode() の歴史的な理由 • 「歴史的な理由により、この関数は RFC 3986 エンコード (rawurlencode() を参照してください) とは異なり、空白を + 記号にエンコードします」
  28. 28. 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;! }! }
  29. 29. 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 文字ずつ走査
  30. 30. 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 に格納
  31. 31. 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 に格納
  32. 32. 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 に格納
  33. 33. 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 モード時の処理は省略)
  34. 34. 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 文字 ずつ走査
  35. 35. 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 に文字を格納
  36. 36. 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 に文字を格納
  37. 37. (つд⊂)ゴシゴシゴシ
  38. 38. (;゚Д゚) 共通化されてねぇ……!
  39. 39. なぜ空白を + に置き換えるか 技術/HTTP/URLエンコードで 0x20(スペース) を "+" にすべきか "%20" にすべきか - Glamenv-Septzen.net
 http://www.glamenv-septzen.net/view/1170
 ※PHP に関する記述 (rawurlencode() が用意された経緯など) は若干事実と異なる部分がある。本スライドで詳述
  40. 40. なぜ空白を + に置き換えるか • application/x-www-form-urlencoded のため • W3C の規格 (たとえば HTML 5) などに含まれる (単独の 規格は存在しない) • form が submit された場合のレスポンスボディのエンコー ド方式 • 空白を + に置き換えるほかはだいたいパーセントエンコード
  41. 41. 他の言語の状況 • 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() とかが代替となっている模 様
  42. 42. 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]='+';
  43. 43. 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 ベースに変更
  44. 44. 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
  45. 45. 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
  46. 46. 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
  47. 47. urlencode() と rawurlencode() についてのまとめ • URL 中の文字列のエンコードをおこなう際は、 rawurlencode() を使ったほうがよい • urlencode() を使うべき場面は滅多にない • 名前的に urlencode() の方が正しそうなので多用されがち だが…… • 自力で POST データのエンコードをしたい場合くらい • そもそも RFC�3986 にも追従していない
  48. 48. 問題のあった事例 • Bug(バグ) #3383: mail_to 関数を用いるときに空白が + に変換されてしまう - OpenPNE 3
 https://redmine.openpne.jp/issues/3383 • ここで気がついた (symfony の link_to() は urlencode() を使っている) • 空白を意図して渡していても + をスペースに展開しない メーラがあったと思われる
  49. 49. DOUBLE 型と FLOAT 型
  50. 50. PHP における浮動小数点数 • 精度は環境依存だが、通常 IEEE 754 の double (倍精度 浮動小数点数) • PHP では float 型ということで統一されている • 型キャストの際に (double) や (real) しても float にな る
  51. 51. 浮動小数点数の (?) 歴史的な理由 • 「double は float と同じものだと考えてください。 2 種類 の名前が存在するのは、歴史的な理由によるものです」
  52. 52. 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 など
  53. 53. なんで double -> float に なったか • 不明…… • ML では議論されてない? • 突如として float 派が出現したように見える [要出典] • 無理に double を float に置換する必要があったのかどう か疑問
  54. 54. float 派の闘いの記録 • Hartmut Holzgraefe • double -> float への置換をおこなった最初の人物 • 多くの double -> float の置換に貢献 • Jeroen van Wolffelaar • ドキュメントに存在するほとんどの double を float に置換した • Gabor Hojtsy
  55. 55. float 派の登場 • Hartmut Holzgraefe による 2001/09/21 のコミット (2c275bf7) で、 floatval(), is_float() のエイリアスとし て doubleval(), is_double() を使うように変更 (それまで は逆) • さらに (03f74067) で関数定義部分のコメント (返り値や 引数型などが書かれている) の double を float に置換
  56. 56. float 派の登場
  57. 57. 新たな double の出現
  58. 58. float 派の反撃 php has no ‘double’, only ’float’ しかし止まらない double の追加
  59. 59. そして 1 年が経ったある日 “php has no ‘double’” とはなんだったのか
  60. 60. ドキュメントの置換も忘れない • 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
  61. 61. ぜんぶ歴史のせいだ。
  62. 62. 未だ残る double の痕跡 • 「歴史的な理由により、 float の場合には “double” が返 されます。 “float” とはなりません」
  63. 63. Phar アーカイブのマニフェスト情報
  64. 64. Phar アーカイブのマニフェスト 情報における歴史的な理由 • 「Phar マニフェストは高度に最適化された書式で (略) 1 バイトをこえる大きさの値はリトルエンディアン形式のバイト 順で保存されます。ただし API バージョンだけは例外です。 これは 3 ニブルのデータですが、歴史的な理由によりビッ グエンディアン形式のバイト順で保存されます」
  65. 65. Phar アーカイブの構造 スタブ マニフェスト コンテンツ シグネチャ (optional)
  66. 66. Phar アーカイブの構造 スタブ マニフェスト コンテンツ シグネチャ (optional) Phar アーカイブの起動時に実行される PHP スクリプト。 __HALT_COMPILER(); で終了
  67. 67. Phar アーカイブの構造 スタブ マニフェスト コンテンツ シグネチャ (optional) Phar アーカイブの起動時に実行される PHP スクリプト。 __HALT_COMPILER(); で終了 バージョン情報などのメタ情報
  68. 68. Phar アーカイブの構造 スタブ マニフェスト コンテンツ シグネチャ (optional) Phar アーカイブの起動時に実行される PHP スクリプト。 __HALT_COMPILER(); で終了 バージョン情報などのメタ情報 アーカイブの内容
  69. 69. Phar アーカイブの構造 スタブ マニフェスト コンテンツ シグネチャ (optional) Phar アーカイブの起動時に実行される PHP スクリプト。 __HALT_COMPILER(); で終了 バージョン情報などのメタ情報 アーカイブの内容 パッケージ検証用のシグネチャ。 Phar 形式のみ
  70. 70. Phar アーカイブのマニフェスト マニフェストの長さ (Little Endian) 格納するファイルの数 (Little Endian) ビットマップフラグ (Little Endian) API バージョン (Big Endian) エイリアスの長さ (Little Endian) エイリアス (※任意桁) … メタデータの長さ (Little Endian) メタデータ (※任意桁) … ファイルのリスト (※任意桁) …
  71. 71. Phar アーカイブのマニフェスト マニフェストの長さ (Little Endian) 格納するファイルの数 (Little Endian) ビットマップフラグ (Little Endian) API バージョン (Big Endian) エイリアスの長さ (Little Endian) エイリアス (※任意桁) … メタデータの長さ (Little Endian) メタデータ (※任意桁) … ファイルのリスト (※任意桁) … ニブル単位でバージョンを表現
  72. 72. Phar アーカイブのマニフェスト マニフェストの長さ (Little Endian) 格納するファイルの数 (Little Endian) ビットマップフラグ (Little Endian) API バージョン (Big Endian) エイリアスの長さ (Little Endian) エイリアス (※任意桁) … メタデータの長さ (Little Endian) メタデータ (※任意桁) … ファイルのリスト (※任意桁) … ニブル単位でバージョンを表現 検証用のシグネチャが含まれて いるか、圧縮されたファイルが存 在するかなどのフラグ
  73. 73. 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)
  74. 74. バージョン情報のみ ビッグエンディアンである理由 • 3842b67 には元になった PHP_Archive に由来する理由とある • PHP_Archive の最新の実装もバージョン情報だけビッグエンディアンで格納している (PHP_Archive_Creator::serializeManifest()) • PHP_Archive の 2f41f8f48 ではアーカイブ作成時にビッグエンディアンで格納 しているが、展開時にはリトルエンディアンでパースしようとしている • PHP_Archive の 8931abf6 で展開時にもビッグエンディアンでパースするよう修 正された • つまり、間違えてビッグエンディアンで格納してしまったために後に引けなくなった のでは……
  75. 75. ZEND ENGINE の HASHTABLE
  76. 76. Zend Engine の HashTable API における歴史的な理由 • 「hash exists for historical reasons and is always ignored」
 (拙訳: 引数 hash は歴史的な理由のために存在し、常に 無視される)
  77. 77. Zend Engine の HashTable API における歴史的な理由 • 時間切れで追い切れず • まあなんとなくわかる
  78. 78. まとめ • 身近な機能とかを深追いしていくのは結構楽しい • PHP 3.0 時代のチェンジログと ML は本当にオススメ • たとえば「昔の PHP は + 演算子で文字列結合できたんだよー」と か無駄知識を披露してドヤ顔できる • 5 個がっつり深追いしていくだけでも結構時間が埋まるので助かった • Phar とか Zend Engine 周りの歴史的な理由が出てきたおかげで割 と闇 PHP っぽくなった気がする

×