Hatena::ブログ(Diary)

hnwの日記 このページをアンテナに追加 RSSフィード

[プロフィール]
 

2018年4月17日(火) 日本語のパスワードジェネレータを作ってみた このエントリーを含むブックマーク このエントリーのブックマークコメント

Webサービスを使っていると、たまに「秘密の質問」の設定を求められることがあります。


f:id:hnw:20180416042915p:image


こういう場合、個人的にはランダム文字列を登録したいと思うのですが、次のようにマルチバイト文字しか登録できないことが多い気がします。


f:id:hnw:20180416042912p:image


普通のパスワードジェネレータではマルチバイト文字のパスワード生成ができないので、このような用途には使えません。そこで、ランダムなマルチバイト文字列を生成するサービス「 秘密の答えジェネレータ」を作ってみました。


f:id:hnw:20180417094732p:image


自分でも実用しており、既に5サービスに設定しましたが、非常に便利だと感じます。


本稿ではこのサービスの技術面の詳細について紹介します。


「秘密の答えジェネレータ」の構成要素

「秘密の答えジェネレータ」はHTML+JavaScriptだけで実現されており、GitHub Pagesでホストしています。また、独自ドメインのDNSおよびSSL化はCloudFlareで行っています。


このような構成にすると月額コスト0円で運用できる点、また万一GitHub Pagesが使えなくなったようなときに移転先がいくらでも見つかる*1点が良いですね。


ブラウザ上でパスワード生成することの是非

パスワード類は自分だけが知っているのが大前提ですから、リモートサーバ上で生成された文字列はパスワードとしては不適切です。その意味で、パスワードジェネレータをサーバサイド実装するのは良いアイデアとは言えません。


前述したとおり、本サービスはHTML+JavaScriptだけで実現されています。言い換えると、ランダム文字列の生成処理は全てローカルマシンのWebブラウザ上で実行されます。つまり、技術的には安全と言えるはずです。


ただ、利用者が一見してサーバサイド実装かクライアントサイド実装かわからない、というのは問題かもしれません。


乱数生成について

JavaScript上で乱数生成する場合Math.random()を使う事例が多いかと思いますが、この関数は多くのブラウザ上で暗号論的擬似乱数生成器(CSPRNG)として実装されておらず、特にパスワード生成には向かないようです*2


一方、多くのブラウザ上で動くCSPRNGとしてwindow.crypto.getRandomValues()という関数があります。私の手元で試したところ、ChromeSafari、Edge、Firefoxの全てで動作しました。PCブラウザだけでなく、スマートフォン上のブラウザでも実装されているようです。


Operaだけはwindow.crypto.getRandomValues()が実装されていないようですが、OperaではMath.random()がCSPRNGになっているとのことです*3。つまり、window.crypto.getRandomValues()が存在しなかったときだけMath.random()にフォールバックすればブラウザ上でもセキュアに乱数生成ができると言えそうです*4


ちなみに、本実装ではwindow.crypto.getRandomValues()から取り出したUInt32の値の剰余を取ることでN文字から1文字を取り出す処理を実装しているのですが、偏りが出てしまうので厳密には良くありません。具体的には、ひらがなのランダム文字列を生成する際に「ぁ」が「ん」より0.0000000233%ほど出現しやすくなっています。将来的に修正するかもしれませんが、現時点ではそのような問題があると認識した上でご利用ください。


ES6実装について

私は普段JavaScriptをほとんど書かないのですが、そろそろ生のES6で書いても許される空気を感じたため、今回のサービスでは実験的に大半の処理をES6で書いてみました。


そのためIE11で動かなくなってしまいましたが、それ以外であればPC・スマートフォンとも大抵のブラウザで動作します。


私が使った機能はarrow functionとclassくらいではあるのですが、トランスパイラなしでES6を書ける時代はすぐそこかな、という印象を持ちました。


秘密の質問は常に悪なのか?

さて、今回秘密の質問に対して嘘の回答をすることをオススメしているわけですが、そもそも秘密の質問というやり方自体にセキュリティ上の問題があるのでしょうか?


個人的には秘密の質問が常に悪いとは思いません。重要な操作の際のみ要求するような、第二パスワード的な位置づけであれば十分意味があるように思います。一方で、秘密の質問に答えられたらパスワード変更ができるようなサービス設計は非常に問題があると感じます。


また、秘密の質問の答えを他人が推測可能であるのも問題点ではありますが、それ以外に複数のサイトで重複しやすい点(母親の旧姓や好きな食べ物など)、また平文で記録されることが多い点も問題だと言えるでしょう。つまり、あるサービスでユーザ情報の漏洩が起きた場合に、他サービスの秘密の質問の答えまでバレてしまうかもしれません。


上記のような理由から、秘密の質問の答えは毎回異なるランダム文字列にすべきだと私は考えています。


生成した文字列をどう保管するか

このサービスで生成した文字列をサービス登録後に破棄してしまうことはオススメしません。破棄するのでなく、パスワードリマインダーのメモ欄などに記録しておいた方が後々面倒が無いように思います。


秘密の質問の答えを平文で保存しておくのが不安な場合は、正しい回答と自動生成文字列を組み合わせて使うこともできます。たとえば「母親の旧姓は?」に対して「高橋ゃたぞゅもぽねん」などと入力し、後ろの8文字だけを平文で記録しておけば多少は安心度が増すかもしれません。


参考URL


まとめ

  • 「秘密の質問」向けのパスワードジェネレータを作りました
  • 半分シャレのつもりでしたが、案外実用的です
    • 入力内容を他人に見られた場合のショルダーハック対策にもなるように思います(↓入力例)

f:id:hnw:20180417215234p:image

*1:パッと思いついた中ではGAEかNetlifyが良さそうです

*2Secure random values (in Node.js)

*3cryptanalysis - Are there any Javascript CSPRNGs? - Cryptography Stack Exchange

*4:IE10以前ではセキュアじゃないと思いますが、今回はそもそも動作しないので無視しています

2018年4月14日(土) PHPのis_numeric関数は使うべきでないという話 このエントリーを含むブックマーク このエントリーのブックマークコメント

本稿は私が前職の技術ブログで執筆した記事「そのis_numeric()は適切ですか?」を改題・再編集して掲載するものです。前職には許可を取ってあります*1


本稿ではPHP関数is_numeric()の使いどころについて問題提起をしてみます。


is_numeric関数とは

さて、まずはis_numeric()リファレンスマニュアルを見てみましょう。


bool is_numeric ( mixed $var )


指定した変数が数値であるかどうかを調べます。数値形式の文字列は以下の要素から なります。(オプションの)符号、任意の数の数字、(オプションの)小数部、 そして(オプションの)指数部。つまり、+0123.45e6 は数値として有効な値です。十六進表記(0xf4c3b00c など) や二進表記 (0b10100111001 など) は認められません。


http://php.net/manual/ja/function.is-numeric.php


なるほど、与えられた引数がnumeric(数字的)かを調べるような、名前の通りの関数なんですね、というのが初見での印象かと思います。


何が問題なのか?

マニュアルの記述を見ただけでこの関数の問題点に気付く人はあまりいないかもしれません。私がこの関数について問題だと思うのは、「numeric」と言われたときに想像するものが人によって違う点です。


具体的には、ユーザーの入力値が10進整数として正しいかチェックする意図でis_numeric()を利用しているPHPプログラムを見かけることがあります。しかし、マニュアルを読めばわかるように、この関数は以下の文字列を受け取ってもtrueを返してしまいます。


  • 1.23
  • .123
  • 1e2

これらの文字列を与えてもtrueになることを忘れていないですか、というのが今回の問題提起です。もちろん、is_numeric()によるチェックをすり抜けて先の処理に進んだところで、セキュリティホールになるようなコードは考えにくいと思います。しかし、これが原因のバグというのは十分考えられるのではないでしょうか。


下記はネット上で見つけたコードを少しアレンジしたものです。


<?php
function validate_date($date) {
    $dateArr = explode("-", $date);
    if (count($dateArr) == 3 &&
        is_numeric($dateArr[0]) && strlen($dateArr[0]) == 4 &&
        is_numeric($dateArr[1]) && strlen($dateArr[1]) == 2 &&
        is_numeric($dateArr[2]) && strlen($dateArr[2]) == 2) {
          return checkdate($dateArr[1], $dateArr[2], $dateArr[0]);
    }
    return false;
}

例えば、上記のコードでvalidate_date("12e3-12-31")はtrueになりますが、この値をそのままSQL文の日付型の値として使うとSQLエラーになります。似たような状況は十分あり得るのではないでしょうか。


is_numeric()の詳細な挙動

細かい挙動が気になる人のために、以下にPHP 7.2.4でis_numeric()がtrueを返す条件を書き出してみました。(バージョンごとに挙動が異なる部分もあるのですが、下記のように把握をしておけば大抵の人にとっては十分だろうと思います)


  • 整数
  • 浮動小数点数
  • 全体が下記のいずれかの正規表現にマッチする文字列
    • [\x20\t\x0a-\x0d]*[\+\-]?[0-9]+([\.][0-9]*)?([Ee][\+\-]?[0-9]+)?
    • [\x20\t\x0a-\x0d]*[\+\-]?[\.][0-9]+([Ee][\+\-]?[0-9]+)?

ほぼマニュアルの通りの挙動です。マニュアルに書いていないことは、先頭の空白文字列を読み飛ばしてくれることくらいでしょうか。「3.」も「.3」もtrueになるのに驚かれた方がいるかもしれませんが、PHPプログラム中に浮動小数点数を書く場合も同じように書けますので、それほど意外なことではありません。


まとめ

is_numeric()引数が数値っぽいかを返す関数ですが、浮動小数点数形式の文字列であってもtrueを返します。知識としては知っている人が多いと思いますが、うっかり10進整数のチェックに使っていたりしないでしょうか。10進整数のチェックが目的であれば、正規表現^[0-9]+$などと記述するのが一番誤解が少ない書き方のような気がします。


is_numeric()のチェックの後で浮動小数点数にキャストするのが適した状況も考えられなくはありませんが、少なくとも私はそんなコードを書いたことがありませんね…。


じゃあctype_digit()を使えばいいんじゃないか?と考える人がいるかもしれませんが、これはこれで罠があるので使わない方がいいというのが私の考えです(参考:「ctype_digit関数の罠」)。

*1:「いっすかー」「いいよー」くらいのノリです

トラックバック - http://d.hatena.ne.jp/hnw/20180414

2018年4月1日(日) PHP 7.2.0からDateTimeでミリ秒表示するときの丸め処理が変わった話 このエントリーを含むブックマーク このエントリーのブックマークコメント

エイプリルフールなので(?)、PHPの日付処理の細かい挙動がひっそり変わった話の解説をします。


ちなみに本稿はSlackグループ「PHPユーザーズ」の#randomチャンネルでの議論をまとめ直したものです。議論のきっかけを下さったmsngさん、tadsanさん、do_akiさんはじめとする皆様ありがとうございました。


PHP 7.0から日付のフォーマット文字列にミリ秒を意味するvが追加された

PHP 7.0.0から、DateTime::format()でミリ秒指定ができるようになっています。


v ミリ秒 (PHP 7.0.0 で追加) Same note applies as for u. 例: 654


http://php.net/manual/ja/function.date.php


date関数と違ってDateTimeオブジェクトはマイクロ秒の処理を行うので、これをミリ秒単位に丸めるような場合にv有用というわけです。たとえば次のように利用できます。


<?php
var_dump((new \DateTime('2018-04-01 00:11:22.123456'))->format('v')); // string(3) "123"

PHP 7.2から挙動が変わった

ところで、このvの挙動がPHP 7.1と7.2とで変わりました。7.1までは1ミリ秒以下を四捨五入していたのが、7.2からは切り捨てになっています。


<?php
var_dump((new \DateTime('2018-04-01 00:11:22.345678'))->format('v')); // PHP 7.1.x: "346" / PHP 7.2.x: "345"

仕様変更が必要だった理由

仕様変更の理由を想像すると、単純に四捨五入すると困る状況があるから、ということだと思います。たとえば次のように現在時刻をミリ秒まで表示するプログラムを考えてみます。


<?php
var_dump((new \DateTime())->format('Y-m-d H:i:s.v'));

DateTimeのコンストラクタは無引数の場合現在時刻を返します。たとえば次のような値を返しているのであれば、四捨五入でも切り捨てでも全く問題はありません。


string(23) "2018-04-01 00:11:22.346"

しかし、現在時刻の秒未満が仮に999.9ミリ秒だった場合、四捨五入すると次のような結果になってしまいます。


string(24) "2018-04-01 00:11:22.1000"

この文字列全体を日付関数でparseするような場合、これでは23秒ではなく22.1秒として解釈されてしまうでしょう。つまり、vで四捨五入するのは仕様として良くないということになります。切り捨てであればこのような問題は起こらず、丸め後の値は必ず3桁になります。


一般に、何かの数を丸める場合に良かれと思って四捨五入にしてしまうことがある気がします。ところが、今回は四捨五入では問題になるという面白い事例でした。


参考URL

2018年3月21日(水) セキュリティの話題に丸腰で踏み込んでくる人を見た このエントリーを含むブックマーク このエントリーのブックマークコメント

Qiita上で「ゲームでよくされるチート手法とその対策 〜アプリケーションハッキング編〜」という記事がいいね数を集めているようですが、全セクションにツッコミどころがあるような印象です。私はセキュリティ本職というわけではありませんが、素人の私から見てもひどいと思ったところだけ個別にツッコミを入れてみます。


念のため補足しておくと、誰であろうと情報発信すること自体は大変良いことです。ただ、誤りを含んだ文章がウッカリ注目されてしまうとそれを信じてしまう人も出てくるので、大人げないと思いつつツッコミを入れる次第です。


デコンパイル(逆コンパイル)

2.の詳しい解説として、C/C++で記述されたコードをコンパイルすると機械語に変換されます。これを逆コンパイルしても、逆アセンブラまでにしかなりません。そのため、この状態ではソースコードの中身を解析するのは(人間では)非常に困難なため、ネイティブコードで書いた処理というのはデコンパイルへの対策となります。


技術用語の使い方が正確でない点は見なかったフリをするとして、C/C++で記述すれば安全だというのは幻想です。攻撃者の多くは高機能な逆アセンブラ(おそらくIDA Pro)を所有しており、昔に比べると格段に処理を追いやすい環境が整っています。基本的にはネイティブバイナリからでもコードの内容は把握可能だと考えるべきでしょう。


そもそもこのセクションの前半で紹介されている「iOS Reverse Engineeringの操作手順」もARMのアセンブリIDA Pro/Hopperで解析する話題になっており、ネイティブコードなら安全という主張とは反対の内容です。ご自身で紹介している記事なのに読んでいないのかしら?と思ってしまいますね。


話を戻すと、クライアントバイナリの挙動は全て解析される前提でシステム全体を設計すべきです。つまり、解析されて困る内容はサーバ側で実装するのが原則論になるでしょう。


絶対に秘密にしたいロジックをユーザーに配布するバイナリに入れたい場合は、自前での対策はあきらめてアンチデバッギング機能・アンチ逆アセンブル機能を持ったセキュリティ製品を購入した方が良いでしょう。実際の現場ではバレてもクリティカルじゃないけど簡単にバレるのはイヤという程度の状況だったりするため、自前実装で頑張っているところもあれば外部ソリューションに頼っているところもあるといった印象です。


乱数調整

なお、乱数を生成する際、現在時刻を基にして、擬似乱数を生成することが多いので、特定のタイミング(時刻)を狙うとうまくいく、といった都市伝説みたいな論調はあながち間違っているわけではありません。

間違いでしょう。明示的ににひどい実装をしない限りそんなことにはなりません。


引用部からすると乱数シードとして現在時刻を利用している言語・環境が多い、という風に読み取れますが、モバイルアプリ文脈でそんな環境はないはずです。現在のOSは各種割り込みのタイミングから十分なエントロピー(乱雑さ)を蓄積し、それを利用して乱数を生成しているため、十分にセキュアだと言えます(マシンの外部から乱数列を推測することは原則不可能です)。また、各言語の乱数生成の実装はOSに依存しているはずで、これらもセキュアだと言えます。


ネット上には自前実装の乱数生成器が転がっていたりして、そうしたものは時刻を乱数シードに使っているかもしれませんが、それらはセキュリティ知識の無い人が作成した脆弱な実装です。言うまでもありませんがコピペして使うなどは言語道断です。


攻撃者がOSroot権限を持っている場合など、乱数生成器の内部状態にアクセスできるようであれば次に生成される乱数を事前に知ることも可能でしょう。その意味で、重要な抽選はサーバ側で行うべきという主張自体は正しいと思います。ただし、サーバ側で実装するにしても乱数シードのエントロピー確保をOSに任せること、および採用する疑似乱数の性質を把握することの2点は必須です。


ちなみに、乱数シードのエントロピー不足でサーバ上の乱数生成器が攻撃対象になりうるという事例は私が以前会社の技術ブログ記事「PHPのセッションIDは暗号論的に弱い乱数生成器を使っており、セッションハイジャックの危険性がある」で紹介しました。古いPHPで設定がイマイチなときにしか発生しない話題ですが、ご参考まで。


余談

本当はDBトランザクションとロックに関してツッコミを入れようと思ったんですが、長編になりそうだったのでその他のところだけ記事にしました。もちろん私より適任の方が記事を書いてくださってもいいんですよ?(チラッ

RishatangRishatang 2018/03/22 20:04 文字数の都合上書けなかったのですが、私が邪推した理由はブコメの内容だけではなく、本文中の「ご自身で紹介している記事なのに読んでいないのかしら?と思ってしまいますね。」の部分も理由としてあります。

人によって感覚は違うのでhnwさんがどういうつもりで書いたのかはわかりませんが、少なくとも私は「完全な煽り、ないし元記事の筆者を馬鹿にしているな」と思いました。

「誰であろうと情報発信すること自体は大変良い」と思っているのなら自然と元記事の筆者に対して敬意を払った態度になると思うのですが、個人的にそうは見えませんでした。

以上です。

hnwhnw 2018/03/22 21:07 情報発信する姿勢はまず良いと思いますが、正しい情報を発信しようとする姿勢があった方がなお良いと私は思います。

ですから、もし自分が読んでもいない記事を紹介しているのであればそれは正しく非難されるべきでしょう。そして、その疑いを私が持っているということも隠すことではないと思います。

そのような可能性は薄い、文脈として十分に整合性がある、とRishatangさんが思われたのであれば不快に思われるのも当然かもしれません。

hnwhnw 2018/03/22 21:42 もう一点補足しておきますと、私は元記事を書いた方が悪だと思っているわけではありません。自分のメモ書き程度の文章であっても気軽に公開すること自体は悪いことではないし、むしろ良いことだと感じているのは本心です。そうした文書の中に宝が埋もれている可能性もあるわけですから。

一方で、誤りを含んだ文章が多くの人に注目されてしまうと、それを鵜呑みにしてしまう別の技術者が現れてしまうかもしれないという危惧も本心です。そうなってしまったときにその文書の品質が批判されることも当然のことだと思いますが、今回について言えば注目を集めるに至った仕組みの側の問題ではないでしょうか。つまり、技術メディアとしてのQiitaにおいて記事の品質担保の仕組みが機能していない点が一番の問題だと思います。

そもそも編集者がいないようなサイトですから品質担保が難しいのは当然なのですが、現在のQiitaはいいね数だけを基準にしたランキングを提示しているなど、いいねの数を良記事の基準のような使い方をしており、品質担保をあきらめてバズる記事を書いた方が偉い、というような姿勢のサイトに見えます。もう少し何とかできるんじゃないの?というのが私の意見です。

hnwhnw 2018/03/23 00:43 タイトルが煽りっぽくなってしまった感はあるかもしれません。セキュリティ本職の人にかかったらすごい量の添削が来そうだなーと思ったのを深く考えずに記事タイトルにしてしまいました。

taptappuntaptappun 2018/03/25 02:39 記事を書いた本人です。
記事をご覧いただきありがとうございます。
記事を書いた意図や経緯の齟齬があるようなので、この場で述べさせていただきます。
・記事に書いた内容は私がこれまでの実務での経験に基づいたもの
・表現は内容を整理して端的にまとめたもの
・参考記事は基本的に結論として述べるにあたり、裏付けを得るためのもの
この3点を基準として記事を書いています。
記事の内容が絶対正しいと述べる気もこれで十分だと述べる気も全くありません。
ただ、実務に基づいたものであるので、「理論的に考えればおかしい」という内容は大いにあります(理論的には問題があると思ったが、実際に開発、運用した中での最適解は違いました。その最たる例がボタン連打なのですが)
また、もちろん記事を書くまでに考察、調査した過程を省略してのべているものもありますし、プラットホームに依存した話のため、いっぱい注釈をつけなければ正しくないというものもあります。
しかし、それらすべて上記3点の基準に準ずる形になるように変えて書いています。
また、私は学生時代、セキュリティの研究室でしたので、ご指摘のようなことも多く経験しました。その中での経験を通して私の記事もご指摘の内容も結果としては眉唾物でしかない、ということを感じています。(セキュリティというものは問題を提起すると必ず批判があるもので、正解も絶対にありえません。)
そのため、私の記事が正しいのか間違っているのかといった評価はご覧にいただいた方にお任せしたいと思います。

hnwhnw 2018/03/25 16:21 taptappunさん

独り言くらいのつもりの記事でしたので、わざわざ反応いただきありがとうございます。

> 私は学生時代、セキュリティの研究室でしたので

ではセミプロくらいの方なんですね。であれば周囲にCTFやっててアセンブリが友達みたいな人もたくさんいそうな気がしますし、韓国や中国の方が実力が断然上だなーといった感覚値も持たれていそうな気がしますが、その上で今回の記事の内容なんですね。

> ・記事に書いた内容は私がこれまでの実務での経験に基づいたもの

なるほど。乱数調整の章で書かれた下記部分も実務で経験されたんでしょうか?

> 特定のタイミング(時刻)を狙うとうまくいく

私はそのような実装はあまり一般的ではない(乱数実装に関するバグがあるにしても、時刻ベースで攻撃が成立するような状況は珍しい)と感じました。一方、taptappunさんの書き方ではそのような実装がありふれているかのように捉えられかねないと感じたので、この点を抜き出して反論させてもらいました。

引用部の直後で例示しておられるポケモンの件もGameBoyでの事例で、昨今のモバイルOSとは相当違う環境ですので、実務での経験のお話が裏にあるとは全く予想もしませんでした。もしも実務での経験で近いお話があったのであれば、書ける範囲でその話を書いた方が良かったかもしれませんね。

> セキュリティというものは問題を提起すると必ず批判があるもので、正解も絶対にありえません

状況を限定すれば必ず正解はあると思いますよ。そうでなければセキュリティの研究や仕事って何をするんでしょう?

2018年3月6日(火) PHPメソッドのprototypeとは何か このエントリーを含むブックマーク このエントリーのブックマークコメント

なんとなくPHPマニュアルを眺めていたところ、リフレクション機能に下記のようなメソッドを見つけました。


ReflectionMethod::getPrototype — メソッドプロトタイプを (存在すれば) 取得する


http://php.net/manual/ja/reflectionmethod.getprototype.php


特定のメソッドについて、「プロトタイプ」の情報を返してくれるもののようです。しかし、この説明だけでは何の値が返ってくるのか想像がつきませんよね。本稿ではこのメソッドについて調べてみます。


プロトタイプ」の意味

そもそもPHPプロトタイプとは何を意味するのでしょう?PHP文脈では耳慣れない単語のような気がします。


私も全くわからなかったのでPHPのCソースコードを眺めてみたところ、プロトタイプとは関数の型宣言の意味だとわかりました。Cの「関数プロトタイプ」と同じ使い方です。


この型宣言インターフェース継承の実現で利用されています*1インターフェースを実装した場合、実装したメソッドインターフェースと同じ個数の引数が必要で、全て同じ型でないといけません。これはまさに関数の型チェックそのものです。継承メソッドをオーバーライドした場合も同様で、親メソッドの型と矛盾しないかどうかのチェックが走ります。


プロトタイプを確認する

では、実際にReflectionMethod::getPrototype()の動作を確認していきましょう。次のようなコードを動かしてみます。


<?php

interface Foo
{
    public function func1(int $x);
}

abstract class Bar implements Foo {
    public function func1(int $x) {
    }
    abstract public function func2(int $x, double $y);
    private function func3() {
    }
}

class Baz extends Bar {
    public function func1(int $x) {
    }
    public function func2(int $x, double $y) {
    }
    protected function func3() {
    }
}

class Baaz extends Baz {
    public function func2(int $x, double $y) {
    }
    public function func3() {
    }
}

$cl = new ReflectionClass(new Baaz());
$methods = $cl->getMethods();
foreach ($methods as $mt) {
    $proto = $mt->getPrototype();
    printf("method=%s::%s(), prototype=%s::%s() \n",
           $mt->getDeclaringClass()->getName(), $mt->getName(),
           $proto->getDeclaringClass()->getName(), $proto->getName());
}

これを実行すると次のような結果になります。


method=Baaz::func2(), prototype=Bar::func2()
method=Baaz::func3(), prototype=Baz::func3()
method=Baz::func1(), prototype=Foo::func1()

これはBaazクラスの全メソッドについて、それぞれのプロトタイプを表示したものです。


Baaz::func2プロトタイプは抽象メソッドBar::func2です。型チェックをするだけなら親のメソッドであるBaz::func2プロトタイプになっていても良い気がしますが、どうやら親子関係として一番上位で定義されたものがプロトタイプになるようです。


Baaz::func3プロトタイプは親のprotectedメソッドであるBar::func3です。親の親には同名のprivateメソッドが定義されていますが、これは子にもエクスポートされないので単に無視されています。



func1プロトタイプインターフェースであるFoo::func1となります。これもやはり最上位で定義されたものがプロトタイプになっています。


まとめ

PHPメソッドプロトタイプとは型宣言のことであり、クラスやインターフェースの親子関係において最上位で定義されたメソッドが実体となります。これは主に子メソッドの型チェックに利用されます。


普段のPHPプログラミングでは全く役に立たない知識だと思いますが、PHPのCソースコードを読むときに少しだけ役立つかも知れません。

*1:他にはClosure::fromCallable()でも使われています

トラックバック - http://d.hatena.ne.jp/hnw/20180306
 
ページビュー
2572809