mumumu の日記

Development, Translation, daily life, thoughts, and so on.

2013-12-31 19:20:00 +0900

End of 2013

2013年がもうすぐ終わる。このエントリで今年を振り返っておこうと思う。

個人的には転職したことが一番大きい。Python を書き始め、Jenkins とか Fabric とか導入した気がする。社内での勉強会が始まってからは LT を沢山やった気がする。ピアレビュー の習慣もいろいろなバックグラウンドの人がいる中で楽しくやれている。ピアレビューは、自分が主張する際に根拠を持った言葉を持っていることが大きな意味を持つ。コードを良くすることにとどまらず、自分の中で技術的な重みを持つ言葉を増やす手段として、大いに役に立つ習慣だ。

今の職場は ソフトウェアエンジニア の文化という意味で、自分のキャリアの中では一番合う職場だ。この文化を大事にするとともに、来年はもっとコードのアウトプットを増やし、いろいろな立場の人との相互作用をもっと楽しみたい。

家族が増えたことも大きな出来事の一つだ。転職と重なり、なかなかに両立がハードな部分もあった。だが、こうした節目が重なったことで以前よりアウトプットしようという気概は強くなったと思う。ちょっとしたことでも言葉やコードにして多くの人に見て貰うことに喜びを見いだせたことも今年の大きな成果の一つだったのではないか。結婚などの人生における大きなイベントが、アウトプットの量の上下に影響するということはよく言われる話だが、自分的には良い方向に持って行けたのではないかという気がする。

一方で、プライベートでコードを書くことがあまりできなかった気がする。自分はもともと 0 を 1 にするのが得意な人ではないようだ。お気に入りのソフトウェアを見つけてはそれを良くしていくことに喜びを見いだすらしい。そうであっても、もっとやりようはあったのではないかという気がする。0 を 1 にした方が、当たったときにそれなりのやり甲斐を見い出せるのは間違いないだろうが、それは問題を見つけ出す能力にも直結している。つまるところ、そういう問題領域やプラットフォームを未だ見つけ出していないということなのだろう。ある意味自分探しに似ている部分もある気がするが、見つけ出すことを諦めたつもりは毛頭ないので、来年も引き続き課題として取り組んでいきたい。

今年もいろいろな方にお世話になりました。家族や周囲の助けがなければ、今の自分はないし、それはこれからも同じだろう。来年も引き続き宜しくお願いします(*´〜`)

2013-12-30 05:35:00 +0900

SQLAlchemy - When do I construct a Session, commit, and close it?

以下は、SQLAlchemyWhen do I construct a Session, when do I commit it, and when do I close it? の全訳である。この文章はデータベースへのクエリを発行するのに使う Sessionクラス を原則的にどう扱うべきかを説明しているが、途中でスコープという概念を持ち込んで説明しているので異常に英語がわかりづらくなっている。

さらに、scoped_session というヘルパ関数も持ち込んでいるので、一見しただけだと scoped_session がなんのためにあるのか非常にわかりづらくなっている。こうしたことから、全訳してメモとして残しておく。

まとめ

SQLAlchemy における Session の寿命は、データベースのデータにアクセスしたり管理したりするオブジェクトや関数と切り離し、それらの外部で扱うのが一般的なルール
Webアプリケーションの場合は、リクエストを処理し始めるときに Session のインスタンスを作り、終わるときに破棄すればよい。通常はフレームワークにそうしたフックが組み込まれているし、それを使うことを推奨
上記のような機構がない場合に、scoped_session と呼ばれるヘルパがある。

こうした内容に興味がある人は、以下の全訳を読むとよいと思う。



Sessionクラス のインスタンスを作ったり、commit や close メソッドを発行するタイミングはいつ?


要約:

Session の寿命は、データベースのデータにアクセスしたり管理したりするオブジェクトや
関数と 「切り離し、そうした関数やオブジェクトの外部で」 扱うのが一般的なルールです。

Session クラスはデータベースへのアクセスがあると思われる操作のはじめに作られるのが一般的です。

Session クラスは、 通信を始めるとすぐにトランザクションを開始します。 'autocommit' フラグを推奨されているデフォルトの 'False' にしたままだと、トランザクションは Session クラスの commit や、 rollback、そして close メソッドが実行されるまで続きます。 Session は再利用されると、以前のトランザクションが終了した後で新しいトランザクションを開始するでしょう。このことから、Session クラスは多くのトランザクションにまたがった寿命を持っているということになります。これらの二つの概念を トランザクションスコープセッションスコープ と呼ぶことにします。

ここでわかるのは、SQLAlchemy は開発者がアプリケーションの中でこれらふたつのスコープをはっきりさせておくことを推奨しているということです。これは、いつスコープが始まり、終了するかということだけでなく、これらのスコープが適用される範囲も含みます。たとえば、Session クラスのインスタンスは特定の関数やメソッド内の実行フローに対してはローカルであるべきですが、アプリケーション全体で使われるものについてはグローバルであるべきですし、これらのふたつの中間にあるインスタンスもあってよいはずです。

このスコープを決めることは、開発者にとって重荷ですし、SQLAlchemy が必然的にデータベースの扱い方に強い影響力を持つ領域です。 変更を集めて一定時間ごとにフラッシュし、メモリの状態をローカルのトランザクションと同期する unit of work パターンがあります。このパターンは意味のあるトランザクションスコープが存在する場合にだけ有用です。

アプリケーションによって状況は様々ですが、最良な Sessionクラス のスコープを決めるのは通常はあまり難しくありません。

Sessionクラス のインスタンスをトランザクションが終わると同時に破棄する方法がよくとられます。これはトランザクションスコープとセッションスコープが同じであることを意味しています。これは出発点としては良いやり方です。なぜなら、セッションスコープとトランザクションスコープを別に考える必要がないからです。

トランザクションスコープを決めるやり方については万能なものはありませんが、一般的なパターンはあります。特に Webアプリケーションを書いているなら、選択肢は決まっています。

Webアプリケーション は単一の一貫したスコープ - リクエスト - の範囲でアプリケーションが既に構築されているため、一番簡単です。このスコープはブラウザからリクエストを受け付け、レスポンスを作るためにそれを処理し、最後にブラウザにレスポンスを返すことまでを表しています。Webアプリケーションを Sessionクラス と統合するのは Sessionクラス のスコープをこのリクエストに結びつけるだけの素直なタスクです。 Session はリクエストがはじまるときに作ることもできますし、必要になったときに lazy initializationパターン を使って作ることもできます。リクエストの処理はさらに続き、アプリケーションロジックが実際のリクエストオブジェクトにアクセスするやり方と似たやり方で Session にアクセスできるシステムもあります。リクエストが終了すると、通常は Webフレームワークが提供しているイベントフックを使って Session も破棄されます。 Session によって使われたトランザクションもこの時点でコミットされます。アプリケーションが明示的にコミットするオプションもありますし、アプリケーションが保証した場合にだけコミットする場合もありますが、想定外のことが起きてリクエストが終了する際に破棄される可能性も常にあります。

ほとんどの Webアプリケーションフレームワークは Session クラスを リクエスト と結びつける基盤を確立しており、Sessionオブジェクト が適切にインスタンス化され、リクエストの終了時に破棄されるようになっています。こうした基盤は Flask と組み合わせて SQLAlchemy を使う場合は Flask-SQLAlchemy に、 PyramidZope framework と組み合わせて使う場合、Zope-SQLAlchemy のようなプロダクトに含まれています。SQLAlchemy はこれらのプロダクトをできる限り使うことを強く推奨しています。

上記のようなライブラリが利用できない場合に、SQLAlchemy は scoped_session というヘルパークラスを用意しています。このオブジェクトの使い方に関するチュートリアルが Contextual/Thread-local Sessions のページにあります。このチュートリアルでは、Session を現在のスレッドに関連づける簡単な方法や、Sessionオブジェクト を他のスコープに関連づけるパターンも示しています。

既に述べたとおり、Webアプリケーション以外では、アーキテクチャのパターンがひとつではないために明快なパターンはありません。一番良い戦略は、"オペレーション(操作)" の境界を決め、特定のスレッドが一連の操作をはじめるときに着目し、それが終わるときに commit メソッドを実行することです。以下に例を示します:

子プロセス を fork するバックグラウンドの デーモン は、 Session を子プロセスにローカルなものとして生成し、その子プロセスが実行するジョブが実行されている間それを使い、ジョブが完了したらそれを破棄したいと考えるでしょう。

コマンドラインスクリプトのために、アプリケーションはグローバルな Session をひとつ作るでしょう。これはプログラムが開始されるときに作られ、タスクが完了したらすぐに commit メソッドを実行します。

GUI インターフェイスを持つアプリケーションでは、Session のスコープは「ボタンを押したとき」のような、ユーザーが生成するイベントの範囲にするのがベストかもしれません。もしくは、明示的なユーザーの操作、たとえば一連のレコードを"取得"し、それを"保存"するといったものに対応付けてもよいかもしれません。

一般的なルールとして、アプリケーションは Session の寿命を特定のデータを扱う関数とは 別に 管理すべきです。これはデータに特有の操作と、データににアクセスし管理するコンテキストは基本的に別の関心事として扱うと言うことです。

E.g. 真似しないでね

    ### 以下は **間違ったやり方です** ###

    class ThingOne(object):
        def go(self):
            session = Session()
            try:
                session.query(FooBar).update({"x": 5})
                session.commit()
            except:
                session.rollback()
                raise

    class ThingTwo(object):
        def go(self):
            session = Session()
            try:
                session.query(Widget).update({"q": 18})
                session.commit()
            except:
                session.rollback()
                raise

    def run_my_program():
        ThingOne().go()
        ThingTwo().go()

そうではなくて、Session (と、通常はトランザクション)の寿命は、分離し、外側で扱いましょう

    ### 以下は **より良い** (ですが唯一ではない) やり方です ###

    class ThingOne(object):
        def go(self, session):
            session.query(FooBar).update({"x": 5})

    class ThingTwo(object):
        def go(self, session):
            session.query(Widget).update({"q": 18})

    def run_my_program():
        session = Session()
        try:
            ThingOne().go(session)
            ThingTwo().go(session)

            session.commit()
        except:
            session.rollback()
            raise
        finally:
            session.close()

高度な開発者は、Session やトランザクションおよび例外のハンドリングを、プログラムがやっていることの詳細からできるだけ切り離そうとするでしょう。たとえば、context manager を使い、これらをさらに分離することができます。

    ### 別の (しかし *しつこいが唯一ではない*) やり方 ###

    from contextlib import contextmanager

    @contextmanager
    def session_scope():
        """Provide a transactional scope around a series of operations."""
        session = Session()
        try:
            yield session
            session.commit()
        except:
            session.rollback()
            raise
        finally:
            session.close()


    def run_my_program():
        with session_scope() as session:
            ThingOne().go(session)
            ThingTwo().go(session)

2013-12-28 23:19:00 +0900

Can you trust software distributed via CDN?

皆様は CDN 経由で配布されるソフトウェアを信用しているだろうか。

最近は scriptタグ で埋め込む JavaScript のライブラリも増えてきて、CDN でそれを配布するプロジェクトも増えてきた。代表例が jQuery である。jQuery は通常のjs や minify されたものの他に、以下のような CDN 経由での配布も可能にしている。

1
2
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="http://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>

ダウンロードせず CDN に任せるということは、js のホストを外部に任せるということである。このことは以下の懸念に繋がる

  1. インターネットに繋がっていないと開発できない
  2. CDN 上のファイルが動かない形に変更されたら困る
  3. CDN がサービスを停止したらどうするか
  4. プロジェクト自体が活動を停止し、配布をやめたときにソースが手に入らなくなる

上記の理由から、自分は CDN を信用していない。jQuery の場合は、絶対に手元にダウンロードして利用する。自分の制御下にないところにホストを任せることほど危険なことはないと思う。

だが、自分の制御下にないソフトウェアを利用せざるを得ない場合がある。

JavaScript のグラフライブラリである Google Chart はその代表例だ。これは 利用規約 でライブラリをダウンロードすることを認めていない。バージョンアップと配布は Google にお任せである。こういうものに全面的に依存したサービスを作るのは、サービスの生死を Google に握られているようなものだ。現にこれを使っていて、ホスト先のファイルが勝手に互換性がない形で変更されて動かなくなり困ったことがある。

Google Chart を使ってから、自分の制御下にないソフトウェアはますます信用できなくなった。
同僚は「開発段階では CDN を使ってもいいけど、Production にする段階で手元に引き上げてきてそれを使うかなー」と言っていたけど、自分的にはそれすら生ぬるいと思う。

2013-12-24 00:08:00 +0900

Tiarra over SSL without stone

Tiarra といえば Perl で書かれた日本では著名な IRC Proxy であるが、SSL 経由で接続しようと思ってぐぐる と 「stone 経由で接続する」 Howto がたくさん出てくる。果ては tiarra では SSL 接続できないから znc に移行した。とかいうエントリまで出てくる。

けれども、Tiarra のソースをちょこっと読んでみると綺麗にモジュール化されており、ソケット周りも綺麗にラップされてるので、簡単なソース変更でSSL接続ができるんじゃないか。というか、ここまでやる以上、作者である Topiaたん がそれを意図して書かないわけがない。と思った。

そこで、IO::Socket::SSLtiarra の現時点での最新ソース (rev.39276) を ag(Silver Searcher) すると、 main/Tiarra/Socket/Connect.pm の _check_connect_dependency 関数に以下のようにある。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sub _check_connect_dependency {
    my $this = shift;

    my $ssl = $this->current_ssl;
    if (defined $ssl && $ssl->{version}) {
        if (!Tiarra::OptionalModules->ssl) {
            $this->_warn(
                qq{You wants to connect with SSL, }.
                    qq{but SSL support is not enabled. }.
                        qq{Use non-SSL or install IO::Socket::SSL if possible.\n});
            return 0;
        }
    }
    return 1;
}

$ssl と $ssl->{version} が定義されていれば SSL 接続に行くらしい。 $ssl = $this->current_ssl なので、それを見ると

1
2
3
4
5
6
7
sub current_ssl {
    my $this = shift;

    utils->get_first_defined(
        $this->{connecting}->{ssl},
        $this->ssl);
}

てな感じで、 $this->{connecting} に ssl が定義されてると、それを返すらしい。 $this->connecting の中身は、下のようになっている。

1
$this->{connecting} = shift @{$this->{queue}};

$this->{queue} の中身は以下のようになっている。どうやら接続オプションを渡すようだ。

1
2
3
4
5
6
7
8
9
10
    foreach my $port (@ports) {
        push (@{$this->{queue}},
              map {
                  $struct = {
                      type => $sock_type,
                      addr => $_,
                      port => $port,
                  };
              } @{$addrs_by_types{$sock_type}});
    }

じゃあ多分接続オプションって tiarra.conf に書けるよね、と思ったら、all.conf にあった。 これを参考にして ssl ブロック以下を接続に追記すると、あっさり stone なしで接続できた。 ただし、IO::Socket::SSL(と Net::SSLeay) が必要です。

verify オプションはデフォルト有効になっているので、証明書の検証に失敗して対処法がわからないようであれば no を指定してみると良いと思います。たとえば SSL が有効な freenode サーバに接続するなら、自分は今以下のように指定しています。(Debian GNU/Linux 7.3 wheezy の環境)

1
2
3
4
5
6
7
8
9
10
11
freenode {
  #  IP アドレスを指定すると名前解決が行われてエラーになる
  #  その場合は hosts の類に名前を書いてあげて、それを指定
  #  するようにする
  host: irc.freenode.net
  port: 7000

  ssl {
      version: sslv23
      ca-path: /etc/ssl/certs
  }

但し、host に IP アドレスを指定すると、 main/Tiarra/Resolver.pm で必要ない名前解決をしに行くためエラーになります(多分バグ)。そのため、host に IP アドレスを指定したくなった場合は hosts に類するものに適当な名前を書いてあげて、それを host の設定に指定すると繋がるようになります。

まとめ

・ tiarra は 別に stone なしでも SSL 接続できる
・ 要 IO::Socket::SSL(と Net::SSLeay)
・ 必要な設定は最低限以下。 それ以外の設定は all.conf を参照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ssl {
    version: sslv23

    # CA 鍵リストファイル。
    # verify (デフォルト有効)を行う場合は ca-file か ca-path の
    # どちらかを必ず指定してください。
    #ca-file: /usr/local/share/certs/ca-root-nss.crt

    # CA 鍵パス
    # verify (デフォルト有効)を行う場合は ca-file か ca-path の
    # どちらかを必ず指定してください。
    #ca-path: /etc/ssl/certs

    #  ca-path や ca-file を指定してもどうしても接続できない場合指定
    #verify: no
}

znc 皆いいとか言ってるけど日本語の扱いイマイチだしその他細かいところも気に入らないしなんでC++なのか意味わかんないしで tiarra を維持できた自分はとても幸せです。

・どうでもいいけど CodeRepos 落ちすぎな気がします。

[ Update December 24th 23:34 JST by m ]

Tiarra の作者の Topia たんから verify:no の件についていろいろ指摘を貰いました。 ca-path または ca-file で証明書のパスを指定しておかなかなったので verify に失敗していました。Debian GNU/Linux 7.3 wheezy の自分の環境では、ca-certificates パッケージと ssl-certs パッケージを入れて、 ca-path を指定すると freenode に verify した上で繋がるようになりました。

これを踏まえて、「まとめ」と freenode の接続例を更新しておきました。 Topiaたん ありがとう!

2013-12-08 00:00:00 +0900

Current status of the PHP Documentation

[Update September 19th 23:29 JST by m ]

ここに書いた内容は、更新も含めて PHP マニュアル 日本語版について に移動しています。最新の内容はそちらを参照して下さい。


 
この記事は、doc-ja Advent Calendar 8日目の参加記事です。

ここでは、PHPを使うと必ず一度は目にするであろう PHP マニュアル についてつれづれと書いてみようと思います。「PHP のドキュメント」に関する日本語の文書が少なくなってきている気がすることが、このエントリを書く動機になっています。

[更新履歴]

1) 2013/12/08 初版
2) 2018/06/17 PHP の最新バージョン と edit.php.net の記述を現状に合わせて置き換え、PHP manual generate howto を最新版のリンクに置き換えた

PHPマニュアル の現状をざっくりと

PHP マニュアル はPHP 4系から現在 (このエントリが更新されている時の最新は7.2.6) に至るまでの歴史が蓄積されており、非常に巨大なマニュアルです。これは DocBook を使って書かれており、それを適宜各ロケールの翻訳者がボランティアで日本語に置き換えていっています。それが php.net のインフラでビルドされ、週の一度 (正確には毎週金曜日の10:00 UTC) 世界中に公開されていくという具合です。

Docbook は、SGML 由来であることによって文書構造が複雑であることや、タグの構造が壊れてしまうと即ビルドが失敗してしまうなどの欠点によって、他のフォーマットに置き換えようという話がこれまで出なかったわけではありません。ですが、DocBook の多彩な表現力にとってかわることと、現在までの巨大な蓄積を効率的に置き換えられる代替案を誰かが示せない限り、移行していくことはないでしょう。

バージョン管理システムは Subversion です。えー今の御時世に(ry という声が聞こえてきそうですが、これも Docbook と同じく強いモチベーションと代替案を示すことができる誰かがいないと、というところでしょう。

PHP マニュアル に間違いを見つけたら?

既に述べたとおり、PHP マニュアル はボランティアベースで作られているため、常に完全な翻訳を維持できているわけではありません。現在でもたくさんの未訳部分が残っていますし、人間がやるモノである以上、常に間違いが存在し得ます。

日本 PHP ユーザー会 では php-doc ML というメーリングリストをホストしており、ここを通じて PHPマニュアルに関する問い合わせや間違いの指摘などを受け付けています。ここで頂く多くの情報が、PHP マニュアル 日本語版の質の向上に大きく寄与していることは言うまでもありませんし、初期の頃からの多くの方々の協力によって現在のマニュアルは存在し、かつ維持されています。

また、PHP のバグトラッカー で Documentation Problem と記して英語でバグ報告をするのもアリですが、こちらは気付かれないことも多いので php-doc ML の方をドキュメントの指摘についてはお勧めしています。

edit.php.net について

edit.php.net は、PHPマニュアルのオンラインエディタです。コミット権限を持っていない人でもソースの修正ができます。修正すると各国語のコミッタに修正が伝わるので、ML で話を通さずに手軽に修正したい場合は、このツールを使うのもアリだと思います。

[ Update June 17th 2018 21:43 JST by m ]

2013年当時は、英語以外のロケールを編集しても各国語のコミッタには通知がなかったので、「使い勝手はイマイチ」と説明していましたが、その点は現在は改善されているため、関連する記述を削除して置き換えました

対応したフォーマット

PHP マニュアル は HTML や Windows の標準的なヘルプ形式である chm に対応したフォーマットが公開されていますが、Docbook からこれらの形式をビルドするための PhD というツールが整備されています。これを使えばHTML形式のビルドは行えるようになっていますが、chm 形式については、それをビルドするのに Windows マシンの助けが必要です。PHP マニュアル のビルド方法については、以下に纏めておいたので参考にしてください。

PHP Manual Generate Howto(2018年6月16日版)

[ Update June 17th 2018 21:43 JST by m ]

上記の Howto を、2013年版から最新版に置き換えました

PHP マニュアル のコミット権限を得るには?

PHPマニュアルをいろいろ見ていくと、継続して改善すべきだというモチベーションが強まってくるかもしれません。PHP-doc ML で指摘しまくっていてそれにも飽き足らないという人は、コミット権限を得て自分で直すことも考えてもよいかもしれません。以下は、そういうモチベーションが高い人向けです。

PHPマニュアルのコミット権限を得るには、php.net のページで英語で申請をする 必要があります。これを php.net の中の人が見てコミット権限を与えるかを決めているわけですが、彼らは何回か patch を提出した実績か、もしくは既存のコミッタの推薦があることを求めて来ます。patch については日本語版の PHP マニュアル の patch でも問題ないはずです。

基本的には php-doc ML に話を通してから申請し、既存の PHP マニュアル 日本語版のコミッタからの推薦を得た方が早く話が進むと思います (自分もそうやって 高木さん の推薦を貰いました) 。

尚、申請ページでは、最初から最後までちゃんと文章を読んでから申請フォームを埋めないと 必ず失敗するようになっています ので気を付けてください。

(番外編) chmファイルが壊れたのでホストを引き継いだ話

さて、ここからちょっと本題からずれます。

PHPマニュアル の chm 版をビルドするには、上で紹介したとおり Windows の助けが必要です。よって、chmファイルは世界のどこかのボランティアの Windows マシンでビルドされ、それを php.net 側が吸い取って世界中に公開しているわけです。

今年の5月あたりから、この世界のどこかにあるボランティアのマシンが止まったらしく、chmファイルが壊れている というバグレポートが届くようになりました。再現させようとしてみたところ、確かにビルドシステムの一部が壊れていたのでそれを直し、流石に Windows マシンをホストしている側がこのエラーに気付かんわけはあるまい、とその後は放置していました。

その後、私事でばたばたしたりなどしていて10月になりました。たまたま本家のメーリングリストを見ていると、「chm ファイルはどうなってるんだ?」 という問い合わせがあったのが目に付きました。

1
2
3
4
5
6
7
8
9
10
> On Fri, Oct 4, 2013 at 2:00 AM, Marvin D. <marvin@phpugph.com> wrote:
> > To whom it may concern,
> >
> > I just went to the downloads section of the php manual, and I can't seem
> > to find any downloadable file that is in ".chm" file format. when will that
> > format be available for downloads of the php manual?. 
> 
> We no longer have access to a Windows machine to build the CHM format
> on, so we had to discontinue it.
> Sorry

「chm ファイルのビルドは止まってるから配布不能だ」としれっとメインの人が答えているのを見て驚愕。 いったいこいつは何を言ってるんだ? どうやら彼らの管理下にマシンはない模様。であれば、自分が chm のビルドやるよ、と言っても文句は出ないよね。ということで最近引き継ぎました。

基本は上で出したリンクでの PHP Manual Generate Howto を Windows でそのままやっただけですが、週に一度定期的にビルドしてアップロードする、という箇所を自動でやらないといけなかったので、そこは Dropbox と Jenkins で補完しました。 php.net の人たちはこれらを Windows でやるのが大変 Annoying なタスクと言ってましたが、確かに2000年代だとそうだったでしょう。2013 年には自動 sync やビルドツールが進化してくれていて大変助かりました(´ー`; )

そんな感じで、日本の片隅でビルドした成果物が世界中に配られているわけです。翻訳をやる動機の一つって、翻訳の広がりをいろんな点で感じられることにあると個人的には思っているんですが、こうやってインフラを支えるのもそのひとつだね。と思いました。まる。

まとめ

・ 日本語PHPマニュアルの現状を紹介
・ マニュアルに間違いがあったら php-doc ML へ!!!1
・ インフラを支えることを手伝うのもまた良いことだ